forked from openlp/openlp
Merged changes from trunk
This commit is contained in:
commit
80a8abf4d0
@ -132,6 +132,7 @@ class Settings(QtCore.QSettings):
|
|||||||
'advanced/save current plugin': False,
|
'advanced/save current plugin': False,
|
||||||
'advanced/slide limits': SlideLimits.End,
|
'advanced/slide limits': SlideLimits.End,
|
||||||
'advanced/single click preview': False,
|
'advanced/single click preview': False,
|
||||||
|
'advanced/single click service preview': False,
|
||||||
'advanced/x11 bypass wm': X11_BYPASS_DEFAULT,
|
'advanced/x11 bypass wm': X11_BYPASS_DEFAULT,
|
||||||
'advanced/search as type': True,
|
'advanced/search as type': True,
|
||||||
'crashreport/last directory': '',
|
'crashreport/last directory': '',
|
||||||
|
@ -77,6 +77,9 @@ class AdvancedTab(SettingsTab):
|
|||||||
self.single_click_preview_check_box = QtWidgets.QCheckBox(self.ui_group_box)
|
self.single_click_preview_check_box = QtWidgets.QCheckBox(self.ui_group_box)
|
||||||
self.single_click_preview_check_box.setObjectName('single_click_preview_check_box')
|
self.single_click_preview_check_box.setObjectName('single_click_preview_check_box')
|
||||||
self.ui_layout.addRow(self.single_click_preview_check_box)
|
self.ui_layout.addRow(self.single_click_preview_check_box)
|
||||||
|
self.single_click_service_preview_check_box = QtWidgets.QCheckBox(self.ui_group_box)
|
||||||
|
self.single_click_service_preview_check_box.setObjectName('single_click_service_preview_check_box')
|
||||||
|
self.ui_layout.addRow(self.single_click_service_preview_check_box)
|
||||||
self.expand_service_item_check_box = QtWidgets.QCheckBox(self.ui_group_box)
|
self.expand_service_item_check_box = QtWidgets.QCheckBox(self.ui_group_box)
|
||||||
self.expand_service_item_check_box.setObjectName('expand_service_item_check_box')
|
self.expand_service_item_check_box.setObjectName('expand_service_item_check_box')
|
||||||
self.ui_layout.addRow(self.expand_service_item_check_box)
|
self.ui_layout.addRow(self.expand_service_item_check_box)
|
||||||
@ -277,6 +280,8 @@ class AdvancedTab(SettingsTab):
|
|||||||
'Double-click to send items straight to live'))
|
'Double-click to send items straight to live'))
|
||||||
self.single_click_preview_check_box.setText(translate('OpenLP.AdvancedTab',
|
self.single_click_preview_check_box.setText(translate('OpenLP.AdvancedTab',
|
||||||
'Preview items when clicked in Media Manager'))
|
'Preview items when clicked in Media Manager'))
|
||||||
|
self.single_click_service_preview_check_box.setText(translate('OpenLP.AdvancedTab',
|
||||||
|
'Preview items when clicked in Service Manager'))
|
||||||
self.expand_service_item_check_box.setText(translate('OpenLP.AdvancedTab',
|
self.expand_service_item_check_box.setText(translate('OpenLP.AdvancedTab',
|
||||||
'Expand new service items on creation'))
|
'Expand new service items on creation'))
|
||||||
self.slide_max_height_label.setText(translate('OpenLP.AdvancedTab',
|
self.slide_max_height_label.setText(translate('OpenLP.AdvancedTab',
|
||||||
@ -349,6 +354,7 @@ class AdvancedTab(SettingsTab):
|
|||||||
self.media_plugin_check_box.setChecked(settings.value('save current plugin'))
|
self.media_plugin_check_box.setChecked(settings.value('save current plugin'))
|
||||||
self.double_click_live_check_box.setChecked(settings.value('double click live'))
|
self.double_click_live_check_box.setChecked(settings.value('double click live'))
|
||||||
self.single_click_preview_check_box.setChecked(settings.value('single click preview'))
|
self.single_click_preview_check_box.setChecked(settings.value('single click preview'))
|
||||||
|
self.single_click_service_preview_check_box.setChecked(settings.value('single click service preview'))
|
||||||
self.expand_service_item_check_box.setChecked(settings.value('expand service item'))
|
self.expand_service_item_check_box.setChecked(settings.value('expand service item'))
|
||||||
self.slide_max_height_spin_box.setValue(settings.value('slide max height'))
|
self.slide_max_height_spin_box.setValue(settings.value('slide max height'))
|
||||||
self.enable_auto_close_check_box.setChecked(settings.value('enable exit confirmation'))
|
self.enable_auto_close_check_box.setChecked(settings.value('enable exit confirmation'))
|
||||||
@ -431,6 +437,7 @@ class AdvancedTab(SettingsTab):
|
|||||||
settings.setValue('save current plugin', self.media_plugin_check_box.isChecked())
|
settings.setValue('save current plugin', self.media_plugin_check_box.isChecked())
|
||||||
settings.setValue('double click live', self.double_click_live_check_box.isChecked())
|
settings.setValue('double click live', self.double_click_live_check_box.isChecked())
|
||||||
settings.setValue('single click preview', self.single_click_preview_check_box.isChecked())
|
settings.setValue('single click preview', self.single_click_preview_check_box.isChecked())
|
||||||
|
settings.setValue('single click service preview', self.single_click_service_preview_check_box.isChecked())
|
||||||
settings.setValue('expand service item', self.expand_service_item_check_box.isChecked())
|
settings.setValue('expand service item', self.expand_service_item_check_box.isChecked())
|
||||||
settings.setValue('slide max height', self.slide_max_height_spin_box.value())
|
settings.setValue('slide max height', self.slide_max_height_spin_box.value())
|
||||||
settings.setValue('enable exit confirmation', self.enable_auto_close_check_box.isChecked())
|
settings.setValue('enable exit confirmation', self.enable_auto_close_check_box.isChecked())
|
||||||
|
@ -180,11 +180,13 @@ class ExceptionForm(QtWidgets.QDialog, Ui_ExceptionDialog, RegistryProperties):
|
|||||||
if ':' in line:
|
if ':' in line:
|
||||||
exception = line.split('\n')[-1].split(':')[0]
|
exception = line.split('\n')[-1].split(':')[0]
|
||||||
subject = 'Bug report: %s in %s' % (exception, source)
|
subject = 'Bug report: %s in %s' % (exception, source)
|
||||||
mail_to_url = QtCore.QUrlQuery('mailto:bugs@openlp.org')
|
mail_urlquery = QtCore.QUrlQuery()
|
||||||
mail_to_url.addQueryItem('subject', subject)
|
mail_urlquery.addQueryItem('subject', subject)
|
||||||
mail_to_url.addQueryItem('body', self.report_text % content)
|
mail_urlquery.addQueryItem('body', self.report_text % content)
|
||||||
if self.file_attachment:
|
if self.file_attachment:
|
||||||
mail_to_url.addQueryItem('attach', self.file_attachment)
|
mail_urlquery.addQueryItem('attach', self.file_attachment)
|
||||||
|
mail_to_url = QtCore.QUrl('mailto:bugs@openlp.org')
|
||||||
|
mail_to_url.setQuery(mail_urlquery)
|
||||||
QtGui.QDesktopServices.openUrl(mail_to_url)
|
QtGui.QDesktopServices.openUrl(mail_to_url)
|
||||||
|
|
||||||
def on_description_updated(self):
|
def on_description_updated(self):
|
||||||
|
@ -408,10 +408,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
|||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
# Workaround for bug #1531319, should not be needed with PyQt 5.6.
|
# Workaround for bug #1531319, should not be needed with PyQt 5.6.
|
||||||
if is_win():
|
if is_win():
|
||||||
# Workaround for bug #1531319, should not be needed with PyQt 5.6.
|
|
||||||
fade_shake_timer.stop()
|
fade_shake_timer.stop()
|
||||||
elif is_win():
|
|
||||||
self.shake_web_view()
|
|
||||||
# Wait for the webview to update before getting the preview.
|
# Wait for the webview to update before getting the preview.
|
||||||
# Important otherwise first preview will miss the background !
|
# Important otherwise first preview will miss the background !
|
||||||
while not self.web_loaded:
|
while not self.web_loaded:
|
||||||
@ -429,6 +426,9 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
|||||||
self.setVisible(True)
|
self.setVisible(True)
|
||||||
else:
|
else:
|
||||||
self.setVisible(True)
|
self.setVisible(True)
|
||||||
|
# Workaround for bug #1531319, should not be needed with PyQt 5.6.
|
||||||
|
if is_win():
|
||||||
|
self.shake_web_view()
|
||||||
return self.grab()
|
return self.grab()
|
||||||
|
|
||||||
def build_html(self, service_item, image_path=''):
|
def build_html(self, service_item, image_path=''):
|
||||||
|
@ -83,60 +83,60 @@ class Ui_ProjectorManager(object):
|
|||||||
self.one_toolbar.add_toolbar_action('new_projector',
|
self.one_toolbar.add_toolbar_action('new_projector',
|
||||||
text=translate('OpenLP.ProjectorManager', 'Add Projector'),
|
text=translate('OpenLP.ProjectorManager', 'Add Projector'),
|
||||||
icon=':/projector/projector_new.png',
|
icon=':/projector/projector_new.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager', 'Add a new projector'),
|
tooltip=translate('OpenLP.ProjectorManager', 'Add a new projector.'),
|
||||||
triggers=self.on_add_projector)
|
triggers=self.on_add_projector)
|
||||||
# Show edit/delete when projector not connected
|
# Show edit/delete when projector not connected
|
||||||
self.one_toolbar.add_toolbar_action('edit_projector',
|
self.one_toolbar.add_toolbar_action('edit_projector',
|
||||||
text=translate('OpenLP.ProjectorManager', 'Edit Projector'),
|
text=translate('OpenLP.ProjectorManager', 'Edit Projector'),
|
||||||
icon=':/general/general_edit.png',
|
icon=':/general/general_edit.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager', 'Edit selected projector'),
|
tooltip=translate('OpenLP.ProjectorManager', 'Edit selected projector.'),
|
||||||
triggers=self.on_edit_projector)
|
triggers=self.on_edit_projector)
|
||||||
self.one_toolbar.add_toolbar_action('delete_projector',
|
self.one_toolbar.add_toolbar_action('delete_projector',
|
||||||
text=translate('OpenLP.ProjectorManager', 'Delete Projector'),
|
text=translate('OpenLP.ProjectorManager', 'Delete Projector'),
|
||||||
icon=':/general/general_delete.png',
|
icon=':/general/general_delete.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager', 'Delete selected projector'),
|
tooltip=translate('OpenLP.ProjectorManager', 'Delete selected projector.'),
|
||||||
triggers=self.on_delete_projector)
|
triggers=self.on_delete_projector)
|
||||||
# Show source/view when projector connected
|
# Show source/view when projector connected
|
||||||
self.one_toolbar.add_toolbar_action('source_view_projector',
|
self.one_toolbar.add_toolbar_action('source_view_projector',
|
||||||
text=translate('OpenLP.ProjectorManager', 'Select Input Source'),
|
text=translate('OpenLP.ProjectorManager', 'Select Input Source'),
|
||||||
icon=':/projector/projector_hdmi.png',
|
icon=':/projector/projector_hdmi.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Choose input source on selected projector'),
|
'Choose input source on selected projector.'),
|
||||||
triggers=self.on_select_input)
|
triggers=self.on_select_input)
|
||||||
self.one_toolbar.add_toolbar_action('view_projector',
|
self.one_toolbar.add_toolbar_action('view_projector',
|
||||||
text=translate('OpenLP.ProjectorManager', 'View Projector'),
|
text=translate('OpenLP.ProjectorManager', 'View Projector'),
|
||||||
icon=':/system/system_about.png',
|
icon=':/system/system_about.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'View selected projector information'),
|
'View selected projector information.'),
|
||||||
triggers=self.on_status_projector)
|
triggers=self.on_status_projector)
|
||||||
self.one_toolbar.addSeparator()
|
self.one_toolbar.addSeparator()
|
||||||
self.one_toolbar.add_toolbar_action('connect_projector',
|
self.one_toolbar.add_toolbar_action('connect_projector',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Connect to selected projector'),
|
'Connect to selected projector.'),
|
||||||
icon=':/projector/projector_connect.png',
|
icon=':/projector/projector_connect.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Connect to selected projector'),
|
'Connect to selected projector.'),
|
||||||
triggers=self.on_connect_projector)
|
triggers=self.on_connect_projector)
|
||||||
self.one_toolbar.add_toolbar_action('connect_projector_multiple',
|
self.one_toolbar.add_toolbar_action('connect_projector_multiple',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Connect to selected projectors'),
|
'Connect to selected projectors'),
|
||||||
icon=':/projector/projector_connect_tiled.png',
|
icon=':/projector/projector_connect_tiled.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Connect to selected projector'),
|
'Connect to selected projectors.'),
|
||||||
triggers=self.on_connect_projector)
|
triggers=self.on_connect_projector)
|
||||||
self.one_toolbar.add_toolbar_action('disconnect_projector',
|
self.one_toolbar.add_toolbar_action('disconnect_projector',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Disconnect from selected projectors'),
|
'Disconnect from selected projectors'),
|
||||||
icon=':/projector/projector_disconnect.png',
|
icon=':/projector/projector_disconnect.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Disconnect from selected projector'),
|
'Disconnect from selected projector.'),
|
||||||
triggers=self.on_disconnect_projector)
|
triggers=self.on_disconnect_projector)
|
||||||
self.one_toolbar.add_toolbar_action('disconnect_projector_multiple',
|
self.one_toolbar.add_toolbar_action('disconnect_projector_multiple',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Disconnect from selected projector'),
|
'Disconnect from selected projector'),
|
||||||
icon=':/projector/projector_disconnect_tiled.png',
|
icon=':/projector/projector_disconnect_tiled.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Disconnect from selected projector'),
|
'Disconnect from selected projectors.'),
|
||||||
triggers=self.on_disconnect_projector)
|
triggers=self.on_disconnect_projector)
|
||||||
self.one_toolbar.addSeparator()
|
self.one_toolbar.addSeparator()
|
||||||
self.one_toolbar.add_toolbar_action('poweron_projector',
|
self.one_toolbar.add_toolbar_action('poweron_projector',
|
||||||
@ -144,26 +144,26 @@ class Ui_ProjectorManager(object):
|
|||||||
'Power on selected projector'),
|
'Power on selected projector'),
|
||||||
icon=':/projector/projector_power_on.png',
|
icon=':/projector/projector_power_on.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Power on selected projector'),
|
'Power on selected projector.'),
|
||||||
triggers=self.on_poweron_projector)
|
triggers=self.on_poweron_projector)
|
||||||
self.one_toolbar.add_toolbar_action('poweron_projector_multiple',
|
self.one_toolbar.add_toolbar_action('poweron_projector_multiple',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Power on selected projector'),
|
'Power on selected projector'),
|
||||||
icon=':/projector/projector_power_on_tiled.png',
|
icon=':/projector/projector_power_on_tiled.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Power on selected projector'),
|
'Power on selected projectors.'),
|
||||||
triggers=self.on_poweron_projector)
|
triggers=self.on_poweron_projector)
|
||||||
self.one_toolbar.add_toolbar_action('poweroff_projector',
|
self.one_toolbar.add_toolbar_action('poweroff_projector',
|
||||||
text=translate('OpenLP.ProjectorManager', 'Standby selected projector'),
|
text=translate('OpenLP.ProjectorManager', 'Standby selected projector'),
|
||||||
icon=':/projector/projector_power_off.png',
|
icon=':/projector/projector_power_off.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Put selected projector in standby'),
|
'Put selected projector in standby.'),
|
||||||
triggers=self.on_poweroff_projector)
|
triggers=self.on_poweroff_projector)
|
||||||
self.one_toolbar.add_toolbar_action('poweroff_projector_multiple',
|
self.one_toolbar.add_toolbar_action('poweroff_projector_multiple',
|
||||||
text=translate('OpenLP.ProjectorManager', 'Standby selected projector'),
|
text=translate('OpenLP.ProjectorManager', 'Standby selected projector'),
|
||||||
icon=':/projector/projector_power_off_tiled.png',
|
icon=':/projector/projector_power_off_tiled.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Put selected projector in standby'),
|
'Put selected projectors in standby.'),
|
||||||
triggers=self.on_poweroff_projector)
|
triggers=self.on_poweroff_projector)
|
||||||
self.one_toolbar.addSeparator()
|
self.one_toolbar.addSeparator()
|
||||||
self.one_toolbar.add_toolbar_action('blank_projector',
|
self.one_toolbar.add_toolbar_action('blank_projector',
|
||||||
@ -175,24 +175,24 @@ class Ui_ProjectorManager(object):
|
|||||||
triggers=self.on_blank_projector)
|
triggers=self.on_blank_projector)
|
||||||
self.one_toolbar.add_toolbar_action('blank_projector_multiple',
|
self.one_toolbar.add_toolbar_action('blank_projector_multiple',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Blank selected projector screen'),
|
'Blank selected projectors screen'),
|
||||||
icon=':/projector/projector_blank_tiled.png',
|
icon=':/projector/projector_blank_tiled.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Blank selected projector screen'),
|
'Blank selected projectors screen.'),
|
||||||
triggers=self.on_blank_projector)
|
triggers=self.on_blank_projector)
|
||||||
self.one_toolbar.add_toolbar_action('show_projector',
|
self.one_toolbar.add_toolbar_action('show_projector',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Show selected projector screen'),
|
'Show selected projector screen'),
|
||||||
icon=':/projector/projector_show.png',
|
icon=':/projector/projector_show.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Show selected projector screen'),
|
'Show selected projector screen.'),
|
||||||
triggers=self.on_show_projector)
|
triggers=self.on_show_projector)
|
||||||
self.one_toolbar.add_toolbar_action('show_projector_multiple',
|
self.one_toolbar.add_toolbar_action('show_projector_multiple',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Show selected projector screen'),
|
'Show selected projector screen'),
|
||||||
icon=':/projector/projector_show_tiled.png',
|
icon=':/projector/projector_show_tiled.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Show selected projector screen'),
|
'Show selected projectors screen.'),
|
||||||
triggers=self.on_show_projector)
|
triggers=self.on_show_projector)
|
||||||
self.layout.addWidget(self.one_toolbar)
|
self.layout.addWidget(self.one_toolbar)
|
||||||
self.projector_one_widget = QtWidgets.QWidgetAction(self.one_toolbar)
|
self.projector_one_widget = QtWidgets.QWidgetAction(self.one_toolbar)
|
||||||
|
@ -211,7 +211,8 @@ class Ui_ServiceManager(object):
|
|||||||
self.layout.addWidget(self.order_toolbar)
|
self.layout.addWidget(self.order_toolbar)
|
||||||
# Connect up our signals and slots
|
# Connect up our signals and slots
|
||||||
self.theme_combo_box.activated.connect(self.on_theme_combo_box_selected)
|
self.theme_combo_box.activated.connect(self.on_theme_combo_box_selected)
|
||||||
self.service_manager_list.doubleClicked.connect(self.on_make_live)
|
self.service_manager_list.doubleClicked.connect(self.on_double_click_live)
|
||||||
|
self.service_manager_list.clicked.connect(self.on_single_click_preview)
|
||||||
self.service_manager_list.itemCollapsed.connect(self.collapsed)
|
self.service_manager_list.itemCollapsed.connect(self.collapsed)
|
||||||
self.service_manager_list.itemExpanded.connect(self.expanded)
|
self.service_manager_list.itemExpanded.connect(self.expanded)
|
||||||
# Last little bits of setting up
|
# Last little bits of setting up
|
||||||
@ -319,6 +320,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
self._modified = False
|
self._modified = False
|
||||||
self._file_name = ''
|
self._file_name = ''
|
||||||
self.service_has_all_original_files = True
|
self.service_has_all_original_files = True
|
||||||
|
self.list_double_clicked = False
|
||||||
|
|
||||||
def bootstrap_initialise(self):
|
def bootstrap_initialise(self):
|
||||||
"""
|
"""
|
||||||
@ -1454,13 +1456,38 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
else:
|
else:
|
||||||
return self.service_items[item]['service_item']
|
return self.service_items[item]['service_item']
|
||||||
|
|
||||||
def on_make_live(self, field=None):
|
def on_double_click_live(self, field=None):
|
||||||
"""
|
"""
|
||||||
Send the current item to the Live slide controller but triggered by a tablewidget click event.
|
Send the current item to the Live slide controller but triggered by a tablewidget click event.
|
||||||
:param field:
|
:param field:
|
||||||
"""
|
"""
|
||||||
|
self.list_double_clicked = True
|
||||||
self.make_live()
|
self.make_live()
|
||||||
|
|
||||||
|
def on_single_click_preview(self, field=None):
|
||||||
|
"""
|
||||||
|
If single click previewing is enabled, and triggered by a tablewidget click event,
|
||||||
|
start a timeout to verify a double-click hasn't triggered.
|
||||||
|
:param field:
|
||||||
|
"""
|
||||||
|
if Settings().value('advanced/single click service preview'):
|
||||||
|
if not self.list_double_clicked:
|
||||||
|
# If a double click has not registered start a timer, otherwise wait for the existing timer to finish.
|
||||||
|
QtCore.QTimer.singleShot(QtWidgets.QApplication.instance().doubleClickInterval(),
|
||||||
|
self.on_single_click_preview_timeout)
|
||||||
|
|
||||||
|
def on_single_click_preview_timeout(self):
|
||||||
|
"""
|
||||||
|
If a single click ok, but double click not triggered, send the current item to the Preview slide controller.
|
||||||
|
:param field:
|
||||||
|
"""
|
||||||
|
if self.list_double_clicked:
|
||||||
|
# If a double click has registered, clear it.
|
||||||
|
self.list_double_clicked = False
|
||||||
|
else:
|
||||||
|
# Otherwise preview the item.
|
||||||
|
self.make_preview()
|
||||||
|
|
||||||
def make_live(self, row=-1):
|
def make_live(self, row=-1):
|
||||||
"""
|
"""
|
||||||
Send the current item to the Live slide controller
|
Send the current item to the Live slide controller
|
||||||
|
@ -198,6 +198,7 @@ class EditCustomForm(QtWidgets.QDialog, Ui_CustomEditDialog):
|
|||||||
# Insert all slides to make the old_slides list complete.
|
# Insert all slides to make the old_slides list complete.
|
||||||
for slide in slides:
|
for slide in slides:
|
||||||
old_slides.insert(old_row, slide)
|
old_slides.insert(old_row, slide)
|
||||||
|
old_row += 1
|
||||||
self.slide_list_view.addItems(old_slides)
|
self.slide_list_view.addItems(old_slides)
|
||||||
self.slide_list_view.repaint()
|
self.slide_list_view.repaint()
|
||||||
|
|
||||||
|
@ -289,40 +289,45 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
for i in range(rec_count):
|
for i in range(rec_count):
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
break
|
break
|
||||||
raw_record = db_file.read(record_size)
|
try:
|
||||||
self.fields = self.record_structure.unpack(raw_record)
|
raw_record = db_file.read(record_size)
|
||||||
self.set_defaults()
|
self.fields = self.record_structure.unpack(raw_record)
|
||||||
self.title = self.get_field(fi_title).decode(self.encoding)
|
self.set_defaults()
|
||||||
# Get remaining fields.
|
self.title = self.get_field(fi_title).decode(self.encoding)
|
||||||
copy = self.get_field(fi_copy)
|
# Get remaining fields.
|
||||||
admin = self.get_field(fi_admin)
|
copy = self.get_field(fi_copy)
|
||||||
ccli = self.get_field(fi_ccli)
|
admin = self.get_field(fi_admin)
|
||||||
authors = self.get_field(fi_author)
|
ccli = self.get_field(fi_ccli)
|
||||||
words = self.get_field(fi_words)
|
authors = self.get_field(fi_author)
|
||||||
if copy:
|
words = self.get_field(fi_words)
|
||||||
self.copyright = copy.decode(self.encoding)
|
|
||||||
if admin:
|
|
||||||
if copy:
|
if copy:
|
||||||
self.copyright += ', '
|
self.copyright = copy.decode(self.encoding)
|
||||||
self.copyright += translate('SongsPlugin.EasyWorshipSongImport',
|
if admin:
|
||||||
'Administered by %s') % admin.decode(self.encoding)
|
if copy:
|
||||||
if ccli:
|
self.copyright += ', '
|
||||||
self.ccli_number = ccli.decode(self.encoding)
|
self.copyright += translate('SongsPlugin.EasyWorshipSongImport',
|
||||||
if authors:
|
'Administered by %s') % admin.decode(self.encoding)
|
||||||
authors = authors.decode(self.encoding)
|
if ccli:
|
||||||
else:
|
self.ccli_number = ccli.decode(self.encoding)
|
||||||
authors = ''
|
if authors:
|
||||||
# Set the SongImport object members.
|
authors = authors.decode(self.encoding)
|
||||||
self.set_song_import_object(authors, words)
|
else:
|
||||||
if self.stop_import_flag:
|
authors = ''
|
||||||
break
|
# Set the SongImport object members.
|
||||||
if self.entry_error_log:
|
self.set_song_import_object(authors, words)
|
||||||
|
if self.stop_import_flag:
|
||||||
|
break
|
||||||
|
if self.entry_error_log:
|
||||||
|
self.log_error(self.import_source,
|
||||||
|
translate('SongsPlugin.EasyWorshipSongImport', '"%s" could not be imported. %s')
|
||||||
|
% (self.title, self.entry_error_log))
|
||||||
|
self.entry_error_log = ''
|
||||||
|
elif not self.finish():
|
||||||
|
self.log_error(self.import_source)
|
||||||
|
except Exception as e:
|
||||||
self.log_error(self.import_source,
|
self.log_error(self.import_source,
|
||||||
translate('SongsPlugin.EasyWorshipSongImport', '"%s" could not be imported. %s')
|
translate('SongsPlugin.EasyWorshipSongImport', '"%s" could not be imported. %s')
|
||||||
% (self.title, self.entry_error_log))
|
% (self.title, e))
|
||||||
self.entry_error_log = ''
|
|
||||||
elif not self.finish():
|
|
||||||
self.log_error(self.import_source)
|
|
||||||
db_file.close()
|
db_file.close()
|
||||||
self.memo_file.close()
|
self.memo_file.close()
|
||||||
|
|
||||||
@ -368,7 +373,7 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
first_line_is_tag = False
|
first_line_is_tag = False
|
||||||
# EW tags: verse, chorus, pre-chorus, bridge, tag,
|
# EW tags: verse, chorus, pre-chorus, bridge, tag,
|
||||||
# intro, ending, slide
|
# intro, ending, slide
|
||||||
for tag in VerseType.tags + ['tag', 'slide']:
|
for tag in VerseType.names + ['tag', 'slide', 'end']:
|
||||||
tag = tag.lower()
|
tag = tag.lower()
|
||||||
ew_tag = verse_split[0].strip().lower()
|
ew_tag = verse_split[0].strip().lower()
|
||||||
if ew_tag.startswith(tag):
|
if ew_tag.startswith(tag):
|
||||||
@ -390,6 +395,9 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
if not number_found:
|
if not number_found:
|
||||||
verse_type += '1'
|
verse_type += '1'
|
||||||
break
|
break
|
||||||
|
# If the verse only consist of the tag-line, add an empty line to create an empty slide
|
||||||
|
if first_line_is_tag and len(verse_split) == 1:
|
||||||
|
verse_split.append("")
|
||||||
self.add_verse(verse_split[-1].strip() if first_line_is_tag else verse, verse_type)
|
self.add_verse(verse_split[-1].strip() if first_line_is_tag else verse, verse_type)
|
||||||
if len(self.comments) > 5:
|
if len(self.comments) > 5:
|
||||||
self.comments += str(translate('SongsPlugin.EasyWorshipSongImport',
|
self.comments += str(translate('SongsPlugin.EasyWorshipSongImport',
|
||||||
|
@ -26,7 +26,7 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
from sqlalchemy.sql import or_
|
from sqlalchemy.sql import and_, or_
|
||||||
|
|
||||||
from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, UiStrings, translate
|
from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, UiStrings, translate
|
||||||
from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItemContext, \
|
from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItemContext, \
|
||||||
@ -37,7 +37,7 @@ from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
|
|||||||
from openlp.plugins.songs.forms.songimportform import SongImportForm
|
from openlp.plugins.songs.forms.songimportform import SongImportForm
|
||||||
from openlp.plugins.songs.forms.songexportform import SongExportForm
|
from openlp.plugins.songs.forms.songexportform import SongExportForm
|
||||||
from openlp.plugins.songs.lib import VerseType, clean_string, delete_song
|
from openlp.plugins.songs.lib import VerseType, clean_string, delete_song
|
||||||
from openlp.plugins.songs.lib.db import Author, AuthorType, Song, Book, MediaFile, SongBookEntry
|
from openlp.plugins.songs.lib.db import Author, AuthorType, Song, Book, MediaFile, SongBookEntry, Topic
|
||||||
from openlp.plugins.songs.lib.ui import SongStrings
|
from openlp.plugins.songs.lib.ui import SongStrings
|
||||||
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, SongXML
|
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, SongXML
|
||||||
|
|
||||||
@ -52,8 +52,11 @@ class SongSearch(object):
|
|||||||
Titles = 2
|
Titles = 2
|
||||||
Lyrics = 3
|
Lyrics = 3
|
||||||
Authors = 4
|
Authors = 4
|
||||||
Books = 5
|
Topics = 5
|
||||||
Themes = 6
|
Books = 6
|
||||||
|
Themes = 7
|
||||||
|
Copyright = 8
|
||||||
|
CCLInumber = 9
|
||||||
|
|
||||||
|
|
||||||
class SongMediaItem(MediaManagerItem):
|
class SongMediaItem(MediaManagerItem):
|
||||||
@ -151,9 +154,17 @@ class SongMediaItem(MediaManagerItem):
|
|||||||
translate('SongsPlugin.MediaItem', 'Search Lyrics...')),
|
translate('SongsPlugin.MediaItem', 'Search Lyrics...')),
|
||||||
(SongSearch.Authors, ':/songs/song_search_author.png', SongStrings.Authors,
|
(SongSearch.Authors, ':/songs/song_search_author.png', SongStrings.Authors,
|
||||||
translate('SongsPlugin.MediaItem', 'Search Authors...')),
|
translate('SongsPlugin.MediaItem', 'Search Authors...')),
|
||||||
|
(SongSearch.Topics, ':/songs/song_search_topic.png', SongStrings.Topics,
|
||||||
|
translate('SongsPlugin.MediaItem', 'Search Topics...')),
|
||||||
(SongSearch.Books, ':/songs/song_book_edit.png', SongStrings.SongBooks,
|
(SongSearch.Books, ':/songs/song_book_edit.png', SongStrings.SongBooks,
|
||||||
translate('SongsPlugin.MediaItem', 'Search Songbooks...')),
|
translate('SongsPlugin.MediaItem', 'Search Songbooks...')),
|
||||||
(SongSearch.Themes, ':/slides/slide_theme.png', UiStrings().Themes, UiStrings().SearchThemes)
|
(SongSearch.Themes, ':/slides/slide_theme.png', UiStrings().Themes, UiStrings().SearchThemes),
|
||||||
|
(SongSearch.Copyright, ':/songs/song_search_copy.png',
|
||||||
|
translate('SongsPlugin.MediaItem', 'Copyright'),
|
||||||
|
translate('SongsPlugin.MediaItem', 'Search Copyright...')),
|
||||||
|
(SongSearch.CCLInumber, ':/songs/song_search_ccli.png',
|
||||||
|
translate('SongsPlugin.MediaItem', 'CCLI number'),
|
||||||
|
translate('SongsPlugin.MediaItem', 'Search CCLI number...'))
|
||||||
])
|
])
|
||||||
self.search_text_edit.set_current_search_type(Settings().value('%s/last search type' % self.settings_section))
|
self.search_text_edit.set_current_search_type(Settings().value('%s/last search type' % self.settings_section))
|
||||||
self.config_update()
|
self.config_update()
|
||||||
@ -184,14 +195,33 @@ class SongMediaItem(MediaManagerItem):
|
|||||||
search_results = self.plugin.manager.get_all_objects(
|
search_results = self.plugin.manager.get_all_objects(
|
||||||
Author, Author.display_name.like(search_string), Author.display_name.asc())
|
Author, Author.display_name.like(search_string), Author.display_name.asc())
|
||||||
self.display_results_author(search_results)
|
self.display_results_author(search_results)
|
||||||
|
elif search_type == SongSearch.Topics:
|
||||||
|
log.debug('Topics Search')
|
||||||
|
search_string = '%' + search_keywords + '%'
|
||||||
|
search_results = self.plugin.manager.get_all_objects(
|
||||||
|
Topic, Topic.name.like(search_string), Topic.name.asc())
|
||||||
|
self.display_results_topic(search_results)
|
||||||
elif search_type == SongSearch.Books:
|
elif search_type == SongSearch.Books:
|
||||||
log.debug('Songbook Search')
|
log.debug('Songbook Search')
|
||||||
self.display_results_book(search_keywords)
|
self.display_results_book(search_keywords)
|
||||||
elif search_type == SongSearch.Themes:
|
elif search_type == SongSearch.Themes:
|
||||||
log.debug('Theme Search')
|
log.debug('Theme Search')
|
||||||
search_string = '%' + search_keywords + '%'
|
search_string = '%' + search_keywords + '%'
|
||||||
search_results = self.plugin.manager.get_all_objects(Song, Song.theme_name.like(search_string))
|
search_results = self.plugin.manager.get_all_objects(
|
||||||
|
Song, Song.theme_name.like(search_string), Song.theme_name.asc())
|
||||||
|
self.display_results_themes(search_results)
|
||||||
|
elif search_type == SongSearch.Copyright:
|
||||||
|
log.debug('Copyright Search')
|
||||||
|
search_string = '%' + search_keywords + '%'
|
||||||
|
search_results = self.plugin.manager.get_all_objects(
|
||||||
|
Song, and_(Song.copyright.like(search_string), Song.copyright != ''))
|
||||||
self.display_results_song(search_results)
|
self.display_results_song(search_results)
|
||||||
|
elif search_type == SongSearch.CCLInumber:
|
||||||
|
log.debug('CCLI number Search')
|
||||||
|
search_string = '%' + search_keywords + '%'
|
||||||
|
search_results = self.plugin.manager.get_all_objects(
|
||||||
|
Song, and_(Song.ccli_number.like(search_string), Song.ccli_number != ''))
|
||||||
|
self.display_results_cclinumber(search_results)
|
||||||
self.check_search_result()
|
self.check_search_result()
|
||||||
|
|
||||||
def search_entire(self, search_keywords):
|
def search_entire(self, search_keywords):
|
||||||
@ -215,6 +245,12 @@ class SongMediaItem(MediaManagerItem):
|
|||||||
log.debug('on_song_list_load - finished')
|
log.debug('on_song_list_load - finished')
|
||||||
|
|
||||||
def display_results_song(self, search_results):
|
def display_results_song(self, search_results):
|
||||||
|
"""
|
||||||
|
Display the song search results in the media manager list
|
||||||
|
|
||||||
|
:param search_results: A list of db Song objects
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
log.debug('display results Song')
|
log.debug('display results Song')
|
||||||
self.save_auto_select_id()
|
self.save_auto_select_id()
|
||||||
self.list_view.clear()
|
self.list_view.clear()
|
||||||
@ -234,6 +270,12 @@ class SongMediaItem(MediaManagerItem):
|
|||||||
self.auto_select_id = -1
|
self.auto_select_id = -1
|
||||||
|
|
||||||
def display_results_author(self, search_results):
|
def display_results_author(self, search_results):
|
||||||
|
"""
|
||||||
|
Display the song search results in the media manager list, grouped by author
|
||||||
|
|
||||||
|
:param search_results: A list of db Author objects
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
log.debug('display results Author')
|
log.debug('display results Author')
|
||||||
self.list_view.clear()
|
self.list_view.clear()
|
||||||
for author in search_results:
|
for author in search_results:
|
||||||
@ -247,6 +289,13 @@ class SongMediaItem(MediaManagerItem):
|
|||||||
self.list_view.addItem(song_name)
|
self.list_view.addItem(song_name)
|
||||||
|
|
||||||
def display_results_book(self, search_keywords):
|
def display_results_book(self, search_keywords):
|
||||||
|
"""
|
||||||
|
Display the song search results in the media manager list, grouped by book
|
||||||
|
|
||||||
|
:param search_keywords: A list of search keywords - book first, then number
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
|
||||||
log.debug('display results Book')
|
log.debug('display results Book')
|
||||||
self.list_view.clear()
|
self.list_view.clear()
|
||||||
|
|
||||||
@ -270,6 +319,64 @@ class SongMediaItem(MediaManagerItem):
|
|||||||
song_name.setData(QtCore.Qt.UserRole, songbook_entry.song.id)
|
song_name.setData(QtCore.Qt.UserRole, songbook_entry.song.id)
|
||||||
self.list_view.addItem(song_name)
|
self.list_view.addItem(song_name)
|
||||||
|
|
||||||
|
def display_results_topic(self, search_results):
|
||||||
|
"""
|
||||||
|
Display the song search results in the media manager list, grouped by topic
|
||||||
|
|
||||||
|
:param search_results: A list of db Topic objects
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
log.debug('display results Topic')
|
||||||
|
self.list_view.clear()
|
||||||
|
search_results = sorted(search_results, key=lambda topic: self._natural_sort_key(topic.name))
|
||||||
|
for topic in search_results:
|
||||||
|
songs = sorted(topic.songs, key=lambda song: song.sort_key)
|
||||||
|
for song in songs:
|
||||||
|
# Do not display temporary songs
|
||||||
|
if song.temporary:
|
||||||
|
continue
|
||||||
|
song_detail = '%s (%s)' % (topic.name, song.title)
|
||||||
|
song_name = QtWidgets.QListWidgetItem(song_detail)
|
||||||
|
song_name.setData(QtCore.Qt.UserRole, song.id)
|
||||||
|
self.list_view.addItem(song_name)
|
||||||
|
|
||||||
|
def display_results_themes(self, search_results):
|
||||||
|
"""
|
||||||
|
Display the song search results in the media manager list, sorted by theme
|
||||||
|
|
||||||
|
:param search_results: A list of db Song objects
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
log.debug('display results Themes')
|
||||||
|
self.list_view.clear()
|
||||||
|
for song in search_results:
|
||||||
|
# Do not display temporary songs
|
||||||
|
if song.temporary:
|
||||||
|
continue
|
||||||
|
song_detail = '%s (%s)' % (song.theme_name, song.title)
|
||||||
|
song_name = QtWidgets.QListWidgetItem(song_detail)
|
||||||
|
song_name.setData(QtCore.Qt.UserRole, song.id)
|
||||||
|
self.list_view.addItem(song_name)
|
||||||
|
|
||||||
|
def display_results_cclinumber(self, search_results):
|
||||||
|
"""
|
||||||
|
Display the song search results in the media manager list, sorted by CCLI number
|
||||||
|
|
||||||
|
:param search_results: A list of db Song objects
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
log.debug('display results CCLI number')
|
||||||
|
self.list_view.clear()
|
||||||
|
songs = sorted(search_results, key=lambda song: self._natural_sort_key(song.ccli_number))
|
||||||
|
for song in songs:
|
||||||
|
# Do not display temporary songs
|
||||||
|
if song.temporary:
|
||||||
|
continue
|
||||||
|
song_detail = '%s (%s)' % (song.ccli_number, song.title)
|
||||||
|
song_name = QtWidgets.QListWidgetItem(song_detail)
|
||||||
|
song_name.setData(QtCore.Qt.UserRole, song.id)
|
||||||
|
self.list_view.addItem(song_name)
|
||||||
|
|
||||||
def on_clear_text_button_click(self):
|
def on_clear_text_button_click(self):
|
||||||
"""
|
"""
|
||||||
Clear the search text.
|
Clear the search text.
|
||||||
@ -587,6 +694,14 @@ class SongMediaItem(MediaManagerItem):
|
|||||||
# List must be empty at the end
|
# List must be empty at the end
|
||||||
return not author_list
|
return not author_list
|
||||||
|
|
||||||
|
def _natural_sort_key(self, s):
|
||||||
|
"""
|
||||||
|
Return a tuple by which s is sorted.
|
||||||
|
:param s: A string value from the list we want to sort.
|
||||||
|
"""
|
||||||
|
return [int(text) if text.isdecimal() else text.lower()
|
||||||
|
for text in re.split('(\d+)', s)]
|
||||||
|
|
||||||
def search(self, string, show_error):
|
def search(self, string, show_error):
|
||||||
"""
|
"""
|
||||||
Search for some songs
|
Search for some songs
|
||||||
|
@ -3,8 +3,11 @@
|
|||||||
<file>song_search_stop.png</file>
|
<file>song_search_stop.png</file>
|
||||||
<file>song_search_all.png</file>
|
<file>song_search_all.png</file>
|
||||||
<file>song_search_author.png</file>
|
<file>song_search_author.png</file>
|
||||||
|
<file>song_search_ccli.png</file>
|
||||||
|
<file>song_search_copy.png</file>
|
||||||
<file>song_search_lyrics.png</file>
|
<file>song_search_lyrics.png</file>
|
||||||
<file>song_search_title.png</file>
|
<file>song_search_title.png</file>
|
||||||
|
<file>song_search_topic.png</file>
|
||||||
<file>topic_edit.png</file>
|
<file>topic_edit.png</file>
|
||||||
<file>author_add.png</file>
|
<file>author_add.png</file>
|
||||||
<file>author_delete.png</file>
|
<file>author_delete.png</file>
|
||||||
|
BIN
resources/images/song_search_ccli.png
Normal file
BIN
resources/images/song_search_ccli.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 403 B |
BIN
resources/images/song_search_copy.png
Normal file
BIN
resources/images/song_search_copy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 498 B |
BIN
resources/images/song_search_topic.png
Normal file
BIN
resources/images/song_search_topic.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 993 B |
@ -26,6 +26,7 @@ Package to test the openlp.core.lib.projector.pjlink1 package.
|
|||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
from openlp.core.lib.projector.pjlink1 import PJLink1
|
from openlp.core.lib.projector.pjlink1 import PJLink1
|
||||||
|
from openlp.core.lib.projector.constants import E_PARAMETER, ERROR_STRING
|
||||||
|
|
||||||
from tests.functional import patch
|
from tests.functional import patch
|
||||||
from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE
|
from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE
|
||||||
@ -74,3 +75,20 @@ class TestPJLink(TestCase):
|
|||||||
# THEN: Projector class should be set with proper value
|
# THEN: Projector class should be set with proper value
|
||||||
self.assertEquals(pjlink.pjlink_class, '1',
|
self.assertEquals(pjlink.pjlink_class, '1',
|
||||||
'Non-standard class reply should have set proper class')
|
'Non-standard class reply should have set proper class')
|
||||||
|
|
||||||
|
@patch.object(pjlink_test, 'change_status')
|
||||||
|
def status_change_test(self, mock_change_status):
|
||||||
|
"""
|
||||||
|
Test process_command call with ERR2 (Parameter) status
|
||||||
|
"""
|
||||||
|
# GIVEN: Test object
|
||||||
|
pjlink = pjlink_test
|
||||||
|
|
||||||
|
# WHEN: process_command is called with "ERR2" status from projector
|
||||||
|
pjlink.process_command('POWR', 'ERR2')
|
||||||
|
|
||||||
|
# THEN: change_status should have called change_status with E_UNDEFINED
|
||||||
|
# as first parameter
|
||||||
|
mock_change_status.called_with(E_PARAMETER,
|
||||||
|
'change_status should have been called with "{}"'.format(
|
||||||
|
ERROR_STRING[E_PARAMETER]))
|
||||||
|
@ -22,13 +22,14 @@
|
|||||||
"""
|
"""
|
||||||
Package to test the openlp.core.ui.slidecontroller package.
|
Package to test the openlp.core.ui.slidecontroller package.
|
||||||
"""
|
"""
|
||||||
|
import PyQt5
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
from openlp.core.common import Registry, ThemeLevel
|
from openlp.core.common import Registry, ThemeLevel, Settings
|
||||||
from openlp.core.lib import ServiceItem, ServiceItemType, ItemCapabilities
|
from openlp.core.lib import ServiceItem, ServiceItemType, ItemCapabilities
|
||||||
from openlp.core.ui import ServiceManager
|
from openlp.core.ui import ServiceManager
|
||||||
|
|
||||||
from tests.functional import MagicMock
|
from tests.functional import MagicMock, patch
|
||||||
|
|
||||||
|
|
||||||
class TestServiceManager(TestCase):
|
class TestServiceManager(TestCase):
|
||||||
@ -540,3 +541,80 @@ class TestServiceManager(TestCase):
|
|||||||
self.assertEquals(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called')
|
self.assertEquals(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called')
|
||||||
self.assertEquals(service_manager.theme_menu.menuAction().setVisible.call_count, 1,
|
self.assertEquals(service_manager.theme_menu.menuAction().setVisible.call_count, 1,
|
||||||
'Should have be called once')
|
'Should have be called once')
|
||||||
|
|
||||||
|
@patch(u'openlp.core.ui.servicemanager.Settings')
|
||||||
|
@patch(u'PyQt5.QtCore.QTimer.singleShot')
|
||||||
|
def single_click_preview_test_true(self, mocked_singleShot, MockedSettings):
|
||||||
|
"""
|
||||||
|
Test that when "Preview items when clicked in Service Manager" enabled the preview timer starts
|
||||||
|
"""
|
||||||
|
# GIVEN: A setting to enable "Preview items when clicked in Service Manager" and a service manager.
|
||||||
|
mocked_settings = MagicMock()
|
||||||
|
mocked_settings.value.return_value = True
|
||||||
|
MockedSettings.return_value = mocked_settings
|
||||||
|
service_manager = ServiceManager(None)
|
||||||
|
# WHEN: on_single_click_preview() is called
|
||||||
|
service_manager.on_single_click_preview()
|
||||||
|
# THEN: timer should have been started
|
||||||
|
mocked_singleShot.assert_called_with(PyQt5.QtWidgets.QApplication.instance().doubleClickInterval(),
|
||||||
|
service_manager.on_single_click_preview_timeout)
|
||||||
|
|
||||||
|
@patch(u'openlp.core.ui.servicemanager.Settings')
|
||||||
|
@patch(u'PyQt5.QtCore.QTimer.singleShot')
|
||||||
|
def single_click_preview_test_false(self, mocked_singleShot, MockedSettings):
|
||||||
|
"""
|
||||||
|
Test that when "Preview items when clicked in Service Manager" disabled the preview timer doesn't start
|
||||||
|
"""
|
||||||
|
# GIVEN: A setting to enable "Preview items when clicked in Service Manager" and a service manager.
|
||||||
|
mocked_settings = MagicMock()
|
||||||
|
mocked_settings.value.return_value = False
|
||||||
|
MockedSettings.return_value = mocked_settings
|
||||||
|
service_manager = ServiceManager(None)
|
||||||
|
# WHEN: on_single_click_preview() is called
|
||||||
|
service_manager.on_single_click_preview()
|
||||||
|
# THEN: timer should not be started
|
||||||
|
self.assertEquals(mocked_singleShot.call_count, 0, 'Should not be called')
|
||||||
|
|
||||||
|
@patch(u'openlp.core.ui.servicemanager.Settings')
|
||||||
|
@patch(u'PyQt5.QtCore.QTimer.singleShot')
|
||||||
|
@patch(u'openlp.core.ui.servicemanager.ServiceManager.make_live')
|
||||||
|
def single_click_preview_test_double(self, mocked_make_live, mocked_singleShot, MockedSettings):
|
||||||
|
"""
|
||||||
|
Test that when a double click has registered the preview timer doesn't start
|
||||||
|
"""
|
||||||
|
# GIVEN: A setting to enable "Preview items when clicked in Service Manager" and a service manager.
|
||||||
|
mocked_settings = MagicMock()
|
||||||
|
mocked_settings.value.return_value = True
|
||||||
|
MockedSettings.return_value = mocked_settings
|
||||||
|
service_manager = ServiceManager(None)
|
||||||
|
# WHEN: on_single_click_preview() is called following a double click
|
||||||
|
service_manager.on_double_click_live()
|
||||||
|
service_manager.on_single_click_preview()
|
||||||
|
# THEN: timer should not be started
|
||||||
|
self.assertEquals(mocked_singleShot.call_count, 0, 'Should not be called')
|
||||||
|
|
||||||
|
@patch(u'openlp.core.ui.servicemanager.ServiceManager.make_preview')
|
||||||
|
def single_click_timeout_test_single(self, mocked_make_preview):
|
||||||
|
"""
|
||||||
|
Test that when a single click has been registered, the item is sent to preview
|
||||||
|
"""
|
||||||
|
# GIVEN: A service manager.
|
||||||
|
service_manager = ServiceManager(None)
|
||||||
|
# WHEN: on_single_click_preview() is called
|
||||||
|
service_manager.on_single_click_preview_timeout()
|
||||||
|
# THEN: make_preview() should have been called
|
||||||
|
self.assertEquals(mocked_make_preview.call_count, 1, 'Should have been called once')
|
||||||
|
|
||||||
|
@patch(u'openlp.core.ui.servicemanager.ServiceManager.make_preview')
|
||||||
|
@patch(u'openlp.core.ui.servicemanager.ServiceManager.make_live')
|
||||||
|
def single_click_timeout_test_double(self, mocked_make_live, mocked_make_preview):
|
||||||
|
"""
|
||||||
|
Test that when a double click has been registered, the item does not goes to preview
|
||||||
|
"""
|
||||||
|
# GIVEN: A service manager.
|
||||||
|
service_manager = ServiceManager(None)
|
||||||
|
# WHEN: on_single_click_preview() is called after a double click
|
||||||
|
service_manager.on_double_click_live()
|
||||||
|
service_manager.on_single_click_preview_timeout()
|
||||||
|
# THEN: make_preview() should have been called
|
||||||
|
self.assertEquals(mocked_make_preview.call_count, 0, 'Should not be called')
|
||||||
|
@ -48,6 +48,12 @@ class TestMediaItem(TestCase, TestMixin):
|
|||||||
with patch('openlp.core.lib.mediamanageritem.MediaManagerItem._setup'), \
|
with patch('openlp.core.lib.mediamanageritem.MediaManagerItem._setup'), \
|
||||||
patch('openlp.plugins.songs.forms.editsongform.EditSongForm.__init__'):
|
patch('openlp.plugins.songs.forms.editsongform.EditSongForm.__init__'):
|
||||||
self.media_item = SongMediaItem(None, MagicMock())
|
self.media_item = SongMediaItem(None, MagicMock())
|
||||||
|
self.media_item.save_auto_select_id = MagicMock()
|
||||||
|
self.media_item.list_view = MagicMock()
|
||||||
|
self.media_item.list_view.save_auto_select_id = MagicMock()
|
||||||
|
self.media_item.list_view.clear = MagicMock()
|
||||||
|
self.media_item.list_view.addItem = MagicMock()
|
||||||
|
self.media_item.auto_select_id = -1
|
||||||
self.media_item.display_songbook = False
|
self.media_item.display_songbook = False
|
||||||
self.media_item.display_copyright_symbol = False
|
self.media_item.display_copyright_symbol = False
|
||||||
self.setup_application()
|
self.setup_application()
|
||||||
@ -60,6 +66,151 @@ class TestMediaItem(TestCase, TestMixin):
|
|||||||
"""
|
"""
|
||||||
self.destroy_settings()
|
self.destroy_settings()
|
||||||
|
|
||||||
|
def display_results_song_test(self):
|
||||||
|
"""
|
||||||
|
Test displaying song search results with basic song
|
||||||
|
"""
|
||||||
|
# GIVEN: Search results, plus a mocked QtListWidgetItem
|
||||||
|
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
|
||||||
|
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
|
||||||
|
mock_search_results = []
|
||||||
|
mock_song = MagicMock()
|
||||||
|
mock_song.id = 1
|
||||||
|
mock_song.title = 'My Song'
|
||||||
|
mock_song.sort_key = 'My Song'
|
||||||
|
mock_song.authors = []
|
||||||
|
mock_author = MagicMock()
|
||||||
|
mock_author.display_name = 'My Author'
|
||||||
|
mock_song.authors.append(mock_author)
|
||||||
|
mock_song.temporary = False
|
||||||
|
mock_search_results.append(mock_song)
|
||||||
|
mock_qlist_widget = MagicMock()
|
||||||
|
MockedQListWidgetItem.return_value = mock_qlist_widget
|
||||||
|
|
||||||
|
# WHEN: I display song search results
|
||||||
|
self.media_item.display_results_song(mock_search_results)
|
||||||
|
|
||||||
|
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
|
||||||
|
self.media_item.list_view.clear.assert_called_with()
|
||||||
|
self.media_item.save_auto_select_id.assert_called_with()
|
||||||
|
MockedQListWidgetItem.assert_called_with('My Song (My Author)')
|
||||||
|
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
|
||||||
|
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
|
||||||
|
|
||||||
|
def display_results_author_test(self):
|
||||||
|
"""
|
||||||
|
Test displaying song search results grouped by author with basic song
|
||||||
|
"""
|
||||||
|
# GIVEN: Search results grouped by author, plus a mocked QtListWidgetItem
|
||||||
|
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
|
||||||
|
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
|
||||||
|
mock_search_results = []
|
||||||
|
mock_author = MagicMock()
|
||||||
|
mock_song = MagicMock()
|
||||||
|
mock_author.display_name = 'My Author'
|
||||||
|
mock_author.songs = []
|
||||||
|
mock_song.id = 1
|
||||||
|
mock_song.title = 'My Song'
|
||||||
|
mock_song.sort_key = 'My Song'
|
||||||
|
mock_song.temporary = False
|
||||||
|
mock_author.songs.append(mock_song)
|
||||||
|
mock_search_results.append(mock_author)
|
||||||
|
mock_qlist_widget = MagicMock()
|
||||||
|
MockedQListWidgetItem.return_value = mock_qlist_widget
|
||||||
|
|
||||||
|
# WHEN: I display song search results grouped by author
|
||||||
|
self.media_item.display_results_author(mock_search_results)
|
||||||
|
|
||||||
|
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
|
||||||
|
self.media_item.list_view.clear.assert_called_with()
|
||||||
|
MockedQListWidgetItem.assert_called_with('My Author (My Song)')
|
||||||
|
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
|
||||||
|
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
|
||||||
|
|
||||||
|
def display_results_topic_test(self):
|
||||||
|
"""
|
||||||
|
Test displaying song search results grouped by topic with basic song
|
||||||
|
"""
|
||||||
|
# GIVEN: Search results grouped by topic, plus a mocked QtListWidgetItem
|
||||||
|
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
|
||||||
|
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
|
||||||
|
mock_search_results = []
|
||||||
|
mock_topic = MagicMock()
|
||||||
|
mock_song = MagicMock()
|
||||||
|
mock_topic.name = 'My Topic'
|
||||||
|
mock_topic.songs = []
|
||||||
|
mock_song.id = 1
|
||||||
|
mock_song.title = 'My Song'
|
||||||
|
mock_song.sort_key = 'My Song'
|
||||||
|
mock_song.temporary = False
|
||||||
|
mock_topic.songs.append(mock_song)
|
||||||
|
mock_search_results.append(mock_topic)
|
||||||
|
mock_qlist_widget = MagicMock()
|
||||||
|
MockedQListWidgetItem.return_value = mock_qlist_widget
|
||||||
|
|
||||||
|
# WHEN: I display song search results grouped by topic
|
||||||
|
self.media_item.display_results_topic(mock_search_results)
|
||||||
|
|
||||||
|
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
|
||||||
|
self.media_item.list_view.clear.assert_called_with()
|
||||||
|
MockedQListWidgetItem.assert_called_with('My Topic (My Song)')
|
||||||
|
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
|
||||||
|
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
|
||||||
|
|
||||||
|
def display_results_themes_test(self):
|
||||||
|
"""
|
||||||
|
Test displaying song search results sorted by theme with basic song
|
||||||
|
"""
|
||||||
|
# GIVEN: Search results sorted by theme, plus a mocked QtListWidgetItem
|
||||||
|
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
|
||||||
|
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
|
||||||
|
mock_search_results = []
|
||||||
|
mock_song = MagicMock()
|
||||||
|
mock_song.id = 1
|
||||||
|
mock_song.title = 'My Song'
|
||||||
|
mock_song.sort_key = 'My Song'
|
||||||
|
mock_song.theme_name = 'My Theme'
|
||||||
|
mock_song.temporary = False
|
||||||
|
mock_search_results.append(mock_song)
|
||||||
|
mock_qlist_widget = MagicMock()
|
||||||
|
MockedQListWidgetItem.return_value = mock_qlist_widget
|
||||||
|
|
||||||
|
# WHEN: I display song search results sorted by theme
|
||||||
|
self.media_item.display_results_themes(mock_search_results)
|
||||||
|
|
||||||
|
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
|
||||||
|
self.media_item.list_view.clear.assert_called_with()
|
||||||
|
MockedQListWidgetItem.assert_called_with('My Theme (My Song)')
|
||||||
|
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
|
||||||
|
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
|
||||||
|
|
||||||
|
def display_results_cclinumber_test(self):
|
||||||
|
"""
|
||||||
|
Test displaying song search results sorted by CCLI number with basic song
|
||||||
|
"""
|
||||||
|
# GIVEN: Search results sorted by CCLI number, plus a mocked QtListWidgetItem
|
||||||
|
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
|
||||||
|
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
|
||||||
|
mock_search_results = []
|
||||||
|
mock_song = MagicMock()
|
||||||
|
mock_song.id = 1
|
||||||
|
mock_song.title = 'My Song'
|
||||||
|
mock_song.sort_key = 'My Song'
|
||||||
|
mock_song.ccli_number = '12345'
|
||||||
|
mock_song.temporary = False
|
||||||
|
mock_search_results.append(mock_song)
|
||||||
|
mock_qlist_widget = MagicMock()
|
||||||
|
MockedQListWidgetItem.return_value = mock_qlist_widget
|
||||||
|
|
||||||
|
# WHEN: I display song search results sorted by CCLI number
|
||||||
|
self.media_item.display_results_cclinumber(mock_search_results)
|
||||||
|
|
||||||
|
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
|
||||||
|
self.media_item.list_view.clear.assert_called_with()
|
||||||
|
MockedQListWidgetItem.assert_called_with('12345 (My Song)')
|
||||||
|
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
|
||||||
|
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
|
||||||
|
|
||||||
def build_song_footer_one_author_test(self):
|
def build_song_footer_one_author_test(self):
|
||||||
"""
|
"""
|
||||||
Test build songs footer with basic song and one author
|
Test build songs footer with basic song and one author
|
||||||
@ -265,6 +416,19 @@ class TestMediaItem(TestCase, TestMixin):
|
|||||||
# THEN: They should not match
|
# THEN: They should not match
|
||||||
self.assertFalse(result, "Authors should not match")
|
self.assertFalse(result, "Authors should not match")
|
||||||
|
|
||||||
|
def natural_sort_key_test(self):
|
||||||
|
"""
|
||||||
|
Test the _natural_sort_key function
|
||||||
|
"""
|
||||||
|
# GIVEN: A string to be converted into a sort key
|
||||||
|
string_sort_key = 'A1B12C'
|
||||||
|
|
||||||
|
# WHEN: We attempt to create a sort key
|
||||||
|
sort_key_result = self.media_item._natural_sort_key(string_sort_key)
|
||||||
|
|
||||||
|
# THEN: We should get back a tuple split on integers
|
||||||
|
self.assertEqual(sort_key_result, ['a', 1, 'b', 12, 'c'])
|
||||||
|
|
||||||
def build_remote_search_test(self):
|
def build_remote_search_test(self):
|
||||||
"""
|
"""
|
||||||
Test results for the remote search api
|
Test results for the remote search api
|
||||||
|
@ -81,3 +81,19 @@ class TestSongUsage(TestCase):
|
|||||||
|
|
||||||
# THEN: It should return True
|
# THEN: It should return True
|
||||||
self.assertTrue(ret)
|
self.assertTrue(ret)
|
||||||
|
|
||||||
|
@patch('openlp.plugins.songusage.songusageplugin.Manager')
|
||||||
|
def toggle_song_usage_state_test(self, MockedManager):
|
||||||
|
"""
|
||||||
|
Test that toggle_song_usage_state does toggle song_usage_state
|
||||||
|
"""
|
||||||
|
# GIVEN: A SongUsagePlugin
|
||||||
|
song_usage = SongUsagePlugin()
|
||||||
|
song_usage.set_button_state = MagicMock()
|
||||||
|
song_usage.song_usage_active = True
|
||||||
|
|
||||||
|
# WHEN: calling toggle_song_usage_state
|
||||||
|
song_usage.toggle_song_usage_state()
|
||||||
|
|
||||||
|
# THEN: song_usage_state should have been toogled
|
||||||
|
self.assertFalse(song_usage.song_usage_active)
|
||||||
|
@ -32,7 +32,7 @@ from PyQt5 import QtWidgets
|
|||||||
|
|
||||||
from openlp.core.common import Registry, Settings
|
from openlp.core.common import Registry, Settings
|
||||||
from openlp.core.lib.pluginmanager import PluginManager
|
from openlp.core.lib.pluginmanager import PluginManager
|
||||||
from tests.interfaces import MagicMock
|
from tests.interfaces import MagicMock, patch
|
||||||
from tests.helpers.testmixin import TestMixin
|
from tests.helpers.testmixin import TestMixin
|
||||||
|
|
||||||
|
|
||||||
@ -45,13 +45,12 @@ class TestPluginManager(TestCase, TestMixin):
|
|||||||
"""
|
"""
|
||||||
Some pre-test setup required.
|
Some pre-test setup required.
|
||||||
"""
|
"""
|
||||||
Settings.setDefaultFormat(Settings.IniFormat)
|
self.setup_application()
|
||||||
self.build_settings()
|
self.build_settings()
|
||||||
self.temp_dir = mkdtemp('openlp')
|
self.temp_dir = mkdtemp('openlp')
|
||||||
Settings().setValue('advanced/data path', self.temp_dir)
|
Settings().setValue('advanced/data path', self.temp_dir)
|
||||||
Registry.create()
|
Registry.create()
|
||||||
Registry().register('service_list', MagicMock())
|
Registry().register('service_list', MagicMock())
|
||||||
self.setup_application()
|
|
||||||
self.main_window = QtWidgets.QMainWindow()
|
self.main_window = QtWidgets.QMainWindow()
|
||||||
Registry().register('main_window', self.main_window)
|
Registry().register('main_window', self.main_window)
|
||||||
|
|
||||||
@ -64,7 +63,13 @@ class TestPluginManager(TestCase, TestMixin):
|
|||||||
gc.collect()
|
gc.collect()
|
||||||
shutil.rmtree(self.temp_dir)
|
shutil.rmtree(self.temp_dir)
|
||||||
|
|
||||||
def find_plugins_test(self):
|
@patch('openlp.plugins.songusage.lib.db.init_schema')
|
||||||
|
@patch('openlp.plugins.songs.lib.db.init_schema')
|
||||||
|
@patch('openlp.plugins.images.lib.db.init_schema')
|
||||||
|
@patch('openlp.plugins.custom.lib.db.init_schema')
|
||||||
|
@patch('openlp.plugins.alerts.lib.db.init_schema')
|
||||||
|
@patch('openlp.plugins.bibles.lib.db.init_schema')
|
||||||
|
def find_plugins_test(self, mocked_is1, mocked_is2, mocked_is3, mocked_is4, mocked_is5, mocked_is6):
|
||||||
"""
|
"""
|
||||||
Test the find_plugins() method to ensure it imports the correct plugins
|
Test the find_plugins() method to ensure it imports the correct plugins
|
||||||
"""
|
"""
|
||||||
|
@ -128,3 +128,19 @@ class TestEditCustomForm(TestCase, TestMixin):
|
|||||||
# THEN: The validate method should have returned False.
|
# THEN: The validate method should have returned False.
|
||||||
assert not result, 'The _validate() method should have retured False'
|
assert not result, 'The _validate() method should have retured False'
|
||||||
mocked_critical_error_message_box.assert_called_with(message='You need to add at least one slide.')
|
mocked_critical_error_message_box.assert_called_with(message='You need to add at least one slide.')
|
||||||
|
|
||||||
|
def update_slide_list_test(self):
|
||||||
|
"""
|
||||||
|
Test the update_slide_list() method
|
||||||
|
"""
|
||||||
|
# GIVEN: Mocked slide_list_view with a slide with 3 lines
|
||||||
|
self.form.slide_list_view = MagicMock()
|
||||||
|
self.form.slide_list_view.count.return_value = 1
|
||||||
|
self.form.slide_list_view.currentRow.return_value = 0
|
||||||
|
self.form.slide_list_view.item.return_value = MagicMock(return_value='1st Slide\n2nd Slide\n3rd Slide')
|
||||||
|
|
||||||
|
# WHEN: updating the slide by splitting the lines into slides
|
||||||
|
self.form.update_slide_list(['1st Slide', '2nd Slide', '3rd Slide'])
|
||||||
|
|
||||||
|
# THEN: The slides should be created in correct order
|
||||||
|
self.form.slide_list_view.addItems.assert_called_with(['1st Slide', '2nd Slide', '3rd Slide'])
|
||||||
|
Loading…
Reference in New Issue
Block a user