From 17d9bf0adc4ee2663dc6e682dd69e0e9bb35c2c6 Mon Sep 17 00:00:00 2001 From: Ian Knight Date: Sat, 16 Apr 2016 16:23:01 +0930 Subject: [PATCH] Implemented auto-scroll-choice --- openlp/core/common/settings.py | 3 +- openlp/core/ui/advancedtab.py | 29 ++++++ openlp/core/ui/listpreviewwidget.py | 18 +++- .../openlp_core_ui/test_listpreviewwidget.py | 92 +++++++++++++++++++ 4 files changed, 136 insertions(+), 6 deletions(-) diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index efa251eec..ad6e812c4 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -107,6 +107,7 @@ class Settings(QtCore.QSettings): __default_settings__ = { 'advanced/add page break': False, 'advanced/alternate rows': not is_win(), + 'advanced/autoscrolling': {'dist':1, 'pos':0}, 'advanced/current media plugin': -1, 'advanced/data path': '', 'advanced/default color': '#ffffff', @@ -121,7 +122,6 @@ class Settings(QtCore.QSettings): 'advanced/double click live': False, 'advanced/enable exit confirmation': True, 'advanced/expand service item': False, - 'advanced/slide max height': 0, 'advanced/hide mouse': True, 'advanced/is portable': False, 'advanced/max recent files': 20, @@ -131,6 +131,7 @@ class Settings(QtCore.QSettings): 'advanced/recent file count': 4, 'advanced/save current plugin': False, 'advanced/slide limits': SlideLimits.End, + 'advanced/slide max height': 0, 'advanced/single click preview': False, 'advanced/single click service preview': False, 'advanced/x11 bypass wm': X11_BYPASS_DEFAULT, diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index f32672f58..2724ec489 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -49,6 +49,9 @@ class AdvancedTab(SettingsTab): self.default_color = '#ffffff' self.data_exists = False self.icon_path = ':/system/system_settings.png' + self.autoscroll_map = [None, {'dist':-1, 'pos':0}, {'dist':-1, 'pos':1}, {'dist':-1, 'pos':2}, + {'dist':0, 'pos':0}, {'dist':0, 'pos':1}, {'dist':0, 'pos':2}, {'dist':0, 'pos':3}, + {'dist':1, 'pos':0}, {'dist':1, 'pos':1}, {'dist':1, 'pos':2}, {'dist':1, 'pos':3}] advanced_translated = translate('OpenLP.AdvancedTab', 'Advanced') super(AdvancedTab, self).__init__(parent, 'Advanced', advanced_translated) @@ -90,6 +93,13 @@ class AdvancedTab(SettingsTab): self.slide_max_height_spin_box.setRange(0, 1000) self.slide_max_height_spin_box.setSingleStep(20) self.ui_layout.addRow(self.slide_max_height_label, self.slide_max_height_spin_box) + self.autoscroll_label = QtWidgets.QLabel(self.ui_group_box) + self.autoscroll_label.setObjectName('autoscroll_label') + self.autoscroll_combo_box = QtWidgets.QComboBox(self.ui_group_box) + self.autoscroll_combo_box.addItems(['', '', '', '', '', '', '', '', '', '', '', '']) + self.autoscroll_combo_box.setObjectName('autoscroll_combo_box') + self.ui_layout.addRow(self.autoscroll_label) + self.ui_layout.addRow(self.autoscroll_combo_box) self.search_as_type_check_box = QtWidgets.QCheckBox(self.ui_group_box) self.search_as_type_check_box.setObjectName('SearchAsType_check_box') self.ui_layout.addRow(self.search_as_type_check_box) @@ -287,6 +297,20 @@ class AdvancedTab(SettingsTab): self.slide_max_height_label.setText(translate('OpenLP.AdvancedTab', 'Max height for non-text slides\nin slide controller:')) self.slide_max_height_spin_box.setSpecialValueText(translate('OpenLP.AdvancedTab', 'Disabled')) + self.autoscroll_label.setText(translate('OpenLP.AdvancedTab', + 'When changing slides:')) + self.autoscroll_combo_box.setItemText(0, translate('OpenLP.AdvancedTab', 'Do not auto-scroll')) + self.autoscroll_combo_box.setItemText(1, translate('OpenLP.AdvancedTab', 'Auto-scroll the previous slide into view')) + self.autoscroll_combo_box.setItemText(2, translate('OpenLP.AdvancedTab', 'Auto-scroll the previous slide to top')) + self.autoscroll_combo_box.setItemText(3, translate('OpenLP.AdvancedTab', 'Auto-scroll the previous slide to middle')) + self.autoscroll_combo_box.setItemText(4, translate('OpenLP.AdvancedTab', 'Auto-scroll the current slide into view')) + self.autoscroll_combo_box.setItemText(5, translate('OpenLP.AdvancedTab', 'Auto-scroll the current slide to top')) + self.autoscroll_combo_box.setItemText(6, translate('OpenLP.AdvancedTab', 'Auto-scroll the current slide to middle')) + self.autoscroll_combo_box.setItemText(7, translate('OpenLP.AdvancedTab', 'Auto-scroll the current slide to bottom')) + self.autoscroll_combo_box.setItemText(8, translate('OpenLP.AdvancedTab', 'Auto-scroll the next slide into view')) + self.autoscroll_combo_box.setItemText(9, translate('OpenLP.AdvancedTab', 'Auto-scroll the next slide to top')) + self.autoscroll_combo_box.setItemText(10, translate('OpenLP.AdvancedTab', 'Auto-scroll the next slide to middle')) + self.autoscroll_combo_box.setItemText(11, translate('OpenLP.AdvancedTab', 'Auto-scroll the next slide to bottom')) self.enable_auto_close_check_box.setText(translate('OpenLP.AdvancedTab', 'Enable application exit confirmation')) self.service_name_group_box.setTitle(translate('OpenLP.AdvancedTab', 'Default Service Name')) @@ -357,6 +381,10 @@ class AdvancedTab(SettingsTab): 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.slide_max_height_spin_box.setValue(settings.value('slide max height')) + autoscroll_value = settings.value('autoscrolling') + for i in range(0, len(self.autoscroll_map)): + if self.autoscroll_map[i] == autoscroll_value: + self.autoscroll_combo_box.setCurrentIndex(i) self.enable_auto_close_check_box.setChecked(settings.value('enable exit confirmation')) self.hide_mouse_check_box.setChecked(settings.value('hide mouse')) self.service_name_day.setCurrentIndex(settings.value('default service day')) @@ -440,6 +468,7 @@ class AdvancedTab(SettingsTab): 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('slide max height', self.slide_max_height_spin_box.value()) + settings.setValue('autoscrolling', self.autoscroll_map[self.autoscroll_combo_box.currentIndex()]) settings.setValue('enable exit confirmation', self.enable_auto_close_check_box.isChecked()) settings.setValue('hide mouse', self.hide_mouse_check_box.isChecked()) settings.setValue('alternate rows', self.alternate_rows_check_box.isChecked()) diff --git a/openlp/core/ui/listpreviewwidget.py b/openlp/core/ui/listpreviewwidget.py index 68c983d42..c1b197cb7 100644 --- a/openlp/core/ui/listpreviewwidget.py +++ b/openlp/core/ui/listpreviewwidget.py @@ -194,11 +194,19 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties): """ Switches to the given row. """ - if slide >= self.slide_count(): - slide = self.slide_count() - 1 - # Scroll to next item if possible. - if slide + 1 < self.slide_count(): - self.scrollToItem(self.item(slide + 1, 0)) + # Retrieve setting + autoscrolling = Settings().value('advanced/autoscrolling') + # Check if auto-scroll disabled (None) and validate value as dict containing 'dist' and 'pos' + if (not isinstance(autoscrolling, dict)) or ('dist' not in autoscrolling) or ('pos' not in autoscrolling): + return + # prevent scrolling past list bounds + scroll_to_slide = slide + autoscrolling['dist'] + if scroll_to_slide < 0: + scroll_to_slide = 0 + if scroll_to_slide >= self.slide_count(): + scroll_to_slide = self.slide_count() - 1 + # Scroll to item if possible. + self.scrollToItem(self.item(scroll_to_slide, 0), autoscrolling['pos']) self.selectRow(slide) def current_slide_number(self): diff --git a/tests/functional/openlp_core_ui/test_listpreviewwidget.py b/tests/functional/openlp_core_ui/test_listpreviewwidget.py index a222189e6..4260711fa 100644 --- a/tests/functional/openlp_core_ui/test_listpreviewwidget.py +++ b/tests/functional/openlp_core_ui/test_listpreviewwidget.py @@ -273,3 +273,95 @@ class TestListPreviewWidget(TestCase): # THEN: self.cellWidget(row, 0).children()[1].setMaximumWidth() should be called mocked_cellWidget_child.setMaximumWidth.assert_called_once_with(150) + + @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.selectRow') + @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.scrollToItem') + @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.item') + @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.slide_count') + def autoscroll_test_setting_invalid(self, mocked_slide_count, mocked_item, mocked_scrollToItem, mocked_selectRow): + """ + Test if 'advanced/autoscrolling' setting None or invalid, that no autoscrolling occurs on change_slide(). + """ + # GIVEN: A setting for autoscrolling and a ListPreviewWidget. + # Mock Settings().value('advanced/autoscrolling') + self.mocked_Settings_obj.value.return_value = None + # Mocked returns + mocked_slide_count.return_value = 1 + mocked_item.return_value = None + # init ListPreviewWidget and load service item + list_preview_widget = ListPreviewWidget(None, 1) + + # WHEN: change_slide() is called + list_preview_widget.change_slide(0) + self.mocked_Settings_obj.value.return_value = 1 + list_preview_widget.change_slide(0) + self.mocked_Settings_obj.value.return_value = {'test':1} + list_preview_widget.change_slide(0) + self.mocked_Settings_obj.value.return_value = {'dist':1, 'test':1} + list_preview_widget.change_slide(0) + + # THEN: no further functions should be called + self.assertEquals(mocked_slide_count.call_count, 0, 'Should not be called') + self.assertEquals(mocked_item.call_count, 0, 'Should not be called') + self.assertEquals(mocked_scrollToItem.call_count, 0, 'Should not be called') + self.assertEquals(mocked_selectRow.call_count, 0, 'Should not be called') + + @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.selectRow') + @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.scrollToItem') + @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.item') + @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.slide_count') + def autoscroll_test_dist_bounds(self, mocked_slide_count, mocked_item, mocked_scrollToItem, mocked_selectRow): + """ + Test if 'advanced/autoscrolling' setting asks to scroll beyond list bounds, that it does not beyond. + """ + # GIVEN: A setting for autoscrolling and a ListPreviewWidget. + # Mock Settings().value('advanced/autoscrolling') + self.mocked_Settings_obj.value.return_value = {'dist':-1, 'pos':1} + # Mocked returns + mocked_slide_count.return_value = 1 + mocked_item.return_value = None + # init ListPreviewWidget and load service item + list_preview_widget = ListPreviewWidget(None, 1) + + # WHEN: change_slide() is called + list_preview_widget.change_slide(0) + self.mocked_Settings_obj.value.return_value = {'dist':1, 'pos':1} + list_preview_widget.change_slide(0) + + # THEN: no further functions should be called + self.assertEquals(mocked_slide_count.call_count, 3, 'Should be called') + self.assertEquals(mocked_scrollToItem.call_count, 2, 'Should be called') + self.assertEquals(mocked_selectRow.call_count, 2, 'Should be called') + calls = [call(0, 0), call(0, 0)] + mocked_item.assert_has_calls(calls) + + @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.selectRow') + @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.scrollToItem') + @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.item') + @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.slide_count') + def autoscroll_test_normal(self, mocked_slide_count, mocked_item, mocked_scrollToItem, mocked_selectRow): + """ + Test if 'advanced/autoscrolling' setting valid, autoscrolling called as expected. + """ + # GIVEN: A setting for autoscrolling and a ListPreviewWidget. + # Mock Settings().value('advanced/autoscrolling') + self.mocked_Settings_obj.value.return_value = {'dist':-1, 'pos':1} + # Mocked returns + mocked_slide_count.return_value = 3 + mocked_item.return_value = None + # init ListPreviewWidget and load service item + list_preview_widget = ListPreviewWidget(None, 1) + + # WHEN: change_slide() is called + list_preview_widget.change_slide(1) + self.mocked_Settings_obj.value.return_value = {'dist':0, 'pos':1} + list_preview_widget.change_slide(1) + self.mocked_Settings_obj.value.return_value = {'dist':1, 'pos':1} + list_preview_widget.change_slide(1) + + # THEN: no further functions should be called + self.assertEquals(mocked_slide_count.call_count, 3, 'Should be called') + self.assertEquals(mocked_scrollToItem.call_count, 3, 'Should be called') + self.assertEquals(mocked_selectRow.call_count, 3, 'Should be called') + calls = [call(0, 0), call(1, 0), call(2, 0)] + mocked_item.assert_has_calls(calls)