Merge branch 'list-view' into 'master'

New Grid View for Theme Manager

See merge request openlp/openlp!491
This commit is contained in:
Raoul Snyman 2023-02-17 14:35:10 +00:00
commit f22eafbeb8
8 changed files with 302 additions and 5 deletions

View File

@ -377,6 +377,7 @@ class Settings(QtCore.QSettings):
'user interface/main window state': QtCore.QByteArray(),
'user interface/preview panel': True,
'user interface/preview splitter geometry': QtCore.QByteArray(),
'user interface/theme manager view mode': 0,
'user interface/show library': True,
'user interface/show projectors': True,
'user interface/show service': True,

View File

@ -337,3 +337,98 @@ def find_and_set_in_combo_box(combo_box, value_to_find, set_missing=True):
# Not Found.
index = 0 if set_missing else combo_box.currentIndex()
combo_box.setCurrentIndex(index)
class MultipleViewModeList(QtWidgets.QListWidget):
"""
An opinionated implementation of QListWidget that allows the list to use List View and
Icon View.
:param parent:
:param mode: The default mode of the list.
"""
def __init__(self, parent, mode=QtWidgets.QListWidget.ViewMode.ListMode):
super().__init__(parent)
self._view_mode_icon_size_list = None
self._view_mode_icon_size_grid = None
if mode == QtWidgets.QListWidget.ViewMode.IconMode:
self.setViewMode(QtWidgets.QListWidget.ViewMode.IconMode)
def set_icon_size_by_view_mode(self, mode, size):
"""
Sets the preferred icon size by view mode.
:param mode: Desired mode to set the default size
:param size: Default size for the provided mode
"""
if mode == QtWidgets.QListWidget.ViewMode.ListMode:
self._view_mode_icon_size_list = size
elif mode == QtWidgets.QListWidget.ViewMode.IconMode:
self._view_mode_icon_size_grid = size
if self.viewMode() == mode:
self.setIconSize(size)
def setViewMode(self, mode):
if mode is None:
mode = QtWidgets.QListWidget.ViewMode.ListMode
super().setViewMode(mode)
if mode == QtWidgets.QListWidget.ViewMode.IconMode:
if self._view_mode_icon_size_list is None:
self._view_mode_icon_size_list = self.iconSize()
if self._view_mode_icon_size_grid is not None:
self.setIconSize(self._view_mode_icon_size_grid)
self.setUniformItemSizes(True)
self.setResizeMode(QtWidgets.QListWidget.ResizeMode.Adjust)
self._on_resize_icon_mode()
elif mode == QtWidgets.QListWidget.ViewMode.ListMode:
if self._view_mode_icon_size_grid is None:
self._view_mode_icon_size_grid = self.iconSize()
if self._view_mode_icon_size_list is not None:
self.setIconSize(self._view_mode_icon_size_list)
self.setUniformItemSizes(False)
self.setResizeMode(QtWidgets.QListWidget.ResizeMode.Fixed)
self.setFlow(QtWidgets.QListWidget.Flow.TopToBottom)
def resizeEvent(self, event: QtGui.QResizeEvent):
super().resizeEvent(event)
self._on_resize_icon_mode()
def _on_resize_icon_mode(self):
if self.viewMode() == QtWidgets.QListWidget.ViewMode.IconMode:
size = self.size()
iconHeight = self.iconSize().height()
if size.height() < ((iconHeight + (iconHeight / 2))):
if self.flow() != QtWidgets.QListWidget.Flow.TopToBottom:
self.setFlow(QtWidgets.QListWidget.Flow.TopToBottom)
elif self.flow() != QtWidgets.QListWidget.Flow.LeftToRight:
self.setFlow(QtWidgets.QListWidget.Flow.LeftToRight)
def set_list_view_mode_toolbar_state(toolbar, mode):
"""
Updates a OpenLPToolbar ListView button states after clicked
:param toolbar: OpenLPToolbar instance
:param mode: New QListView mode
"""
if mode == QtWidgets.QListView.ViewMode.IconMode:
toolbar.set_widget_checked('listView', False)
toolbar.set_widget_checked('gridView', True)
elif mode == QtWidgets.QListView.ViewMode.ListMode:
toolbar.set_widget_checked('listView', True)
toolbar.set_widget_checked('gridView', False)
def add_list_view_mode_items_to_toolbar(toolbar, trigger_handler):
toolbar.add_toolbar_action('listView',
text=translate('OpenLP.Ui', 'List View'),
icon=UiIcons().view_list,
checked=False,
tooltip=translate('OpenLP.Ui', 'Shows the list in a list view.'),
triggers=trigger_handler.on_set_view_mode_list)
toolbar.add_toolbar_action('gridView',
text=translate('OpenLP.Ui', 'Grid View'),
icon=UiIcons().view_grid,
checked=False,
tooltip=translate('OpenLP.Ui', 'Shows the list in a grid view.'),
triggers=trigger_handler.on_set_view_mode_grid)

View File

@ -170,6 +170,8 @@ class UiIcons(metaclass=Singleton):
'usermo': {'icon': 'op.users'},
'users': {'icon': 'fa.users'},
'video': {'icon': 'fa.file-video-o'},
'view_list': {'icon': 'fa.th-list'},
'view_grid': {'icon': 'fa.th-large'},
'volunteer': {'icon': 'fa.group'}
}
self.load_icons(icon_list)

View File

@ -1340,6 +1340,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
self.settings.setValue('user interface/live splitter geometry', self.live_controller.splitter.saveState())
self.settings.setValue('user interface/preview splitter geometry', self.preview_controller.splitter.saveState())
self.settings.setValue('user interface/main window splitter geometry', self.control_splitter.saveState())
self.theme_manager.save_settings()
def update_recent_files_menu(self):
"""

View File

@ -40,7 +40,8 @@ from openlp.core.common.utils import wait_for
from openlp.core.lib import build_icon, check_item_selected, create_thumb, get_text_file_string, validate_thumb
from openlp.core.lib.exceptions import ValidationError
from openlp.core.lib.theme import Theme
from openlp.core.lib.ui import create_widget_action, critical_error_message_box
from openlp.core.lib.ui import MultipleViewModeList, add_list_view_mode_items_to_toolbar, create_widget_action, \
critical_error_message_box, set_list_view_mode_toolbar_state
from openlp.core.ui.filerenameform import FileRenameForm
from openlp.core.ui.icons import UiIcons
from openlp.core.ui.themeform import ThemeForm
@ -92,13 +93,18 @@ class Ui_ThemeManager(object):
icon=UiIcons().upload,
tooltip=translate('OpenLP.ThemeManager', 'Export a theme.'),
triggers=self.on_export_theme)
self.toolbar.addSeparator()
add_list_view_mode_items_to_toolbar(self.toolbar, self)
self.layout.addWidget(self.toolbar)
self.theme_widget = QtWidgets.QWidgetAction(self.toolbar)
self.theme_widget.setObjectName('theme_widget')
# create theme manager list
self.theme_list_widget = QtWidgets.QListWidget(widget)
self.theme_list_widget = MultipleViewModeList(widget)
self.theme_list_widget.setAlternatingRowColors(True)
self.theme_list_widget.setIconSize(QtCore.QSize(88, 50))
self.theme_list_widget.set_icon_size_by_view_mode(QtWidgets.QListView.ViewMode.ListMode, QtCore.QSize(88, 50))
self.theme_list_widget.set_icon_size_by_view_mode(QtWidgets.QListView.ViewMode.IconMode,
QtCore.QSize(176, 100))
self.theme_list_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.theme_list_widget.setObjectName('theme_list_widget')
self.layout.addWidget(self.theme_list_widget)
@ -180,6 +186,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
"""
process the bootstrap post setup request
"""
self.load_settings()
self.progress_form = ThemeProgressForm(self)
self.theme_form = ThemeForm(self)
self.theme_form.path = self.theme_path
@ -187,6 +194,20 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
self.upgrade_themes() # TODO: Can be removed when upgrade path from OpenLP 2.4 no longer needed
self.load_themes()
def load_settings(self):
view_mode_value = None
try:
view_mode_value = self.settings.value('user interface/theme manager view mode')
self.theme_list_widget.setViewMode(view_mode_value)
except Exception:
view_mode_value = QtWidgets.QListWidget.ViewMode.ListMode
self.settings.setValue('user interface/theme manager view mode', view_mode_value)
set_list_view_mode_toolbar_state(self.toolbar, view_mode_value)
def save_settings(self):
if self.theme_list_widget:
self.settings.setValue('user interface/theme manager view mode', self.theme_list_widget.viewMode())
def screen_changed(self):
"""
Update the default theme location and size for when screen size changed
@ -278,6 +299,16 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
self.delete_toolbar_action.setVisible(item not in self.theme_list_widget.selectedItems())
Registry().execute('theme_change_global')
def on_set_view_mode_list(self):
mode = QtWidgets.QListView.ViewMode.ListMode
self.theme_list_widget.setViewMode(mode)
set_list_view_mode_toolbar_state(self.toolbar, mode)
def on_set_view_mode_grid(self):
mode = QtWidgets.QListView.ViewMode.IconMode
self.theme_list_widget.setViewMode(mode)
set_list_view_mode_toolbar_state(self.toolbar, mode)
def on_theme_level_updated(self):
"""
Update the theme level, called from web controller.

View File

@ -95,6 +95,25 @@ class OpenLPToolbar(QtWidgets.QToolBar):
else:
log.warning('No handle "%s" in actions list.', str(handle))
def set_widget_checked(self, widgets, checked=True):
"""
Set the checked state for a widget or a list of widgets.
:param widgets: A list of string with widget object names.
:param enabled: The new state as bool.
"""
if isinstance(widgets, list):
for handle in widgets:
if handle in self.actions:
self.actions[handle].setChecked(checked)
else:
log.warning('No handle "%s" in actions list.', str(handle))
else:
if widgets in self.actions:
self.actions[widgets].setChecked(checked)
else:
log.warning('No handle "%s" in actions list.', str(widgets))
def remove_widget(self, name):
"""
Find and remove an action from the toolbar

View File

@ -26,9 +26,11 @@ from unittest.mock import MagicMock, call, patch
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.lib.ui import add_welcome_page, create_action, create_button, create_button_box, \
create_horizontal_adjusting_combo_box, create_valign_selection_widgets, create_widget_action, \
critical_error_message_box, find_and_set_in_combo_box, set_case_insensitive_completer
from openlp.core.lib.ui import MultipleViewModeList, add_list_view_mode_items_to_toolbar, add_welcome_page, \
create_action, create_button, create_button_box, create_horizontal_adjusting_combo_box, \
create_valign_selection_widgets, create_widget_action, critical_error_message_box, find_and_set_in_combo_box, \
set_case_insensitive_completer, set_list_view_mode_toolbar_state
from openlp.core.widgets.toolbar import OpenLPToolbar
def test_add_welcome_page():
@ -341,3 +343,148 @@ def test_set_case_insensitive_completer():
completer = line_edit.completer()
assert isinstance(completer, QtWidgets.QCompleter)
assert completer.caseSensitivity() == QtCore.Qt.CaseInsensitive
def test_multiple_view_mode_list(settings):
"""
Tests if MultipleViewModeList works and have the default ViewMode
"""
# GIVEN: MultipleViewModeList
# WHEN: It's built
list = MultipleViewModeList(None)
# THEN: It's should be build sucessfully, with default ListMode
list.viewMode() == QtWidgets.QListWidget.ViewMode.ListMode
def test_multiple_view_mode_list_set_icon_size_by_view_mode_icon_mode(settings):
"""
Tests if MultipleViewModeList's set_icon_size_by_view_mode works with a IconMode default value
"""
# GIVEN: a MultipleViewModeList instance and a custom icon size
list = MultipleViewModeList(None)
icon_size = QtCore.QSize(93, 37)
# WHEN: set_icon_size_by_view_mode is called with IconMode and mode is changed
list.set_icon_size_by_view_mode(QtWidgets.QListWidget.ViewMode.IconMode, icon_size)
list.setViewMode(QtWidgets.QListWidget.ViewMode.IconMode)
# THEN: New icon size should be set successfully
assert list.iconSize() == icon_size
def test_multiple_view_mode_list_set_icon_size_by_view_mode_list_mode(settings):
"""
Tests if MultipleViewModeList's set_icon_size_by_view_mode works with a ListMode default value
"""
# GIVEN: a MultipleViewModeList instance and a custom icon size
list = MultipleViewModeList(None, QtWidgets.QListWidget.ViewMode.IconMode)
icon_size = QtCore.QSize(93, 37)
# WHEN: set_icon_size_by_view_mode is called with ListMode and mode is changed
list.set_icon_size_by_view_mode(QtWidgets.QListWidget.ViewMode.ListMode, icon_size)
list.setViewMode(QtWidgets.QListWidget.ViewMode.ListMode)
# THEN: New icon size should be set successfully
assert list.iconSize() == icon_size
def test_multiple_view_mode_list_set_icon_size_by_view_mode_changes_current_size(settings):
"""
Tests if MultipleViewModeList's set_icon_size_by_view_mode changes the current value if provided mode is the same
as current List's ViewMode.
"""
# GIVEN: a MultipleViewModeList instance and a custom icon size
list = MultipleViewModeList(None, QtWidgets.QListWidget.ViewMode.IconMode)
icon_size = QtCore.QSize(93, 37)
# WHEN: set_icon_size_by_view_mode is called with IconMode and mode is changed
list.set_icon_size_by_view_mode(QtWidgets.QListWidget.ViewMode.IconMode, icon_size)
# THEN: New icon size should be set successfully
assert list.iconSize() == icon_size
def test_multiple_view_mode_list_set_view_mode_preserves_last_icon_size(settings):
"""
Tests if MultipleViewModeList's preserves the last iconSize when no equivalent
default icon size is present
"""
# GIVEN: a MultipleViewModeList instance and a custom icon size
list = MultipleViewModeList(None, QtWidgets.QListWidget.ViewMode.IconMode)
icon_size = QtCore.QSize(93, 37)
last_icon_size = list.iconSize()
list.set_icon_size_by_view_mode(QtWidgets.QListWidget.ViewMode.ListMode, icon_size)
# WHEN: viewMode() is changed and reverted
list.setViewMode(QtWidgets.QListWidget.ViewMode.ListMode)
list.setViewMode(QtWidgets.QListWidget.ViewMode.IconMode)
# THEN: Old icon size should be restored
assert list.iconSize() == last_icon_size
def test_add_list_view_mode_items_to_toolbar_creates_items(settings):
"""
Tests if add_list_view_mode_items_to_toolbar creates the list view items.
"""
# GIVEN: an OpenLPToolbar
toolbar = OpenLPToolbar(None)
# WHEN: add_list_view_mode_items_to_toolbar is called
add_list_view_mode_items_to_toolbar(toolbar, MagicMock())
# THEN: Assert correct icons are created
assert isinstance(toolbar.actions['listView'], QtWidgets.QAction) is True
assert isinstance(toolbar.actions['gridView'], QtWidgets.QAction) is True
def test_add_list_view_mode_items_to_toolbar_click_calls_handlers(settings):
"""
Tests if add_list_view_mode_items_to_toolbar created items calls the handlers
"""
# GIVEN: an OpenLPToolbar with ListView items
toolbar = OpenLPToolbar(None)
handler = MagicMock()
add_list_view_mode_items_to_toolbar(toolbar, handler)
# WHEN: handler items is called
toolbar.actions['listView'].trigger()
toolbar.actions['gridView'].trigger()
# THEN: Assert correct handlers were called
handler.on_set_view_mode_list.assert_called_once()
handler.on_set_view_mode_grid.assert_called_once()
def test_set_list_view_mode_toolbar_state_view_mode_list_mode(settings):
"""
Tests if set_list_view_mode_toolbar_state changes the toolbar items state to List Mode correctly
"""
# GIVEN: an OpenLPToolbar with checkable List View items
toolbar = OpenLPToolbar(None)
add_list_view_mode_items_to_toolbar(toolbar, MagicMock())
# WHEN: set_list_view_mode_toolbar_state is called with ListMode
set_list_view_mode_toolbar_state(toolbar, QtWidgets.QListWidget.ViewMode.ListMode)
# THEN: Assert correct icons are checked or not
assert toolbar.actions['listView'].isChecked() is True
assert toolbar.actions['gridView'].isChecked() is False
def test_set_list_view_mode_toolbar_state_view_mode_icon_mode(settings):
"""
Tests if set_list_view_mode_toolbar_state changes the toolbar items state to Grid Mode correctly
"""
# GIVEN: an OpenLPToolbar with checkable List View items
toolbar = OpenLPToolbar(None)
add_list_view_mode_items_to_toolbar(toolbar, MagicMock())
# WHEN: set_list_view_mode_toolbar_state is called with ListMode
set_list_view_mode_toolbar_state(toolbar, QtWidgets.QListWidget.ViewMode.IconMode)
# THEN: Assert correct icons are checked or not
assert toolbar.actions['listView'].isChecked() is False
assert toolbar.actions['gridView'].isChecked() is True

View File

@ -492,6 +492,7 @@ def test_bootstrap_post(mocked_rename_form, mocked_theme_form, theme_manager):
Test the functions of bootstrap_post_setup are called.
"""
# GIVEN:
theme_manager.toolbar = MagicMock()
theme_manager.theme_path = MagicMock()
theme_manager.load_themes = MagicMock()
theme_manager.upgrade_themes = MagicMock()