forked from openlp/openlp
481 lines
21 KiB
Python
481 lines
21 KiB
Python
# -*- coding: utf-8 -*-
|
|
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
|
|
|
###############################################################################
|
|
# OpenLP - Open Source Lyrics Projection #
|
|
# --------------------------------------------------------------------------- #
|
|
# Copyright (c) 2008-2011 Raoul Snyman #
|
|
# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
|
|
# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
|
|
# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
|
|
# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
|
|
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
|
|
# --------------------------------------------------------------------------- #
|
|
# 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 #
|
|
###############################################################################
|
|
|
|
import logging
|
|
import re
|
|
|
|
from PyQt4 import QtCore, QtGui
|
|
|
|
from openlp.core.lib import Receiver
|
|
from openlp.core.utils import translate
|
|
from openlp.core.utils.actions import ActionList
|
|
from shortcutlistdialog import Ui_ShortcutListDialog
|
|
|
|
REMOVE_AMPERSAND = re.compile(r'&{1}')
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
|
|
"""
|
|
The shortcut list dialog
|
|
"""
|
|
|
|
def __init__(self, parent=None):
|
|
QtGui.QDialog.__init__(self, parent)
|
|
self.setupUi(self)
|
|
self.changedActions = {}
|
|
self.action_list = ActionList.get_instance()
|
|
QtCore.QObject.connect(self.primaryPushButton,
|
|
QtCore.SIGNAL(u'toggled(bool)'), self.onPrimaryPushButtonClicked)
|
|
QtCore.QObject.connect(self.alternatePushButton,
|
|
QtCore.SIGNAL(u'toggled(bool)'), self.onAlternatePushButtonClicked)
|
|
QtCore.QObject.connect(self.treeWidget, QtCore.SIGNAL(
|
|
u'currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'),
|
|
self.onCurrentItemChanged)
|
|
QtCore.QObject.connect(self.treeWidget,
|
|
QtCore.SIGNAL(u'itemDoubleClicked(QTreeWidgetItem*, int)'),
|
|
self.onItemDoubleClicked)
|
|
QtCore.QObject.connect(self.clearPrimaryButton,
|
|
QtCore.SIGNAL(u'clicked(bool)'), self.onClearPrimaryButtonClicked)
|
|
QtCore.QObject.connect(self.clearAlternateButton,
|
|
QtCore.SIGNAL(u'clicked(bool)'), self.onClearAlternateButtonClicked)
|
|
QtCore.QObject.connect(self.buttonBox,
|
|
QtCore.SIGNAL(u'clicked(QAbstractButton*)'),
|
|
self.onRestoreDefaultsClicked)
|
|
QtCore.QObject.connect(self.defaultRadioButton,
|
|
QtCore.SIGNAL(u'clicked(bool)'), self.onDefaultRadioButtonClicked)
|
|
QtCore.QObject.connect(self.customRadioButton,
|
|
QtCore.SIGNAL(u'clicked(bool)'), self.onCustomRadioButtonClicked)
|
|
|
|
def keyPressEvent(self, event):
|
|
if event.key() == QtCore.Qt.Key_Space:
|
|
self.keyReleaseEvent(event)
|
|
elif self.primaryPushButton.isChecked() or \
|
|
self.alternatePushButton.isChecked():
|
|
event.ignore()
|
|
elif event.key() == QtCore.Qt.Key_Escape:
|
|
event.accept()
|
|
self.close()
|
|
|
|
def keyReleaseEvent(self, event):
|
|
if not self.primaryPushButton.isChecked() and \
|
|
not self.alternatePushButton.isChecked():
|
|
return
|
|
key = event.key()
|
|
if key == QtCore.Qt.Key_Shift or key == QtCore.Qt.Key_Control or \
|
|
key == QtCore.Qt.Key_Meta or key == QtCore.Qt.Key_Alt:
|
|
return
|
|
key_string = QtGui.QKeySequence(key).toString()
|
|
if event.modifiers() & QtCore.Qt.ControlModifier == \
|
|
QtCore.Qt.ControlModifier:
|
|
key_string = u'Ctrl+' + key_string
|
|
if event.modifiers() & QtCore.Qt.AltModifier == QtCore.Qt.AltModifier:
|
|
key_string = u'Alt+' + key_string
|
|
if event.modifiers() & QtCore.Qt.ShiftModifier == \
|
|
QtCore.Qt.ShiftModifier:
|
|
key_string = u'Shift+' + key_string
|
|
if event.modifiers() & QtCore.Qt.MetaModifier == \
|
|
QtCore.Qt.MetaModifier:
|
|
key_string = u'Meta+' + key_string
|
|
key_sequence = QtGui.QKeySequence(key_string)
|
|
if self._validiate_shortcut(self._currentItemAction(), key_sequence):
|
|
if self.primaryPushButton.isChecked():
|
|
self._adjustButton(self.primaryPushButton,
|
|
False, text=key_sequence.toString())
|
|
elif self.alternatePushButton.isChecked():
|
|
self._adjustButton(self.alternatePushButton,
|
|
False, text=key_sequence.toString())
|
|
|
|
def exec_(self):
|
|
self.changedActions = {}
|
|
self.reloadShortcutList()
|
|
self._adjustButton(self.primaryPushButton, False, False, u'')
|
|
self._adjustButton(self.alternatePushButton, False, False, u'')
|
|
return QtGui.QDialog.exec_(self)
|
|
|
|
def reloadShortcutList(self):
|
|
"""
|
|
Reload the ``treeWidget`` list to add new and remove old actions.
|
|
"""
|
|
self.treeWidget.clear()
|
|
for category in self.action_list.categories:
|
|
# Check if the category is for internal use only.
|
|
if category.name is None:
|
|
continue
|
|
item = QtGui.QTreeWidgetItem([category.name])
|
|
for action in category.actions:
|
|
actionText = REMOVE_AMPERSAND.sub('', unicode(action.text()))
|
|
actionItem = QtGui.QTreeWidgetItem([actionText])
|
|
actionItem.setIcon(0, action.icon())
|
|
actionItem.setData(0,
|
|
QtCore.Qt.UserRole, QtCore.QVariant(action))
|
|
item.addChild(actionItem)
|
|
self.treeWidget.addTopLevelItem(item)
|
|
item.setExpanded(True)
|
|
self.refreshShortcutList()
|
|
|
|
def refreshShortcutList(self):
|
|
"""
|
|
This refreshes the item's shortcuts shown in the list. Note, this
|
|
neither adds new actions nor removes old actions.
|
|
"""
|
|
iterator = QtGui.QTreeWidgetItemIterator(self.treeWidget)
|
|
while iterator.value():
|
|
item = iterator.value()
|
|
iterator += 1
|
|
action = self._currentItemAction(item)
|
|
if action is None:
|
|
continue
|
|
shortcuts = self._actionShortcuts(action)
|
|
if len(shortcuts) == 0:
|
|
item.setText(1, u'')
|
|
item.setText(2, u'')
|
|
elif len(shortcuts) == 1:
|
|
item.setText(1, shortcuts[0].toString())
|
|
item.setText(2, u'')
|
|
else:
|
|
item.setText(1, shortcuts[0].toString())
|
|
item.setText(2, shortcuts[1].toString())
|
|
self.onCurrentItemChanged()
|
|
|
|
def onPrimaryPushButtonClicked(self, toggled):
|
|
"""
|
|
Save the new primary shortcut.
|
|
"""
|
|
self.customRadioButton.setChecked(True)
|
|
if toggled:
|
|
self.alternatePushButton.setChecked(False)
|
|
self.primaryPushButton.setText(u'')
|
|
return
|
|
action = self._currentItemAction()
|
|
if action is None:
|
|
return
|
|
shortcuts = self._actionShortcuts(action)
|
|
new_shortcuts = [QtGui.QKeySequence(self.primaryPushButton.text())]
|
|
if len(shortcuts) == 2:
|
|
new_shortcuts.append(shortcuts[1])
|
|
self.changedActions[action] = new_shortcuts
|
|
self.refreshShortcutList()
|
|
|
|
def onAlternatePushButtonClicked(self, toggled):
|
|
"""
|
|
Save the new alternate shortcut.
|
|
"""
|
|
self.customRadioButton.setChecked(True)
|
|
if toggled:
|
|
self.primaryPushButton.setChecked(False)
|
|
self.alternatePushButton.setText(u'')
|
|
return
|
|
action = self._currentItemAction()
|
|
if action is None:
|
|
return
|
|
shortcuts = self._actionShortcuts(action)
|
|
new_shortcuts = []
|
|
if len(shortcuts) != 0:
|
|
new_shortcuts.append(shortcuts[0])
|
|
new_shortcuts.append(
|
|
QtGui.QKeySequence(self.alternatePushButton.text()))
|
|
self.changedActions[action] = new_shortcuts
|
|
if not self.primaryPushButton.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.primaryPushButton.setText(self.alternatePushButton.text())
|
|
self.alternatePushButton.setText(u'')
|
|
self.refreshShortcutList()
|
|
|
|
def onItemDoubleClicked(self, item, column):
|
|
"""
|
|
A item has been double clicked. The ``primaryPushButton`` will be
|
|
checked and the item's shortcut will be displayed.
|
|
"""
|
|
action = self._currentItemAction(item)
|
|
if action is None:
|
|
return
|
|
self.primaryPushButton.setChecked(column in [0, 1])
|
|
self.alternatePushButton.setChecked(column not in [0, 1])
|
|
if column in [0, 1]:
|
|
self.primaryPushButton.setText(u'')
|
|
self.primaryPushButton.setFocus(QtCore.Qt.OtherFocusReason)
|
|
else:
|
|
self.alternatePushButton.setText(u'')
|
|
self.alternatePushButton.setFocus(QtCore.Qt.OtherFocusReason)
|
|
|
|
def onCurrentItemChanged(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._currentItemAction(item)
|
|
self.primaryPushButton.setEnabled(action is not None)
|
|
self.alternatePushButton.setEnabled(action is not None)
|
|
primary_text = u''
|
|
alternate_text = u''
|
|
primary_label_text = u''
|
|
alternate_label_text = u''
|
|
if action is None:
|
|
self.primaryPushButton.setChecked(False)
|
|
self.alternatePushButton.setChecked(False)
|
|
else:
|
|
if len(action.defaultShortcuts) != 0:
|
|
primary_label_text = action.defaultShortcuts[0].toString()
|
|
if len(action.defaultShortcuts) == 2:
|
|
alternate_label_text = action.defaultShortcuts[1].toString()
|
|
shortcuts = self._actionShortcuts(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.primaryPushButton.text()
|
|
alternate_text = self.alternatePushButton.text()
|
|
elif len(shortcuts) == 1:
|
|
primary_text = shortcuts[0].toString()
|
|
elif len(shortcuts) == 2:
|
|
primary_text = shortcuts[0].toString()
|
|
alternate_text = shortcuts[1].toString()
|
|
# When we are capturing a new shortcut, we do not want, the buttons to
|
|
# display the current shortcut.
|
|
if self.primaryPushButton.isChecked():
|
|
primary_text = u''
|
|
if self.alternatePushButton.isChecked():
|
|
alternate_text = u''
|
|
self.primaryPushButton.setText(primary_text)
|
|
self.alternatePushButton.setText(alternate_text)
|
|
self.primaryLabel.setText(primary_label_text)
|
|
self.alternateLabel.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.defaultRadioButton.toggle()
|
|
else:
|
|
self.customRadioButton.toggle()
|
|
|
|
def onRestoreDefaultsClicked(self, button):
|
|
"""
|
|
Restores all default shortcuts.
|
|
"""
|
|
if self.buttonBox.buttonRole(button) != \
|
|
QtGui.QDialogButtonBox.ResetRole:
|
|
return
|
|
if QtGui.QMessageBox.question(self,
|
|
translate('OpenLP.ShortcutListDialog', 'Restore Default Shortcuts'),
|
|
translate('OpenLP.ShortcutListDialog', 'Do you want to restore all '
|
|
'shortcuts to their defaults?'), QtGui.QMessageBox.StandardButtons(
|
|
QtGui.QMessageBox.Yes |
|
|
QtGui.QMessageBox.No)) == QtGui.QMessageBox.No:
|
|
return
|
|
self._adjustButton(self.primaryPushButton, False, text=u'')
|
|
self._adjustButton(self.alternatePushButton, False, text=u'')
|
|
for category in self.action_list.categories:
|
|
for action in category.actions:
|
|
self.changedActions[action] = action.defaultShortcuts
|
|
self.refreshShortcutList()
|
|
|
|
def onDefaultRadioButtonClicked(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._currentItemAction()
|
|
if action is None:
|
|
return
|
|
temp_shortcuts = self._actionShortcuts(action)
|
|
self.changedActions[action] = action.defaultShortcuts
|
|
self.refreshShortcutList()
|
|
primary_button_text = u''
|
|
alternate_button_text = u''
|
|
if len(temp_shortcuts) != 0:
|
|
primary_button_text = temp_shortcuts[0].toString()
|
|
if len(temp_shortcuts) == 2:
|
|
alternate_button_text = temp_shortcuts[1].toString()
|
|
self.primaryPushButton.setText(primary_button_text)
|
|
self.alternatePushButton.setText(alternate_button_text)
|
|
|
|
def onCustomRadioButtonClicked(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
|
|
self.onPrimaryPushButtonClicked(False)
|
|
self.onAlternatePushButtonClicked(False)
|
|
self.refreshShortcutList()
|
|
|
|
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 = QtCore.QSettings()
|
|
settings.beginGroup(u'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 self.changedActions .has_key(action):
|
|
action.setShortcuts(self.changedActions[action])
|
|
settings.setValue(
|
|
action.objectName(), QtCore.QVariant(action.shortcuts()))
|
|
settings.endGroup()
|
|
|
|
def onClearPrimaryButtonClicked(self, toggled):
|
|
"""
|
|
Restore the defaults of this action.
|
|
"""
|
|
self.primaryPushButton.setChecked(False)
|
|
action = self._currentItemAction()
|
|
if action is None:
|
|
return
|
|
shortcuts = self._actionShortcuts(action)
|
|
new_shortcuts = []
|
|
if len(action.defaultShortcuts) != 0:
|
|
new_shortcuts.append(action.defaultShortcuts[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.changedActions[action] = new_shortcuts
|
|
self.refreshShortcutList()
|
|
self.onCurrentItemChanged(self.treeWidget.currentItem())
|
|
|
|
def onClearAlternateButtonClicked(self, toggled):
|
|
"""
|
|
Restore the defaults of this action.
|
|
"""
|
|
self.alternatePushButton.setChecked(False)
|
|
action = self._currentItemAction()
|
|
if action is None:
|
|
return
|
|
shortcuts = self._actionShortcuts(action)
|
|
new_shortcuts = []
|
|
if len(shortcuts) != 0:
|
|
new_shortcuts.append(shortcuts[0])
|
|
if len(action.defaultShortcuts) == 2:
|
|
new_shortcuts.append(action.defaultShortcuts[1])
|
|
if len(new_shortcuts) == 2:
|
|
if not self._validiate_shortcut(action, new_shortcuts[1]):
|
|
return
|
|
self.changedActions[action] = new_shortcuts
|
|
self.refreshShortcutList()
|
|
self.onCurrentItemChanged(self.treeWidget.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``.
|
|
|
|
``changing_action``
|
|
The action which wants to use the ``key_sequence``.
|
|
|
|
``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._actionShortcuts(action)
|
|
if key_sequence not in shortcuts:
|
|
continue
|
|
if action is changing_action:
|
|
if self.primaryPushButton.isChecked() and \
|
|
shortcuts.index(key_sequence) == 0:
|
|
continue
|
|
if self.alternatePushButton.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
|
|
# vaild, 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:
|
|
Receiver.send_message(u'openlp_warning_message', {
|
|
u'title': translate('OpenLP.ShortcutListDialog',
|
|
'Duplicate Shortcut'),
|
|
u'message': unicode(translate('OpenLP.ShortcutListDialog',
|
|
'The shortcut "%s" is already assigned to another action, '
|
|
'please use a different shortcut.')) % key_sequence.toString()
|
|
})
|
|
return is_valid
|
|
|
|
def _actionShortcuts(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 self.changedActions.has_key(action):
|
|
return self.changedActions[action]
|
|
return action.shortcuts()
|
|
|
|
def _currentItemAction(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 ``treeWidget``.
|
|
"""
|
|
if item is None:
|
|
item = self.treeWidget.currentItem()
|
|
if item is None:
|
|
return
|
|
return item.data(0, QtCore.Qt.UserRole).toPyObject()
|
|
|
|
def _adjustButton(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)
|