# -*- coding: utf-8 -*- ########################################################################## # OpenLP - Open Source Lyrics Projection # # ---------------------------------------------------------------------- # # Copyright (c) 2008-2022 OpenLP Developers # # ---------------------------------------------------------------------- # # This program is free software: you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # # the Free Software Foundation, either version 3 of the License, or # # (at your option) any later version. # # # # This program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # GNU General Public License for more details. # # # # You should have received a copy of the GNU General Public License # # along with this program. If not, see . # ########################################################################## """ The :mod:`~openlp.core.widgets.widgets` module contains custom widgets used in OpenLP """ from PyQt5 import QtCore, QtWidgets from openlp.core.common.i18n import translate from openlp.core.common.registry import Registry from openlp.core.common.settings import ProxyMode from openlp.core.lib.ui import critical_error_message_box SCREENS_LAYOUT_STYLE = """ #screen_frame { background-color: palette(base); } #screen_frame QPushButton { background-color: palette(window); border: 3px solid palette(text); border-radius: 3px; height: 100px; outline: 0; width: 150px; } #screen_frame QPushButton:checked { border-color: palette(highlight); } """ class ProxyWidget(QtWidgets.QGroupBox): """ A proxy settings widget that implements loading and saving its settings. """ def __init__(self, parent=None): """ Initialise the widget. :param QtWidgets.QWidget | None parent: The widgets parent """ super().__init__(parent) self.settings = Registry().get('settings') self._setup() def _setup(self): """ A setup method seperate from __init__ to allow easier testing """ self.setup_ui() self.load() def setup_ui(self): """ Create the widget layout and sub widgets """ self.layout = QtWidgets.QFormLayout(self) self.radio_group = QtWidgets.QButtonGroup(self) self.no_proxy_radio = QtWidgets.QRadioButton('', self) self.radio_group.addButton(self.no_proxy_radio, ProxyMode.NO_PROXY) self.layout.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.no_proxy_radio) self.use_sysem_proxy_radio = QtWidgets.QRadioButton('', self) self.radio_group.addButton(self.use_sysem_proxy_radio, ProxyMode.SYSTEM_PROXY) self.layout.setWidget(1, QtWidgets.QFormLayout.SpanningRole, self.use_sysem_proxy_radio) self.manual_proxy_radio = QtWidgets.QRadioButton('', self) self.radio_group.addButton(self.manual_proxy_radio, ProxyMode.MANUAL_PROXY) self.layout.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.manual_proxy_radio) self.http_edit = QtWidgets.QLineEdit(self) self.layout.addRow('HTTP:', self.http_edit) self.https_edit = QtWidgets.QLineEdit(self) self.layout.addRow('HTTPS:', self.https_edit) self.username_edit = QtWidgets.QLineEdit(self) self.layout.addRow('Username:', self.username_edit) self.password_edit = QtWidgets.QLineEdit(self) self.password_edit.setEchoMode(QtWidgets.QLineEdit.Password) self.layout.addRow('Password:', self.password_edit) # Signal / Slots self.radio_group.buttonToggled.connect(self.on_radio_group_button_toggled) def on_radio_group_button_toggled(self, button, checked): """ Handles the toggled signal on the radio buttons. The signal is emitted twice if a radio butting being toggled on causes another radio button in the group to be toggled off. En/Disables the `Manual Proxy` line edits depending on the currently selected radio button :param QtWidgets.QRadioButton button: The button that has toggled :param bool checked: The buttons new state """ group_id = self.radio_group.id(button) # The work around (see above comment) enable_manual_edits = group_id == ProxyMode.MANUAL_PROXY and checked self.http_edit.setEnabled(enable_manual_edits) self.https_edit.setEnabled(enable_manual_edits) self.username_edit.setEnabled(enable_manual_edits) self.password_edit.setEnabled(enable_manual_edits) def retranslate_ui(self): """ Translate the Ui """ self.setTitle(translate('OpenLP.ProxyWidget', 'Proxy Server Settings')) self.no_proxy_radio.setText(translate('OpenLP.ProxyWidget', 'No prox&y')) self.use_sysem_proxy_radio.setText(translate('OpenLP.ProxyWidget', '&Use system proxy')) self.manual_proxy_radio.setText(translate('OpenLP.ProxyWidget', '&Manual proxy configuration')) proxy_example = translate('OpenLP.ProxyWidget', 'e.g. proxy_server_address:port_no') self.layout.labelForField(self.http_edit).setText(translate('OpenLP.ProxyWidget', 'HTTP:')) self.http_edit.setPlaceholderText(proxy_example) self.layout.labelForField(self.https_edit).setText(translate('OpenLP.ProxyWidget', 'HTTPS:')) self.https_edit.setPlaceholderText(proxy_example) self.layout.labelForField(self.username_edit).setText(translate('OpenLP.ProxyWidget', 'Username:')) self.layout.labelForField(self.password_edit).setText(translate('OpenLP.ProxyWidget', 'Password:')) def load(self): """ Load the data from the settings to the widget. """ checked_radio = self.radio_group.button(self.settings.value('advanced/proxy mode')) checked_radio.setChecked(True) self.http_edit.setText(self.settings.value('advanced/proxy http')) self.https_edit.setText(self.settings.value('advanced/proxy https')) self.username_edit.setText(self.settings.value('advanced/proxy username')) self.password_edit.setText(self.settings.value('advanced/proxy password')) def save(self): """ Save the widget data to the settings """ self.settings.setValue('advanced/proxy mode', self.radio_group.checkedId()) self.settings.setValue('advanced/proxy http', self.http_edit.text()) self.settings.setValue('advanced/proxy https', self.https_edit.text()) self.settings.setValue('advanced/proxy username', self.username_edit.text()) self.settings.setValue('advanced/proxy password', self.password_edit.text()) class ProxyDialog(QtWidgets.QDialog): """ A basic dialog to show proxy settingd """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.layout = QtWidgets.QVBoxLayout(self) self.proxy_widget = ProxyWidget(self) self.layout.addWidget(self.proxy_widget) self.button_box = \ QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel, self) self.layout.addWidget(self.button_box) self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.reject) self.retranslate_ui() def accept(self): """ Reimplement the accept slot so that the ProxyWidget settings can be saved. :rtype: None """ self.proxy_widget.save() super().accept() def retranslate_ui(self): self.proxy_widget.retranslate_ui() self.setWindowTitle(translate('OpenLP.ProxyDialog', 'Proxy Server Settings')) class ScreenButton(QtWidgets.QPushButton): """ A special button class that holds the screen information about it """ def __init__(self, parent, screen): """ Initialise this button """ super().__init__(parent) self.setObjectName('screen_{number}_button'.format(number=screen.number)) self.setCheckable(True) if isinstance(screen.geometry, QtCore.QRect): screen_size_text = translate('OpenLP.ScreenButton', f'({screen.geometry.width()} x {screen.geometry.height()} pixels)') self.setText(str(screen) + '\n' + screen_size_text) else: self.setText(str(screen)) self.screen = screen class ScreenSelectionWidget(QtWidgets.QWidget): def __init__(self, parent=None, screens=None): super().__init__(parent) self.current_screen = None self.identify_labels = [] self.screens = screens or [] self.timer = QtCore.QTimer() self.timer.setSingleShot(True) self.timer.setInterval(3000) self.timer.timeout.connect(self._on_identify_timer_shot) self.setup_ui() def setup_ui(self): self.setStyleSheet(SCREENS_LAYOUT_STYLE) self.layout = QtWidgets.QVBoxLayout(self) self.screen_frame = QtWidgets.QFrame(self) self.screen_frame.setFrameShape(QtWidgets.QFrame.StyledPanel) self.screen_frame.setFrameShadow(QtWidgets.QFrame.Sunken) self.screen_frame.setObjectName('screen_frame') self.screen_frame_layout = QtWidgets.QHBoxLayout(self.screen_frame) self.screen_frame_layout.setContentsMargins(16, 16, 16, 16) self.screen_frame_layout.setSpacing(8) self.screen_frame_layout.setObjectName('screen_frame_layout') self.layout.addWidget(self.screen_frame) self.identify_layout = QtWidgets.QHBoxLayout() self.screen_details_layout = QtWidgets.QVBoxLayout() self.screen_details_layout.setObjectName('screen_details_layout') self.screen_number_label = QtWidgets.QLabel(self) self.screen_number_label.setObjectName('screen_number_label') self.screen_details_layout.addWidget(self.screen_number_label) self.use_screen_check_box = QtWidgets.QCheckBox(self) self.use_screen_check_box.setVisible(False) self.screen_details_layout.addWidget(self.use_screen_check_box) self.display_group_box = QtWidgets.QGroupBox(self) self.display_group_box.setObjectName('display_group_box') self.display_group_box.setCheckable(True) self.display_group_box_layout = QtWidgets.QGridLayout(self.display_group_box) self.display_group_box_layout.setSpacing(8) self.display_group_box_layout.setObjectName('display_group_box_layout') self.full_screen_radio_button = QtWidgets.QRadioButton(self.display_group_box) self.full_screen_radio_button.setObjectName('full_screen_radio_button') self.display_group_box_layout.addWidget(self.full_screen_radio_button, 0, 0, 1, 4) self.custom_geometry_button = QtWidgets.QRadioButton(self.display_group_box) self.custom_geometry_button.setObjectName('custom_geometry_button') self.display_group_box_layout.addWidget(self.custom_geometry_button, 1, 0, 1, 4) self.left_label = QtWidgets.QLabel(self.display_group_box) self.left_label.setObjectName('left_label') self.display_group_box_layout.addWidget(self.left_label, 2, 1, 1, 1) self.top_label = QtWidgets.QLabel(self.display_group_box) self.top_label.setObjectName('top_label') self.display_group_box_layout.addWidget(self.top_label, 2, 2, 1, 1) self.width_label = QtWidgets.QLabel(self.display_group_box) self.width_label.setObjectName('width_label') self.display_group_box_layout.addWidget(self.width_label, 2, 3, 1, 1) self.height_label = QtWidgets.QLabel(self.display_group_box) self.height_label.setObjectName('height_label') self.display_group_box_layout.addWidget(self.height_label, 2, 4, 1, 1) self.left_spin_box = QtWidgets.QSpinBox(self.display_group_box) self.left_spin_box.setObjectName('left_spin_box') self.left_spin_box.setEnabled(False) self.display_group_box_layout.addWidget(self.left_spin_box, 3, 1, 1, 1) self.top_spin_box = QtWidgets.QSpinBox(self.display_group_box) self.top_spin_box.setObjectName('top_spin_box') self.top_spin_box.setEnabled(False) self.display_group_box_layout.addWidget(self.top_spin_box, 3, 2, 1, 1) self.width_spin_box = QtWidgets.QSpinBox(self.display_group_box) self.width_spin_box.setObjectName('width_spin_box') self.width_spin_box.setEnabled(False) self.display_group_box_layout.addWidget(self.width_spin_box, 3, 3, 1, 1) self.height_spin_box = QtWidgets.QSpinBox(self.display_group_box) self.height_spin_box.setObjectName('height_spin_box') self.height_spin_box.setEnabled(False) self.display_group_box_layout.addWidget(self.height_spin_box, 3, 4, 1, 1) self.display_group_box_layout.setColumnStretch(3, 1) self.display_group_box.setLayout(self.display_group_box_layout) self.screen_details_layout.addWidget(self.display_group_box) self.identify_layout.addLayout(self.screen_details_layout) self.identify_button = QtWidgets.QPushButton(self) self.identify_button.setObjectName('identify_button') self.identify_layout.addWidget( self.identify_button, stretch=1, alignment=QtCore.Qt.AlignRight | QtCore.Qt.AlignTop) self.screen_button_group = QtWidgets.QButtonGroup(self.screen_frame) self.screen_button_group.setExclusive(True) self.screen_button_group.setObjectName('screen_button_group') self.layout.addLayout(self.identify_layout) self.layout.addStretch() # Signals and slots self.display_group_box.clicked.connect(self.on_display_clicked) self.use_screen_check_box.clicked.connect(self.on_display_clicked) self.use_screen_check_box.toggled.connect(self.display_group_box.setChecked) self.custom_geometry_button.toggled.connect(self.height_spin_box.setEnabled) self.custom_geometry_button.toggled.connect(self.left_spin_box.setEnabled) self.custom_geometry_button.toggled.connect(self.top_spin_box.setEnabled) self.custom_geometry_button.toggled.connect(self.width_spin_box.setEnabled) self.identify_button.clicked.connect(self.on_identify_button_clicked) self._setup_spin_box(self.left_spin_box, -99999, 99999, 0) self._setup_spin_box(self.top_spin_box, -99999, 99999, 0) self._setup_spin_box(self.width_spin_box, -99999, 99999, 0) self._setup_spin_box(self.height_spin_box, -99999, 99999, 0) self.retranslate_ui() def retranslate_ui(self): self.full_screen_radio_button.setText(translate('OpenLP.ScreensTab', 'F&ull screen')) self.width_label.setText(translate('OpenLP.ScreensTab', 'Width:')) use_screen_str = translate('OpenLP.ScreensTab', 'Use this screen as a display') self.use_screen_check_box.setText(use_screen_str) self.display_group_box.setTitle(use_screen_str) self.left_label.setText(translate('OpenLP.ScreensTab', 'Left:')) self.custom_geometry_button.setText(translate('OpenLP.ScreensTab', 'Custom &geometry')) self.top_label.setText(translate('OpenLP.ScreensTab', 'Top:')) self.height_label.setText(translate('OpenLP.ScreensTab', 'Height:')) self.identify_button.setText(translate('OpenLP.ScreensTab', 'Identify Screens')) def on_display_clicked(self, is_checked): if not is_checked: critical_error_message_box(translate('OpenLP.ScreensTab', 'Select a Display'), translate('OpenLP.ScreensTab', 'You need to select at least one screen to be ' 'used as a display. Select the screen you wish to use as a display, ' 'and check the checkbox for that screen.'), parent=self, question=False) self.use_screen_check_box.setChecked(True) self.display_group_box.setChecked(True) else: for screen in self.screens: screen.is_display = False self.current_screen.is_display = True def _save_screen(self, screen): """ Save the details in the UI to the screen :param openlp.core.display.screens.Screen screen: :return: None """ if not screen: return screen.is_display = self.display_group_box.isChecked() if self.custom_geometry_button.isChecked(): screen.custom_geometry = QtCore.QRect(self.left_spin_box.value(), self.top_spin_box.value(), self.width_spin_box.value(), self.height_spin_box.value()) else: screen.custom_geometry = None def _setup_spin_box(self, spin_box, mininum, maximum, value): """ Set up the spin box :param QtWidgets.QSpinBox spin_box: :param int minimun: :param int maximum: :param int value: :return: None """ spin_box.setMinimum(mininum) spin_box.setMaximum(maximum) spin_box.setValue(value) def load(self): # Remove all the existing items in the layout while self.screen_frame_layout.count() > 0: item = self.screen_frame_layout.takeAt(0) if item.widget() is not None: widget = item.widget() if widget in self.screen_button_group.buttons(): self.screen_button_group.removeButton(widget) widget.setParent(None) widget.deleteLater() del item # Add the existing screens into the frame self.screen_frame_layout.addStretch() for screen in self.screens: screen_button = ScreenButton(self.screen_frame, screen) screen_button.clicked.connect(self.on_screen_button_clicked) self.screen_frame_layout.addWidget(screen_button) self.screen_button_group.addButton(screen_button) if screen.number == 0: screen_button.click() self.screen_frame_layout.addStretch() def save(self): """ Save the screen settings """ self._save_screen(self.current_screen) screen_settings = {} for screen in self.screens: screen_settings[screen.number] = screen.to_dict() Registry().get('settings').setValue('core/screens', screen_settings) # On save update the screens as well def use_simple_view(self): """ Hide advanced options. Added for use in the FTW :rtype: None """ self.use_screen_check_box.setVisible(True) self.display_group_box.setVisible(False) @QtCore.pyqtSlot() def _on_identify_timer_shot(self): for label in self.identify_labels: label.hide() label.setParent(None) label.deleteLater() self.identify_labels = [] def on_identify_button_clicked(self): """ Display a widget on every screen for 5 seconds """ for screen in self.screens: label = QtWidgets.QLabel(None) label.setAlignment(QtCore.Qt.AlignCenter) label.setText(str(screen)) label.setStyleSheet('font-size: 24pt; font-weight: bold; ' 'background-color: #0C0; color: #000; border: 5px solid #000;') label.setGeometry(QtCore.QRect(screen.geometry.x(), screen.geometry.y(), screen.geometry.width(), 100)) label.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.WindowDoesNotAcceptFocus) label.show() self.identify_labels.append(label) self.timer.start() def on_screen_button_clicked(self): """ Respond to a screen button being clicked """ screen = self.sender().screen if self.current_screen is not screen: self._save_screen(self.current_screen) self.screen_number_label.setText(str(screen)) self.use_screen_check_box.setChecked(screen.is_display) self.display_group_box.setChecked(screen.is_display) self.full_screen_radio_button.setChecked(screen.custom_geometry is None) self.custom_geometry_button.setChecked(screen.custom_geometry is not None) if isinstance(screen.custom_geometry, QtCore.QRect): self.left_spin_box.setValue(screen.custom_geometry.x()) self.top_spin_box.setValue(screen.custom_geometry.y()) self.width_spin_box.setValue(screen.custom_geometry.width()) self.height_spin_box.setValue(screen.custom_geometry.height()) else: self.left_spin_box.setValue(0) self.top_spin_box.setValue(0) self.width_spin_box.setValue(screen.geometry.width()) self.height_spin_box.setValue(screen.geometry.height()) self.current_screen = screen