Merge lp:~phill-ridout/openlp/wigify_screen_selection into lp:~openlp-dev/openlp/webengine-migrate

Proposed by Phill
Status: Merged
Merged at revision: 2862
Proposed branch: lp:~phill-ridout/openlp/wigify_screen_selection
Merge into: lp:~openlp-dev/openlp/webengine-migrate
Diff against target: 793 lines (+462/-231)
4 files modified
openlp/core/common/settings.py (+1/-1)
openlp/core/ui/screenstab.py (+11/-229)
openlp/core/widgets/widgets.py (+251/-1)
tests/functional/openlp_core/widgets/test_widgets.py (+199/-0)
To merge this branch: bzr merge lp:~phill-ridout/openlp/wigify_screen_selection
Reviewer Review Type Date Requested Status
Tomas Groth Approve
Review via email: mp+358590@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Tomas Groth (tomasgroth) wrote :

MERGE! :-D

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/core/common/settings.py'
2--- openlp/core/common/settings.py 2018-10-24 21:02:06 +0000
3+++ openlp/core/common/settings.py 2018-11-10 08:28:19 +0000
4@@ -84,7 +84,7 @@
5 """
6 geometry_key = 'geometry'
7 if can_override:
8- geometry_key = 'display_geometry'
9+ geometry_key = 'custom_geometry'
10 return {
11 number: {
12 'number': number,
13
14=== modified file 'openlp/core/ui/screenstab.py'
15--- openlp/core/ui/screenstab.py 2018-11-06 20:39:09 +0000
16+++ openlp/core/ui/screenstab.py 2018-11-10 08:28:19 +0000
17@@ -22,49 +22,15 @@
18 """
19 The screen settings tab in the configuration dialog
20 """
21-import logging
22-
23-from PyQt5 import QtCore, QtWidgets
24+from PyQt5 import QtWidgets
25
26 from openlp.core.common.i18n import translate
27 from openlp.core.common.settings import Settings
28 from openlp.core.display.screens import ScreenList
29 from openlp.core.lib.settingstab import SettingsTab
30+from openlp.core.common.registry import Registry
31 from openlp.core.ui.icons import UiIcons
32-
33-
34-SCREENS_LAYOUT_STYLE = """
35-#screen_frame {
36- background-color: palette(base);
37-}
38-#screen_frame QPushButton {
39- background-color: palette(window);
40- border: 3px solid palette(text);
41- border-radius: 3px;
42- height: 100px;
43- outline: 0;
44- width: 150px;
45-}
46-#screen_frame QPushButton:checked {
47- border-color: palette(highlight);
48-}
49-"""
50-log = logging.getLogger(__name__)
51-
52-
53-class ScreenButton(QtWidgets.QPushButton):
54- """
55- A special button class that holds the screen information about it
56- """
57- def __init__(self, parent, screen):
58- """
59- Initialise this button
60- """
61- super(ScreenButton, self).__init__(parent)
62- self.setObjectName('screen{number}_button'.format(number=screen.number))
63- self.setText(str(screen))
64- self.setCheckable(True)
65- self.screen = screen
66+from openlp.core.widgets.widgets import ScreenSelectionWidget
67
68
69 class ScreensTab(SettingsTab):
70@@ -75,88 +41,21 @@
71 """
72 Initialise the screen settings tab
73 """
74- self.screens = ScreenList()
75 self.icon_path = UiIcons().settings
76 screens_translated = translate('OpenLP.ScreensTab', 'Screens')
77 super(ScreensTab, self).__init__(parent, 'Screens', screens_translated)
78 self.settings_section = 'core'
79- self.current_screen = None
80- self.identify_labels = []
81
82 def setup_ui(self):
83 """
84 Set up the user interface elements
85 """
86 self.setObjectName('self')
87- self.setStyleSheet(SCREENS_LAYOUT_STYLE)
88 self.tab_layout = QtWidgets.QVBoxLayout(self)
89 self.tab_layout.setObjectName('tab_layout')
90- self.screen_frame = QtWidgets.QFrame(self)
91- self.screen_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
92- self.screen_frame.setFrameShadow(QtWidgets.QFrame.Sunken)
93- self.screen_frame.setObjectName('screen_frame')
94- self.screen_frame_layout = QtWidgets.QHBoxLayout(self.screen_frame)
95- self.screen_frame_layout.setContentsMargins(16, 16, 16, 16)
96- self.screen_frame_layout.setSpacing(8)
97- self.screen_frame_layout.setObjectName('screen_frame_layout')
98- self.tab_layout.addWidget(self.screen_frame)
99- self.screen_details_widget = QtWidgets.QWidget(self)
100- self.screen_details_widget.setObjectName('screen_details_widget')
101- self.screen_details_layout = QtWidgets.QGridLayout(self.screen_details_widget)
102- self.screen_details_layout.setSpacing(8)
103- self.screen_details_layout.setObjectName('screen_details_layout')
104- self.screen_number_label = QtWidgets.QLabel(self.screen_details_widget)
105- self.screen_number_label.setObjectName('screen_number_label')
106- self.screen_details_layout.addWidget(self.screen_number_label, 0, 0, 1, 4)
107- self.is_display_check_box = QtWidgets.QCheckBox(self.screen_details_widget)
108- self.is_display_check_box.setObjectName('is_display_check_box')
109- self.screen_details_layout.addWidget(self.is_display_check_box, 1, 0, 1, 4)
110- self.full_screen_radio_button = QtWidgets.QRadioButton(self.screen_details_widget)
111- self.full_screen_radio_button.setObjectName('full_screen_radio_button')
112- self.screen_details_layout.addWidget(self.full_screen_radio_button, 2, 0, 1, 4)
113- self.custom_geometry_button = QtWidgets.QRadioButton(self.screen_details_widget)
114- self.custom_geometry_button.setObjectName('custom_geometry_button')
115- self.screen_details_layout.addWidget(self.custom_geometry_button, 3, 0, 1, 4)
116- self.left_label = QtWidgets.QLabel(self.screen_details_widget)
117- self.left_label.setObjectName('left_label')
118- self.screen_details_layout.addWidget(self.left_label, 4, 1, 1, 1)
119- self.top_label = QtWidgets.QLabel(self.screen_details_widget)
120- self.top_label.setObjectName('top_label')
121- self.screen_details_layout.addWidget(self.top_label, 4, 2, 1, 1)
122- self.width_label = QtWidgets.QLabel(self.screen_details_widget)
123- self.width_label.setObjectName('width_label')
124- self.screen_details_layout.addWidget(self.width_label, 4, 3, 1, 1)
125- self.height_label = QtWidgets.QLabel(self.screen_details_widget)
126- self.height_label.setObjectName('height_label')
127- self.screen_details_layout.addWidget(self.height_label, 4, 4, 1, 1)
128- self.left_spin_box = QtWidgets.QSpinBox(self.screen_details_widget)
129- self.left_spin_box.setObjectName('left_spin_box')
130- self.screen_details_layout.addWidget(self.left_spin_box, 5, 1, 1, 1)
131- self.top_spin_box = QtWidgets.QSpinBox(self.screen_details_widget)
132- self.top_spin_box.setObjectName('top_spin_box')
133- self.screen_details_layout.addWidget(self.top_spin_box, 5, 2, 1, 1)
134- self.width_spin_box = QtWidgets.QSpinBox(self.screen_details_widget)
135- self.width_spin_box.setObjectName('width_spin_box')
136- self.screen_details_layout.addWidget(self.width_spin_box, 5, 3, 1, 1)
137- self.height_spin_box = QtWidgets.QSpinBox(self.screen_details_widget)
138- self.height_spin_box.setObjectName('height_spin_box')
139- self.screen_details_layout.addWidget(self.height_spin_box, 5, 4, 1, 1)
140- self.screen_details_layout.setColumnStretch(5, 1)
141- self.tab_layout.addWidget(self.screen_details_widget)
142- self.tab_layout.addStretch()
143- self.identify_button = QtWidgets.QPushButton(self)
144- self.identify_button.setGeometry(QtCore.QRect(596, 98, 124, 32))
145- self.identify_button.setObjectName('identify_button')
146- self.screen_button_group = QtWidgets.QButtonGroup(self.screen_frame)
147- self.screen_button_group.setExclusive(True)
148- self.screen_button_group.setObjectName('screen_button_group')
149- self.identify_button.clicked.connect(self.on_identify_button_clicked)
150-
151- self._setup_spin_box(self.left_spin_box, 0, 9999, 0)
152- self._setup_spin_box(self.top_spin_box, 0, 9999, 0)
153- self._setup_spin_box(self.width_spin_box, 0, 9999, 0)
154- self._setup_spin_box(self.height_spin_box, 0, 9999, 0)
155-
156+
157+ self.screen_selection_widget = ScreenSelectionWidget(self, ScreenList())
158+ self.tab_layout.addWidget(self.screen_selection_widget)
159 self.generic_group_box = QtWidgets.QGroupBox(self)
160 self.generic_group_box.setObjectName('generic_group_box')
161 self.generic_group_layout = QtWidgets.QVBoxLayout(self.generic_group_box)
162@@ -165,19 +64,12 @@
163 self.generic_group_layout.addWidget(self.display_on_monitor_check)
164 self.tab_layout.addWidget(self.generic_group_box)
165
166+ Registry().register_function('config_screen_changed', self.screen_selection_widget.load)
167+
168 self.retranslate_ui()
169
170 def retranslate_ui(self):
171- self.setWindowTitle(translate('self', 'self'))
172- self.full_screen_radio_button.setText(translate('OpenLP.ScreensTab', 'F&ull screen'))
173- self.width_label.setText(translate('OpenLP.ScreensTab', 'Width:'))
174- self.is_display_check_box.setText(translate('OpenLP.ScreensTab', 'Use this screen as a display'))
175- self.left_label.setText(translate('OpenLP.ScreensTab', 'Left:'))
176- self.custom_geometry_button.setText(translate('OpenLP.ScreensTab', 'Custom &geometry'))
177- self.top_label.setText(translate('OpenLP.ScreensTab', 'Top:'))
178- self.height_label.setText(translate('OpenLP.ScreensTab', 'Height'))
179- self.screen_number_label.setText(translate('OpenLP.ScreensTab', '<strong>Screen 1</strong>'))
180- self.identify_button.setText(translate('OpenLP.ScreensTab', 'Identify Screens'))
181+ self.setWindowTitle(translate('self', 'self')) # TODO: ???
182 self.generic_group_box.setTitle(translate('OpenLP.ScreensTab', 'Generic screen settings'))
183 self.display_on_monitor_check.setText(translate('OpenLP.ScreensTab', 'Display if a single screen'))
184
185@@ -187,130 +79,20 @@
186
187 NB: Don't call SettingsTab's resizeEvent() because we're not using its widgets.
188 """
189- button_geometry = self.identify_button.geometry()
190- frame_geometry = self.screen_frame.geometry()
191- button_geometry.moveTop(frame_geometry.bottom() + 8)
192- button_geometry.moveRight(frame_geometry.right())
193- self.identify_button.setGeometry(button_geometry)
194 QtWidgets.QWidget.resizeEvent(self, event)
195
196- def show(self):
197- """
198- Override show just to do some initialisation
199- """
200- super(ScreensTab, self).show()
201- if self.screen_frame_layout.count() > 2:
202- self.screen_frame_layout.itemAt(1).widget().click()
203-
204- def _setup_spin_box(self, spin_box, mininum, maximum, value):
205- """
206- Set up the spin box
207- """
208- spin_box.setMinimum(mininum)
209- spin_box.setMaximum(maximum)
210- spin_box.setValue(value)
211-
212- def _save_screen(self, screen):
213- """
214- Save the details in the UI to the screen
215- """
216- if not screen:
217- return
218- screen.is_display = self.is_display_check_box.isChecked()
219- if self.custom_geometry_button.isChecked():
220- custom_geometry = QtCore.QRect()
221- custom_geometry.setTop(self.top_spin_box.value())
222- custom_geometry.setLeft(self.left_spin_box.value())
223- custom_geometry.setWidth(self.width_spin_box.value())
224- custom_geometry.setHeight(self.height_spin_box.value())
225- screen.custom_geometry = custom_geometry
226- else:
227- screen.custom_geometry = None
228-
229 def load(self):
230 """
231 Load the settings to populate the tab
232 """
233 settings = Settings()
234 settings.beginGroup(self.settings_section)
235- # Remove all the existing items in the layout
236- while self.screen_frame_layout.count() > 0:
237- item = self.screen_frame_layout.takeAt(0)
238- if item.widget() is not None:
239- widget = item.widget()
240- if widget in self.screen_button_group.buttons():
241- self.screen_button_group.removeButton(widget)
242- widget.setParent(None)
243- widget.deleteLater()
244- del item
245- # Add the existing screens into the frame
246- self.screen_frame_layout.addStretch()
247- for screen in self.screens:
248- screen_button = ScreenButton(self.screen_frame, screen)
249- screen_button.clicked.connect(self.on_screen_button_clicked)
250- self.screen_frame_layout.addWidget(screen_button)
251- self.screen_button_group.addButton(screen_button)
252- self.screen_frame_layout.addStretch()
253+ self.screen_selection_widget.load()
254 # Load generic settings
255 self.display_on_monitor_check.setChecked(Settings().value('core/display on monitor'))
256
257 def save(self):
258- """
259- Save the screen settings
260- """
261- self._save_screen(self.current_screen)
262- settings = Settings()
263- screen_settings = {}
264- for screen in self.screens:
265- screen_settings[screen.number] = screen.to_dict()
266- settings.setValue('core/screens', screen_settings)
267+ self.screen_selection_widget.save()
268 settings.setValue('core/display on monitor', self.display_on_monitor_check.isChecked())
269 # On save update the screens as well
270 self.settings_form.register_post_process('config_screen_changed')
271-
272- @QtCore.pyqtSlot()
273- def _on_identify_timer_shot(self):
274- for label in self.identify_labels:
275- label.hide()
276- label.setParent(None)
277- label.deleteLater()
278- self.identify_labels = []
279-
280- def on_identify_button_clicked(self):
281- """
282- Display a widget on every screen for 5 seconds
283- """
284- for screen in self.screens:
285- label = QtWidgets.QLabel(None)
286- label.setAlignment(QtCore.Qt.AlignCenter)
287- label.font().setBold(True)
288- label.font().setPointSize(24)
289- label.setText('<font size="24">Screen {number}</font>'.format(number=screen.number + 1))
290- label.setStyleSheet('background-color: #0c0; color: #000; border: 5px solid #000;')
291- label.setGeometry(QtCore.QRect(screen.geometry.x(), screen.geometry.y(), 200, 100))
292- label.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | QtCore.Qt.WindowStaysOnTopHint |
293- QtCore.Qt.WindowDoesNotAcceptFocus)
294- label.show()
295- self.identify_labels.append(label)
296-
297- QtCore.QTimer.singleShot(3000, self._on_identify_timer_shot)
298-
299- def on_screen_button_clicked(self):
300- """
301- Respond to a screen button being clicked
302- """
303- screen = self.sender().screen
304- if self.current_screen is not screen:
305- self._save_screen(self.current_screen)
306- self.screen_number_label.setText(str(screen))
307- self.is_display_check_box.setChecked(screen.is_display)
308- self.full_screen_radio_button.setChecked(screen.custom_geometry is None)
309- self.custom_geometry_button.setChecked(screen.custom_geometry is not None)
310- self._setup_spin_box(self.left_spin_box, screen.display_geometry.y(), screen.display_geometry.right(),
311- screen.display_geometry.x())
312- self._setup_spin_box(self.top_spin_box, screen.display_geometry.y(), screen.display_geometry.bottom(),
313- screen.display_geometry.y())
314- self._setup_spin_box(self.width_spin_box, 0, screen.display_geometry.width(), screen.display_geometry.width())
315- self._setup_spin_box(self.height_spin_box, 0, screen.display_geometry.height(),
316- screen.display_geometry.height())
317- self.current_screen = screen
318
319=== modified file 'openlp/core/widgets/widgets.py'
320--- openlp/core/widgets/widgets.py 2018-06-10 06:38:16 +0000
321+++ openlp/core/widgets/widgets.py 2018-11-10 08:28:19 +0000
322@@ -22,12 +22,30 @@
323 """
324 The :mod:`~openlp.core.widgets.widgets` module contains custom widgets used in OpenLP
325 """
326-from PyQt5 import QtWidgets
327+from PyQt5 import QtCore, QtWidgets
328
329 from openlp.core.common.i18n import translate
330 from openlp.core.common.settings import ProxyMode, Settings
331
332
333+SCREENS_LAYOUT_STYLE = """
334+#screen_frame {
335+ background-color: palette(base);
336+}
337+#screen_frame QPushButton {
338+ background-color: palette(window);
339+ border: 3px solid palette(text);
340+ border-radius: 3px;
341+ height: 100px;
342+ outline: 0;
343+ width: 150px;
344+}
345+#screen_frame QPushButton:checked {
346+ border-color: palette(highlight);
347+}
348+"""
349+
350+
351 class ProxyWidget(QtWidgets.QGroupBox):
352 """
353 A proxy settings widget that implements loading and saving its settings.
354@@ -130,3 +148,235 @@
355 settings.setValue('advanced/proxy https', self.https_edit.text())
356 settings.setValue('advanced/proxy username', self.username_edit.text())
357 settings.setValue('advanced/proxy password', self.password_edit.text())
358+
359+
360+class ScreenButton(QtWidgets.QPushButton):
361+ """
362+ A special button class that holds the screen information about it
363+ """
364+ def __init__(self, parent, screen):
365+ """
366+ Initialise this button
367+ """
368+ super().__init__(parent)
369+ self.setObjectName('screen_{number}_button'.format(number=screen.number))
370+ self.setCheckable(True)
371+ self.setText(str(screen))
372+ self.screen = screen
373+
374+
375+class ScreenSelectionWidget(QtWidgets.QWidget):
376+ def __init__(self, parent=None, screens=[]):
377+ super().__init__(parent)
378+ self.current_screen = None
379+ self.identify_labels = []
380+ self.screens = screens
381+ self.timer = QtCore.QTimer()
382+ self.timer.setSingleShot(True)
383+ self.timer.setInterval(3000)
384+ self.timer.timeout.connect(self._on_identify_timer_shot)
385+ self.setup_ui()
386+
387+ def setup_ui(self):
388+ self.setStyleSheet(SCREENS_LAYOUT_STYLE)
389+ self.layout = QtWidgets.QVBoxLayout(self)
390+ self.screen_frame = QtWidgets.QFrame(self)
391+ self.screen_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
392+ self.screen_frame.setFrameShadow(QtWidgets.QFrame.Sunken)
393+ self.screen_frame.setObjectName('screen_frame')
394+ self.screen_frame_layout = QtWidgets.QHBoxLayout(self.screen_frame)
395+ self.screen_frame_layout.setContentsMargins(16, 16, 16, 16)
396+ self.screen_frame_layout.setSpacing(8)
397+ self.screen_frame_layout.setObjectName('screen_frame_layout')
398+ self.layout.addWidget(self.screen_frame)
399+ self.identify_layout = QtWidgets.QHBoxLayout(self)
400+ self.screen_details_layout = QtWidgets.QVBoxLayout(self)
401+ self.screen_details_layout.setObjectName('screen_details_layout')
402+ self.screen_number_label = QtWidgets.QLabel(self)
403+ self.screen_number_label.setObjectName('screen_number_label')
404+ self.screen_details_layout.addWidget(self.screen_number_label)
405+ self.display_group_box = QtWidgets.QGroupBox(self)
406+ self.display_group_box.setObjectName('display_group_box')
407+ self.display_group_box.setCheckable(True)
408+ self.display_group_box_layout = QtWidgets.QGridLayout(self.display_group_box)
409+ self.display_group_box_layout.setSpacing(8)
410+ self.display_group_box_layout.setObjectName('display_group_box_layout')
411+ self.full_screen_radio_button = QtWidgets.QRadioButton(self.display_group_box)
412+ self.full_screen_radio_button.setObjectName('full_screen_radio_button')
413+ self.display_group_box_layout.addWidget(self.full_screen_radio_button, 0, 0, 1, 4)
414+ self.custom_geometry_button = QtWidgets.QRadioButton(self.display_group_box)
415+ self.custom_geometry_button.setObjectName('custom_geometry_button')
416+ self.display_group_box_layout.addWidget(self.custom_geometry_button, 1, 0, 1, 4)
417+ self.left_label = QtWidgets.QLabel(self.display_group_box)
418+ self.left_label.setObjectName('left_label')
419+ self.display_group_box_layout.addWidget(self.left_label, 2, 1, 1, 1)
420+ self.top_label = QtWidgets.QLabel(self.display_group_box)
421+ self.top_label.setObjectName('top_label')
422+ self.display_group_box_layout.addWidget(self.top_label, 2, 2, 1, 1)
423+ self.width_label = QtWidgets.QLabel(self.display_group_box)
424+ self.width_label.setObjectName('width_label')
425+ self.display_group_box_layout.addWidget(self.width_label, 2, 3, 1, 1)
426+ self.height_label = QtWidgets.QLabel(self.display_group_box)
427+ self.height_label.setObjectName('height_label')
428+ self.display_group_box_layout.addWidget(self.height_label, 2, 4, 1, 1)
429+ self.left_spin_box = QtWidgets.QSpinBox(self.display_group_box)
430+ self.left_spin_box.setObjectName('left_spin_box')
431+ self.left_spin_box.setEnabled(False)
432+ self.display_group_box_layout.addWidget(self.left_spin_box, 3, 1, 1, 1)
433+ self.top_spin_box = QtWidgets.QSpinBox(self.display_group_box)
434+ self.top_spin_box.setObjectName('top_spin_box')
435+ self.top_spin_box.setEnabled(False)
436+ self.display_group_box_layout.addWidget(self.top_spin_box, 3, 2, 1, 1)
437+ self.width_spin_box = QtWidgets.QSpinBox(self.display_group_box)
438+ self.width_spin_box.setObjectName('width_spin_box')
439+ self.width_spin_box.setEnabled(False)
440+ self.display_group_box_layout.addWidget(self.width_spin_box, 3, 3, 1, 1)
441+ self.height_spin_box = QtWidgets.QSpinBox(self.display_group_box)
442+ self.height_spin_box.setObjectName('height_spin_box')
443+ self.height_spin_box.setEnabled(False)
444+ self.display_group_box_layout.addWidget(self.height_spin_box, 3, 4, 1, 1)
445+ self.display_group_box_layout.setColumnStretch(3, 1)
446+ self.display_group_box.setLayout(self.display_group_box_layout)
447+ self.screen_details_layout.addWidget(self.display_group_box)
448+ self.identify_layout.addLayout(self.screen_details_layout)
449+ self.identify_button = QtWidgets.QPushButton(self)
450+ self.identify_button.setObjectName('identify_button')
451+ self.identify_layout.addWidget(
452+ self.identify_button, stretch=1, alignment=QtCore.Qt.AlignRight | QtCore.Qt.AlignTop)
453+ self.screen_button_group = QtWidgets.QButtonGroup(self.screen_frame)
454+ self.screen_button_group.setExclusive(True)
455+ self.screen_button_group.setObjectName('screen_button_group')
456+ self.layout.addLayout(self.identify_layout)
457+ self.layout.addStretch()
458+
459+ # Signals and slots
460+ self.custom_geometry_button.toggled.connect(self.height_spin_box.setEnabled)
461+ self.custom_geometry_button.toggled.connect(self.left_spin_box.setEnabled)
462+ self.custom_geometry_button.toggled.connect(self.top_spin_box.setEnabled)
463+ self.custom_geometry_button.toggled.connect(self.width_spin_box.setEnabled)
464+ self.identify_button.clicked.connect(self.on_identify_button_clicked)
465+
466+ self._setup_spin_box(self.left_spin_box, 0, 9999, 0)
467+ self._setup_spin_box(self.top_spin_box, 0, 9999, 0)
468+ self._setup_spin_box(self.width_spin_box, 0, 9999, 0)
469+ self._setup_spin_box(self.height_spin_box, 0, 9999, 0)
470+ self.retranslate_ui()
471+
472+ def retranslate_ui(self):
473+ self.full_screen_radio_button.setText(translate('OpenLP.ScreensTab', 'F&ull screen'))
474+ self.width_label.setText(translate('OpenLP.ScreensTab', 'Width:'))
475+ self.display_group_box.setTitle(translate('OpenLP.ScreensTab', 'Use this screen as a display'))
476+ self.left_label.setText(translate('OpenLP.ScreensTab', 'Left:'))
477+ self.custom_geometry_button.setText(translate('OpenLP.ScreensTab', 'Custom &geometry'))
478+ self.top_label.setText(translate('OpenLP.ScreensTab', 'Top:'))
479+ self.height_label.setText(translate('OpenLP.ScreensTab', 'Height:'))
480+ self.identify_button.setText(translate('OpenLP.ScreensTab', 'Identify Screens'))
481+
482+ def _save_screen(self, screen):
483+ """
484+ Save the details in the UI to the screen
485+
486+ :param openlp.core.display.screens.Screen screen:
487+ :return: None
488+ """
489+ if not screen:
490+ return
491+ screen.is_display = self.display_group_box.isChecked()
492+ if self.custom_geometry_button.isChecked():
493+ screen.custom_geometry = QtCore.QRect(self.left_spin_box.value(), self.top_spin_box.value(),
494+ self.width_spin_box.value(), self.height_spin_box.value())
495+ else:
496+ screen.custom_geometry = None
497+
498+ def _setup_spin_box(self, spin_box, mininum, maximum, value):
499+ """
500+ Set up the spin box
501+
502+ :param QtWidgets.QSpinBox spin_box:
503+ :param int minimun:
504+ :param int maximum:
505+ :param int value:
506+ :return: None
507+ """
508+ spin_box.setMinimum(mininum)
509+ spin_box.setMaximum(maximum)
510+ spin_box.setValue(value)
511+
512+ def load(self):
513+ # Remove all the existing items in the layout
514+ while self.screen_frame_layout.count() > 0:
515+ item = self.screen_frame_layout.takeAt(0)
516+ if item.widget() is not None:
517+ widget = item.widget()
518+ if widget in self.screen_button_group.buttons():
519+ self.screen_button_group.removeButton(widget)
520+ widget.setParent(None)
521+ widget.deleteLater()
522+ del item
523+ # Add the existing screens into the frame
524+ self.screen_frame_layout.addStretch()
525+ for screen in self.screens:
526+ screen_button = ScreenButton(self.screen_frame, screen)
527+ screen_button.clicked.connect(self.on_screen_button_clicked)
528+ if not self.current_screen or screen.is_display:
529+ screen_button.click()
530+ self.screen_frame_layout.addWidget(screen_button)
531+ self.screen_button_group.addButton(screen_button)
532+ self.screen_frame_layout.addStretch()
533+
534+ def save(self):
535+ """
536+ Save the screen settings
537+ """
538+ self._save_screen(self.current_screen)
539+ settings = Settings()
540+ screen_settings = {}
541+ for screen in self.screens:
542+ screen_settings[screen.number] = screen.to_dict()
543+ settings.setValue('core/screens', screen_settings)
544+ # On save update the screens as well
545+
546+ @QtCore.pyqtSlot()
547+ def _on_identify_timer_shot(self):
548+ for label in self.identify_labels:
549+ label.hide()
550+ label.setParent(None)
551+ label.deleteLater()
552+ self.identify_labels = []
553+
554+ def on_identify_button_clicked(self):
555+ """
556+ Display a widget on every screen for 5 seconds
557+ """
558+ for screen in self.screens:
559+ label = QtWidgets.QLabel(None)
560+ label.setAlignment(QtCore.Qt.AlignCenter)
561+ label.setText(str(screen))
562+ label.setStyleSheet('font-size: 24pt; font-weight: bold;'
563+ 'background-color: #0C0; color: #000; border: 5px solid #000;')
564+ label.setGeometry(QtCore.QRect(screen.geometry.x(), screen.geometry.y(), screen.geometry.width(), 100))
565+ label.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | QtCore.Qt.WindowStaysOnTopHint |
566+ QtCore.Qt.WindowDoesNotAcceptFocus)
567+ label.show()
568+ self.identify_labels.append(label)
569+ self.timer.start()
570+
571+ def on_screen_button_clicked(self):
572+ """
573+ Respond to a screen button being clicked
574+ """
575+ screen = self.sender().screen
576+ if self.current_screen is not screen:
577+ self._save_screen(self.current_screen)
578+ self.screen_number_label.setText(str(screen))
579+ self.display_group_box.setChecked(screen.is_display)
580+ self.full_screen_radio_button.setChecked(screen.custom_geometry is None)
581+ self.custom_geometry_button.setChecked(screen.custom_geometry is not None)
582+ self._setup_spin_box(self.left_spin_box, screen.display_geometry.y(), screen.display_geometry.right(),
583+ screen.display_geometry.x())
584+ self._setup_spin_box(self.top_spin_box, screen.display_geometry.y(), screen.display_geometry.bottom(),
585+ screen.display_geometry.y())
586+ self._setup_spin_box(self.width_spin_box, 0, screen.display_geometry.width(), screen.display_geometry.width())
587+ self._setup_spin_box(self.height_spin_box, 0, screen.display_geometry.height(),
588+ screen.display_geometry.height())
589+ self.current_screen = screen
590
591=== added file 'tests/functional/openlp_core/widgets/test_widgets.py'
592--- tests/functional/openlp_core/widgets/test_widgets.py 1970-01-01 00:00:00 +0000
593+++ tests/functional/openlp_core/widgets/test_widgets.py 2018-11-10 08:28:19 +0000
594@@ -0,0 +1,199 @@
595+# -*- coding: utf-8 -*-
596+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
597+
598+###############################################################################
599+# OpenLP - Open Source Lyrics Projection #
600+# --------------------------------------------------------------------------- #
601+# Copyright (c) 2008-2018 OpenLP Developers #
602+# --------------------------------------------------------------------------- #
603+# This program is free software; you can redistribute it and/or modify it #
604+# under the terms of the GNU General Public License as published by the Free #
605+# Software Foundation; version 2 of the License. #
606+# #
607+# This program is distributed in the hope that it will be useful, but WITHOUT #
608+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
609+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
610+# more details. #
611+# #
612+# You should have received a copy of the GNU General Public License along #
613+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
614+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
615+###############################################################################
616+"""
617+Package to test the openlp.core.widgets.widgets package.
618+"""
619+from unittest import TestCase
620+from unittest.mock import MagicMock, patch
621+
622+from PyQt5 import QtCore, QtWidgets
623+
624+from openlp.core.display.screens import Screen
625+from openlp.core.widgets.widgets import ScreenButton, ScreenSelectionWidget
626+
627+
628+class TestSceenButton(TestCase):
629+ def test_screen_button_initialisation(self):
630+ """
631+ Test the initialisation of the ScreenButton object
632+ """
633+ # GIVEN: A mocked screen object
634+ screen_mock = MagicMock(spec=Screen)
635+ screen_mock.number = 0
636+ screen_mock.__str__.return_value = 'Mocked Screen Object'
637+
638+ # WHEN: initialising the ScreenButton object
639+ instance = ScreenButton(None, screen_mock)
640+
641+ # THEN: The ScreenButton should have been initalised correctly with the data from the mocked screen object
642+ assert isinstance(instance, QtWidgets.QPushButton)
643+ assert instance.objectName() == 'screen_0_button'
644+ assert instance.isCheckable() is True
645+ assert instance.text() == 'Mocked Screen Object'
646+
647+
648+class TestScreenSelectionWidget(TestCase):
649+ def setUp(self):
650+ patched_qtimer = patch('openlp.core.widgets.widgets.QtCore.QTimer')
651+ self.addCleanup(patched_qtimer.stop)
652+ self.timer_mock = MagicMock(spec=QtCore.QTimer)
653+ qtimer_mock = patched_qtimer.start()
654+ qtimer_mock.return_value = self.timer_mock
655+
656+ patched_screen_selection_widget_setup_ui = patch.object(ScreenSelectionWidget, 'setup_ui')
657+ self.addCleanup(patched_screen_selection_widget_setup_ui.stop)
658+ patched_screen_selection_widget_setup_ui.start()
659+
660+ def test_init_default_args(self):
661+ """
662+ Test the initialisation of ScreenSelectionWidget, when initialised with default arguments
663+ """
664+ # GIVEN: The ScreenSelectionWidget class
665+ # WHEN: Initialising ScreenSelectionWidget with default arguments
666+ instance = ScreenSelectionWidget()
667+
668+ # THEN: ScreenSelectionWidget should be an instance of QWidget and the screens attribute should be an empty list
669+ assert isinstance(instance, QtWidgets.QWidget)
670+ assert instance.screens == []
671+ self.timer_mock.setSingleShot.assert_called_once_with(True)
672+ self.timer_mock.setInterval.assert_called_once_with(3000)
673+
674+ def test_init_with_args(self):
675+ """
676+ Test the initialisation of ScreenSelectionWidget, when initialised with the screens keyword arg set
677+ """
678+ # GIVEN: The ScreenSelectionWidget class
679+ screens_object_mock = MagicMock()
680+
681+ # WHEN: Initialising ScreenSelectionWidget with the screens keyword arg set
682+ instance = ScreenSelectionWidget(screens=screens_object_mock)
683+
684+ # THEN: ScreenSelectionWidget should be an instance of QWidget and the screens attribute should the mock used
685+ assert isinstance(instance, QtWidgets.QWidget)
686+ assert instance.screens is screens_object_mock
687+ self.timer_mock.setSingleShot.assert_called_once_with(True)
688+ self.timer_mock.setInterval.assert_called_once_with(3000)
689+
690+ def test_save_screen_none(self):
691+ """
692+ Test ScreenSelectionWidget._save_screen when called with the screen arg set as None
693+ """
694+ # GIVEN: An instance of the ScreenSelectionWidget
695+ instance = ScreenSelectionWidget()
696+ instance.display_group_box = MagicMock(spec=QtWidgets.QGroupBox)
697+
698+ # WHEN: Calling _save_screen and no screen is selected
699+ instance._save_screen(None)
700+
701+ # THEN: _save_screen should return without attempting to write to the screen object
702+ instance.display_group_box.isChecked.assert_not_called()
703+
704+ def test_save_screen_not_display(self):
705+ """
706+ Test ScreenSelectionWidget._save_screen when the display_group_box is not checked.
707+ """
708+ # GIVEN: An instance of the ScreenSelectionWidget, and a mocked group_box
709+ instance = ScreenSelectionWidget()
710+ instance.display_group_box = MagicMock(spec=QtWidgets.QGroupBox)
711+ instance.custom_geometry_button = MagicMock(spec=QtWidgets.QRadioButton, **{'isChecked.return_value': False})
712+ mocked_screen_object = MagicMock(spec=Screen)
713+ mocked_screen_object.is_dislpay = True
714+
715+ # WHEN: display_group_box isn't checked and _save_screen is called with a mocked Screen object.
716+ instance.display_group_box.isChecked.return_value = False
717+ instance._save_screen(mocked_screen_object)
718+
719+ # THEN: _save_screen should should be set to False
720+ assert mocked_screen_object.is_display is False
721+
722+ def test_save_screen_display(self):
723+ """
724+ Test ScreenSelectionWidget._save_screen when the display_group_box is checked.
725+ """
726+ # GIVEN: An instance of the ScreenSelectionWidget, and a mocked group_box
727+ instance = ScreenSelectionWidget()
728+ instance.display_group_box = MagicMock(spec=QtWidgets.QGroupBox)
729+ instance.custom_geometry_button = MagicMock(spec=QtWidgets.QRadioButton, **{'isChecked.return_value': False})
730+ mocked_screen_object = MagicMock(spec=Screen)
731+
732+ # WHEN: display_group_box is checked and _save_screen is called with a mocked Screen object.
733+ instance.display_group_box.isChecked.return_value = True
734+ instance._save_screen(mocked_screen_object)
735+
736+ # THEN: _save_screen should should be set to True
737+ assert mocked_screen_object.is_display is True
738+
739+ @patch('openlp.core.widgets.widgets.QtCore.QRect')
740+ def test_save_screen_full_screen(self, mocked_q_rect):
741+ """
742+ Test ScreenSelectionWidget._save_screen when the display is set to full screen
743+ """
744+ # GIVEN: An instance of the ScreenSelectionWidget, and a mocked custom_geometry_button
745+ instance = ScreenSelectionWidget()
746+ instance.display_group_box = MagicMock(spec=QtWidgets.QGroupBox)
747+ instance.custom_geometry_button = MagicMock(spec=QtWidgets.QRadioButton)
748+ mocked_screen_object = MagicMock(spec=Screen)
749+
750+ # WHEN: custom_geometry_button isn't checked and _save_screen is called with a mocked Screen object.
751+ instance.custom_geometry_button.isChecked.return_value = False
752+ instance._save_screen(mocked_screen_object)
753+
754+ # THEN: _save_screen should not attempt to save a custom geometry
755+ mocked_q_rect.assert_not_called()
756+
757+ @patch('openlp.core.widgets.widgets.QtCore.QRect')
758+ def test_save_screen_custom_geometry(self, mocked_q_rect):
759+ """
760+ Test ScreenSelectionWidget._save_screen when a custom geometry is set
761+ """
762+ # GIVEN: An instance of the ScreenSelectionWidget, and a mocked custom_geometry_button
763+ instance = ScreenSelectionWidget()
764+ instance.display_group_box = MagicMock(spec=QtWidgets.QGroupBox)
765+ instance.custom_geometry_button = MagicMock(spec=QtWidgets.QRadioButton)
766+ instance.left_spin_box = MagicMock(spec=QtWidgets.QSpinBox, **{'value.return_value': 100})
767+ instance.top_spin_box = MagicMock(spec=QtWidgets.QSpinBox, **{'value.return_value': 200})
768+ instance.width_spin_box = MagicMock(spec=QtWidgets.QSpinBox, **{'value.return_value': 300})
769+ instance.height_spin_box = MagicMock(spec=QtWidgets.QSpinBox, **{'value.return_value': 400})
770+ mocked_screen_object = MagicMock(spec=Screen)
771+
772+ # WHEN: custom_geometry_button is checked and _save_screen is called with a mocked Screen object.
773+ instance.custom_geometry_button.isChecked.return_value = True
774+ instance._save_screen(mocked_screen_object)
775+
776+ # THEN: _save_screen should save the custom geometry
777+ mocked_q_rect.assert_called_once_with(100, 200, 300, 400)
778+
779+ def test_setup_spin_box(self):
780+ """
781+ Test that ScreenSelectionWidget._setup_spin_box sets up the given spinbox correctly
782+ """
783+ # GIVEN: An instance of the ScreenSelectionWidget class and a mocked spin box object
784+ instance = ScreenSelectionWidget()
785+ spin_box_mock = MagicMock(spec=QtWidgets.QSpinBox)
786+
787+ # WHEN: Calling _setup_spin_box with the mocked spin box object and some sample values
788+ instance._setup_spin_box(spin_box_mock, 0, 100, 50)
789+
790+ # THEN: The mocked spin box object should have been set up with the specified values
791+ spin_box_mock.setMinimum.assert_called_once_with(0)
792+ spin_box_mock.setMaximum.assert_called_once_with(100)
793+ spin_box_mock.setValue.assert_called_once_with(50)

Subscribers

People subscribed via source and target branches

to all changes: