Source code for openlp.core.ui.shortcutlistform

# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4

###############################################################################
# OpenLP - Open Source Lyrics Projection                                      #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers                                   #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it     #
# under the terms of the GNU General Public License as published by the Free  #
# Software Foundation; version 2 of the License.                              #
#                                                                             #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
# more details.                                                               #
#                                                                             #
# You should have received a copy of the GNU General Public License along     #
# with this program; if not, write to the Free Software Foundation, Inc., 59  #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
###############################################################################
"""
The :mod:`~openlp.core.ui.shortcutlistform` module contains the form class"""
import logging
import re

from PyQt5 import QtCore, QtGui, QtWidgets

from openlp.core.common import RegistryProperties, Settings, translate
from openlp.core.common.actions import ActionList
from .shortcutlistdialog import Ui_ShortcutListDialog

REMOVE_AMPERSAND = re.compile(r'&{1}')

log = logging.getLogger(__name__)


[docs]class ShortcutListForm(QtWidgets.QDialog, Ui_ShortcutListDialog, RegistryProperties): """ The shortcut list dialog """ def __init__(self, parent=None): """ Constructor """ super(ShortcutListForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) self.setupUi(self) self.changed_actions = {} self.action_list = ActionList.get_instance() self.dialog_was_shown = False self.primary_push_button.toggled.connect(self.on_primary_push_button_clicked) self.alternate_push_button.toggled.connect(self.on_alternate_push_button_clicked) self.tree_widget.currentItemChanged.connect(self.on_current_item_changed) self.tree_widget.itemDoubleClicked.connect(self.on_item_double_clicked) self.clear_primary_button.clicked.connect(self.on_clear_primary_button_clicked) self.clear_alternate_button.clicked.connect(self.on_clear_alternate_button_clicked) self.button_box.clicked.connect(self.on_restore_defaults_clicked) self.default_radio_button.clicked.connect(self.on_default_radio_button_clicked) self.custom_radio_button.clicked.connect(self.on_custom_radio_button_clicked)
[docs] def keyPressEvent(self, event): """ Respond to certain key presses """ if event.key() == QtCore.Qt.Key_Space: self.keyReleaseEvent(event) elif self.primary_push_button.isChecked() or self.alternate_push_button.isChecked(): self.keyReleaseEvent(event) elif event.key() == QtCore.Qt.Key_Escape: event.accept() self.close()
[docs] def keyReleaseEvent(self, event): """ Respond to certain key presses """ if not self.primary_push_button.isChecked() and not self.alternate_push_button.isChecked(): return # Do not continue, as the event is for the dialog (close it). if self.dialog_was_shown and event.key() in (QtCore.Qt.Key_Escape, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return): self.dialog_was_shown = False return key = event.key() if key in (QtCore.Qt.Key_Shift, QtCore.Qt.Key_Control, QtCore.Qt.Key_Meta, QtCore.Qt.Key_Alt): return key_string = QtGui.QKeySequence(key).toString() if event.modifiers() & QtCore.Qt.ControlModifier == QtCore.Qt.ControlModifier: key_string = 'Ctrl+' + key_string if event.modifiers() & QtCore.Qt.AltModifier == QtCore.Qt.AltModifier: key_string = 'Alt+' + key_string if event.modifiers() & QtCore.Qt.ShiftModifier == QtCore.Qt.ShiftModifier: key_string = 'Shift+' + key_string if event.modifiers() & QtCore.Qt.MetaModifier == QtCore.Qt.MetaModifier: key_string = 'Meta+' + key_string key_sequence = QtGui.QKeySequence(key_string) if self._validiate_shortcut(self._current_item_action(), key_sequence): if self.primary_push_button.isChecked(): self._adjust_button(self.primary_push_button, False, text=self.get_shortcut_string(key_sequence, for_display=True)) elif self.alternate_push_button.isChecked(): self._adjust_button(self.alternate_push_button, False, text=self.get_shortcut_string(key_sequence, for_display=True))
[docs] def exec(self): """ Execute the dialog """ self.changed_actions = {} self.reload_shortcut_list() self._adjust_button(self.primary_push_button, False, False, '') self._adjust_button(self.alternate_push_button, False, False, '') return QtWidgets.QDialog.exec(self)
[docs] def reload_shortcut_list(self): """ Reload the ``tree_widget`` list to add new and remove old actions. """ self.tree_widget.clear() for category in self.action_list.categories: # Check if the category is for internal use only. if category.name is None: continue item = QtWidgets.QTreeWidgetItem([category.name]) for action in category.actions: action_text = REMOVE_AMPERSAND.sub('', action.text()) action_item = QtWidgets.QTreeWidgetItem([action_text]) action_item.setIcon(0, action.icon()) action_item.setData(0, QtCore.Qt.UserRole, action) tool_tip_text = action.toolTip() # Only display tool tips if they are helpful. if tool_tip_text != action_text: # Display the tool tip in all three colums. action_item.setToolTip(0, tool_tip_text) action_item.setToolTip(1, tool_tip_text) action_item.setToolTip(2, tool_tip_text) item.addChild(action_item) self.tree_widget.addTopLevelItem(item) item.setExpanded(True) self.refresh_shortcut_list()
[docs] def refresh_shortcut_list(self): """ This refreshes the item's shortcuts shown in the list. Note, this neither adds new actions nor removes old actions. """ iterator = QtWidgets.QTreeWidgetItemIterator(self.tree_widget) while iterator.value(): item = iterator.value() iterator += 1 action = self._current_item_action(item) if action is None: continue shortcuts = self._action_shortcuts(action) if not shortcuts: item.setText(1, '') item.setText(2, '') elif len(shortcuts) == 1: item.setText(1, self.get_shortcut_string(shortcuts[0], for_display=True)) item.setText(2, '') else: item.setText(1, self.get_shortcut_string(shortcuts[0], for_display=True)) item.setText(2, self.get_shortcut_string(shortcuts[1], for_display=True)) self.on_current_item_changed()
[docs] def on_primary_push_button_clicked(self, toggled): """ Save the new primary shortcut. """ self.custom_radio_button.setChecked(True) if toggled: self.alternate_push_button.setChecked(False) self.primary_push_button.setText('') return action = self._current_item_action() if action is None: return shortcuts = self._action_shortcuts(action) new_shortcuts = [QtGui.QKeySequence(self.primary_push_button.text())] if len(shortcuts) == 2: new_shortcuts.append(shortcuts[1]) self.changed_actions[action] = new_shortcuts self.refresh_shortcut_list()
[docs] def on_alternate_push_button_clicked(self, toggled): """ Save the new alternate shortcut. """ self.custom_radio_button.setChecked(True) if toggled: self.primary_push_button.setChecked(False) self.alternate_push_button.setText('') return action = self._current_item_action() if action is None: return shortcuts = self._action_shortcuts(action) new_shortcuts = [] if shortcuts: new_shortcuts.append(shortcuts[0]) new_shortcuts.append(QtGui.QKeySequence(self.alternate_push_button.text())) self.changed_actions[action] = new_shortcuts if not self.primary_push_button.text(): # When we do not have a primary shortcut, the just entered alternate shortcut will automatically become the # primary shortcut. That is why we have to adjust the primary button's text. self.primary_push_button.setText(self.alternate_push_button.text()) self.alternate_push_button.setText('') self.refresh_shortcut_list()
[docs] def on_item_double_clicked(self, item, column): """ A item has been double clicked. The ``primaryPushButton`` will be checked and the item's shortcut will be displayed. """ action = self._current_item_action(item) if action is None: return self.primary_push_button.setChecked(column in [0, 1]) self.alternate_push_button.setChecked(column not in [0, 1]) if column in [0, 1]: self.primary_push_button.setText('') self.primary_push_button.setFocus() else: self.alternate_push_button.setText('') self.alternate_push_button.setFocus()
[docs] def on_current_item_changed(self, item=None, previousItem=None): """ A item has been pressed. We adjust the button's text to the action's shortcut which is encapsulate in the item. """ action = self._current_item_action(item) self.primary_push_button.setEnabled(action is not None) self.alternate_push_button.setEnabled(action is not None) primary_text = '' alternate_text = '' primary_label_text = '' alternate_label_text = '' if action is None: self.primary_push_button.setChecked(False) self.alternate_push_button.setChecked(False) else: if action.default_shortcuts: primary_label_text = self.get_shortcut_string(action.default_shortcuts[0], for_display=True) if len(action.default_shortcuts) == 2: alternate_label_text = self.get_shortcut_string(action.default_shortcuts[1], for_display=True) shortcuts = self._action_shortcuts(action) # We do not want to loose pending changes, that is why we have to keep the text when, this function has not # been triggered by a signal. if item is None: primary_text = self.primary_push_button.text() alternate_text = self.alternate_push_button.text() elif len(shortcuts) == 1: primary_text = self.get_shortcut_string(shortcuts[0], for_display=True) elif len(shortcuts) == 2: primary_text = self.get_shortcut_string(shortcuts[0], for_display=True) alternate_text = self.get_shortcut_string(shortcuts[1], for_display=True) # When we are capturing a new shortcut, we do not want, the buttons to display the current shortcut. if self.primary_push_button.isChecked(): primary_text = '' if self.alternate_push_button.isChecked(): alternate_text = '' self.primary_push_button.setText(primary_text) self.alternate_push_button.setText(alternate_text) self.primary_label.setText(primary_label_text) self.alternate_label.setText(alternate_label_text) # We do not want to toggle and radio button, as the function has not been triggered by a signal. if item is None: return if primary_label_text == primary_text and alternate_label_text == alternate_text: self.default_radio_button.toggle() else: self.custom_radio_button.toggle()
[docs] def on_restore_defaults_clicked(self, button): """ Restores all default shortcuts. """ if self.button_box.buttonRole(button) != QtWidgets.QDialogButtonBox.ResetRole: return if QtWidgets.QMessageBox.question(self, translate('OpenLP.ShortcutListDialog', 'Restore Default Shortcuts'), translate('OpenLP.ShortcutListDialog', 'Do you want to restore all ' 'shortcuts to their defaults?'), QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) ) == QtWidgets.QMessageBox.No: return self._adjust_button(self.primary_push_button, False, text='') self._adjust_button(self.alternate_push_button, False, text='') for category in self.action_list.categories: for action in category.actions: self.changed_actions[action] = action.default_shortcuts self.refresh_shortcut_list()
[docs] def on_default_radio_button_clicked(self, toggled): """ The default radio button has been clicked, which means we have to make sure, that we use the default shortcuts for the action. """ if not toggled: return action = self._current_item_action() if action is None: return temp_shortcuts = self._action_shortcuts(action) self.changed_actions[action] = action.default_shortcuts self.refresh_shortcut_list() primary_button_text = '' alternate_button_text = '' if temp_shortcuts: primary_button_text = self.get_shortcut_string(temp_shortcuts[0], for_display=True) if len(temp_shortcuts) == 2: alternate_button_text = self.get_shortcut_string(temp_shortcuts[1], for_display=True) self.primary_push_button.setText(primary_button_text) self.alternate_push_button.setText(alternate_button_text)
[docs] def on_custom_radio_button_clicked(self, toggled): """ The custom shortcut radio button was clicked, thus we have to restore the custom shortcuts by calling those functions triggered by button clicks. """ if not toggled: return action = self._current_item_action() shortcuts = self._action_shortcuts(action) self.refresh_shortcut_list() primary_button_text = '' alternate_button_text = '' if shortcuts: primary_button_text = self.get_shortcut_string(shortcuts[0], for_display=True) if len(shortcuts) == 2: alternate_button_text = self.get_shortcut_string(shortcuts[1], for_display=True) self.primary_push_button.setText(primary_button_text) self.alternate_push_button.setText(alternate_button_text)
[docs] def save(self): """ Save the shortcuts. **Note**, that we do not have to load the shortcuts, as they are loaded in :class:`~openlp.core.utils.ActionList`. """ settings = Settings() settings.beginGroup('shortcuts') for category in self.action_list.categories: # Check if the category is for internal use only. if category.name is None: continue for action in category.actions: if action in self.changed_actions: old_shortcuts = list(map(self.get_shortcut_string, action.shortcuts())) action.setShortcuts(self.changed_actions[action]) self.action_list.update_shortcut_map(action, old_shortcuts) settings.setValue(action.objectName(), action.shortcuts()) settings.endGroup()
[docs] def on_clear_primary_button_clicked(self, toggled): """ Restore the defaults of this action. """ self.primary_push_button.setChecked(False) action = self._current_item_action() if action is None: return shortcuts = self._action_shortcuts(action) new_shortcuts = [] if action.default_shortcuts: new_shortcuts.append(action.default_shortcuts[0]) # We have to check if the primary default shortcut is available. But we only have to check, if the action # has a default primary shortcut (an "empty" shortcut is always valid and if the action does not have a # default primary shortcut, then the alternative shortcut (not the default one) will become primary # shortcut, thus the check will assume that an action were going to have the same shortcut twice. if not self._validiate_shortcut(action, new_shortcuts[0]) and new_shortcuts[0] != shortcuts[0]: return if len(shortcuts) == 2: new_shortcuts.append(shortcuts[1]) self.changed_actions[action] = new_shortcuts self.refresh_shortcut_list() self.on_current_item_changed(self.tree_widget.currentItem())
[docs] def on_clear_alternate_button_clicked(self, toggled): """ Restore the defaults of this action. """ self.alternate_push_button.setChecked(False) action = self._current_item_action() if action is None: return shortcuts = self._action_shortcuts(action) new_shortcuts = [] if shortcuts: new_shortcuts.append(shortcuts[0]) if len(action.default_shortcuts) == 2: new_shortcuts.append(action.default_shortcuts[1]) if len(new_shortcuts) == 2: if not self._validiate_shortcut(action, new_shortcuts[1]): return self.changed_actions[action] = new_shortcuts self.refresh_shortcut_list() self.on_current_item_changed(self.tree_widget.currentItem())
def _validiate_shortcut(self, changing_action, key_sequence): """ Checks if the given ``changing_action `` can use the given ``key_sequence``. Returns ``True`` if the ``key_sequence`` can be used by the action, otherwise displays a dialog and returns ``False``. :param changing_action: The action which wants to use the ``key_sequence``. :param key_sequence: The key sequence which the action want so use. """ is_valid = True for category in self.action_list.categories: for action in category.actions: shortcuts = self._action_shortcuts(action) if key_sequence not in shortcuts: continue if action is changing_action: if self.primary_push_button.isChecked() and shortcuts.index(key_sequence) == 0: continue if self.alternate_push_button.isChecked() and shortcuts.index(key_sequence) == 1: continue # Have the same parent, thus they cannot have the same shortcut. if action.parent() is changing_action.parent(): is_valid = False # The new shortcut is already assigned, but if both shortcuts are only valid in a different widget the # new shortcut is valid, because they will not interfere. if action.shortcutContext() in [QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]: is_valid = False if changing_action.shortcutContext() in [QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]: is_valid = False if not is_valid: text = translate('OpenLP.ShortcutListDialog', 'The shortcut "{key}" is already assigned to another action,\n' 'please use a different shortcut.' ).format(key=self.get_shortcut_string(key_sequence)) self.main_window.warning_message(translate('OpenLP.ShortcutListDialog', 'Duplicate Shortcut'), text) self.dialog_was_shown = True return is_valid def _action_shortcuts(self, action): """ This returns the shortcuts for the given ``action``, which also includes those shortcuts which are not saved yet but already assigned (as changes are applied when closing the dialog). """ if action in self.changed_actions: return self.changed_actions[action] return action.shortcuts() def _current_item_action(self, item=None): """ Returns the action of the given ``item``. If no item is given, we return the action of the current item of the ``tree_widget``. """ if item is None: item = self.tree_widget.currentItem() if item is None: return return item.data(0, QtCore.Qt.UserRole) def _adjust_button(self, button, checked=None, enabled=None, text=None): """ Can be called to adjust more properties of the given ``button`` at once. """ # Set the text before checking the button, because this emits a signal. if text is not None: button.setText(text) if checked is not None: button.setChecked(checked) if enabled is not None: button.setEnabled(enabled)
[docs] @staticmethod def get_shortcut_string(shortcut, for_display=False): if for_display: if any(modifier in shortcut.toString() for modifier in ['Ctrl', 'Alt', 'Meta', 'Shift']): sequence_format = QtGui.QKeySequence.NativeText else: sequence_format = QtGui.QKeySequence.PortableText else: sequence_format = QtGui.QKeySequence.PortableText return shortcut.toString(sequence_format)