openlp/openlp/core/widgets/widgets.py

453 lines
21 KiB
Python

# -*- 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 <https://www.gnu.org/licenses/>. #
##########################################################################
"""
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