diff --git a/src/calibre/gui2/shortcuts.py b/src/calibre/gui2/shortcuts.py deleted file mode 100644 index e8390cef09..0000000000 --- a/src/calibre/gui2/shortcuts.py +++ /dev/null @@ -1,320 +0,0 @@ -#!/usr/bin/env python - - -__license__ = 'GPL v3' -__copyright__ = '2009, Kovid Goyal ' -__docformat__ = 'restructuredtext en' - -from functools import partial - -from qt.core import ( - QAbstractListModel, Qt, QKeySequence, QListView, QVBoxLayout, QLabel, QAbstractItemView, - QHBoxLayout, QWidget, QApplication, QStyledItemDelegate, QStyle, QIcon, QAbstractItemDelegate, - QTextDocument, QRectF, QFrame, QSize, QFont, QKeyEvent, QRadioButton, QPushButton, QToolButton, QEvent -) - -from calibre.gui2 import error_dialog -from calibre.utils.config import XMLConfig -from calibre.utils.icu import sort_key - -DEFAULTS = Qt.ItemDataRole.UserRole -DESCRIPTION = Qt.ItemDataRole.UserRole + 1 -CUSTOM = Qt.ItemDataRole.UserRole + 2 -KEY = Qt.ItemDataRole.UserRole + 3 - - -class Customize(QFrame): - - def __init__(self, index, dup_check, parent=None): - QFrame.__init__(self, parent) - self.setFrameShape(QFrame.Shape.StyledPanel) - self.setFrameShadow(QFrame.Shadow.Raised) - self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) - self.setAutoFillBackground(True) - self.l = l = QVBoxLayout(self) - self.header = la = QLabel(self) - la.setWordWrap(True) - l.addWidget(la) - self.default_shortcuts = QRadioButton(_("&Default"), self) - self.custom = QRadioButton(_("&Custom"), self) - self.custom.toggled.connect(self.custom_toggled) - l.addWidget(self.default_shortcuts) - l.addWidget(self.custom) - for which in 1, 2: - la = QLabel(_("&Shortcut:") if which == 1 else _("&Alternate shortcut:")) - setattr(self, 'label%d' % which, la) - h = QHBoxLayout() - l.addLayout(h) - h.setContentsMargins(25, -1, -1, -1) - h.addWidget(la) - b = QPushButton(_("Click to change"), self) - la.setBuddy(b) - b.clicked.connect(partial(self.capture_clicked, which=which)) - b.installEventFilter(self) - setattr(self, 'button%d' % which, b) - h.addWidget(b) - c = QToolButton(self) - c.setIcon(QIcon(I('clear_left.png'))) - c.setToolTip(_('Clear')) - h.addWidget(c) - c.clicked.connect(partial(self.clear_clicked, which=which)) - setattr(self, 'clear%d' % which, c) - self.data_model = index.model() - self.capture = 0 - self.key = None - self.shorcut1 = self.shortcut2 = None - self.dup_check = dup_check - self.custom_toggled(False) - - def eventFilter(self, obj, event): - if self.capture == 0 or obj not in (self.button1, self.button2): - return QFrame.eventFilter(self, obj, event) - t = event.type() - if t == QEvent.Type.ShortcutOverride: - event.accept() - return True - if t == QEvent.Type.KeyPress: - self.key_press_event(event, 1 if obj is self.button1 else 2) - return True - return QFrame.eventFilter(self, obj, event) - - def clear_button(self, which): - b = getattr(self, 'button%d' % which) - s = getattr(self, 'shortcut%d' % which, None) - b.setText(_('None') if s is None else s.toString(QKeySequence.SequenceFormat.NativeText)) - b.setFont(QFont()) - - def clear_clicked(self, which=0): - setattr(self, 'shortcut%d'%which, None) - self.clear_button(which) - - def custom_toggled(self, checked): - for w in ('1', '2'): - for o in ('label', 'button', 'clear'): - getattr(self, o+w).setEnabled(checked) - - def capture_clicked(self, which=1): - self.capture = which - for w in 1, 2: - self.clear_button(w) - button = getattr(self, 'button%d'%which) - button.setText(_('Press a key...')) - button.setFocus(Qt.FocusReason.OtherFocusReason) - font = QFont() - font.setBold(True) - button.setFont(font) - - def key_press_event(self, ev, which=0): - code = ev.key() - if self.capture == 0 or code in (0, Qt.Key.Key_unknown, - Qt.Key.Key_Shift, Qt.Key.Key_Control, Qt.Key.Key_Alt, Qt.Key.Key_Meta, - Qt.Key.Key_AltGr, Qt.Key.Key_CapsLock, Qt.Key.Key_NumLock, Qt.Key.Key_ScrollLock): - return QWidget.keyPressEvent(self, ev) - sequence = QKeySequence(code|(int(ev.modifiers()) & (~Qt.KeyboardModifier.KeypadModifier))) - setattr(self, 'shortcut%d'%which, sequence) - self.clear_button(which) - self.capture = 0 - dup_desc = self.dup_check(sequence, self.key) - if dup_desc is not None: - error_dialog(self, _('Already assigned'), - str(sequence.toString(QKeySequence.SequenceFormat.NativeText)) + ' ' + - _('already assigned to') + ' ' + dup_desc, show=True) - self.clear_clicked(which=which) - - -class Delegate(QStyledItemDelegate): - - def __init__(self, parent=None): - QStyledItemDelegate.__init__(self, parent) - self.editing_indices = {} - self.closeEditor.connect(self.editing_done) - - def to_doc(self, index): - doc = QTextDocument() - doc.setHtml(index.data()) - return doc - - def editing_done(self, editor, hint): - remove = None - for row, w in self.editing_indices.items(): - remove = (row, w.data_model.index(row)) - if remove is not None: - self.editing_indices.pop(remove[0]) - self.sizeHintChanged.emit(remove[1]) - - def sizeHint(self, option, index): - if index.row() in self.editing_indices: - return QSize(200, 200) - ans = self.to_doc(index).size().toSize() - ans.setHeight(ans.height()+10) - return ans - - def paint(self, painter, option, index): - painter.save() - painter.setClipRect(QRectF(option.rect)) - if hasattr(QStyle, 'CE_ItemViewItem'): - QApplication.style().drawControl(QStyle.ControlElement.CE_ItemViewItem, option, painter) - elif option.state & QStyle.StateFlag.State_Selected: - painter.fillRect(option.rect, option.palette.highlight()) - painter.translate(option.rect.topLeft()) - self.to_doc(index).drawContents(painter) - painter.restore() - - def createEditor(self, parent, option, index): - w = Customize(index, index.model().duplicate_check, parent=parent) - self.editing_indices[index.row()] = w - self.sizeHintChanged.emit(index) - return w - - def setEditorData(self, editor, index): - defs = index.data(DEFAULTS) - defs = _(' or ').join([str(x.toString(QKeySequence.SequenceFormat.NativeText)) for x in defs]) - editor.key = str(index.data(KEY)) - editor.default_shortcuts.setText(_('&Default') + ': %s' % defs) - editor.default_shortcuts.setChecked(True) - editor.header.setText('%s: %s'%(_('Customize shortcuts for'), - str(index.data(DESCRIPTION)))) - custom = index.data(CUSTOM) - if custom: - editor.custom.setChecked(True) - for x in (0, 1): - button = getattr(editor, 'button%d'%(x+1)) - if len(custom) > x: - seq = QKeySequence(custom[x]) - button.setText(seq.toString(QKeySequence.SequenceFormat.NativeText)) - setattr(editor, 'shortcut%d'%(x+1), seq) - - def setModelData(self, editor, model, index): - self.closeEditor.emit(editor, QAbstractItemDelegate.EndEditHint.NoHint) - custom = [] - if editor.custom.isChecked(): - for x in ('1', '2'): - sc = getattr(editor, 'shortcut'+x, None) - if sc is not None: - custom.append(sc) - - model.set_data(index, custom) - - def updateEditorGeometry(self, editor, option, index): - editor.setGeometry(option.rect) - - -class Shortcuts(QAbstractListModel): - - TEMPLATE = ''' -

{0}
- {2}: {1}

- ''' - - def __init__(self, shortcuts, config_file_base_name, parent=None): - QAbstractListModel.__init__(self, parent) - - self.descriptions = {} - for k, v in shortcuts.items(): - self.descriptions[k] = v[-1] - self.keys = {} - for k, v in shortcuts.items(): - self.keys[k] = v[0] - self.order = list(shortcuts) - self.order.sort(key=lambda x : sort_key(self.descriptions[x])) - self.sequences = {} - for k, v in self.keys.items(): - self.sequences[k] = [QKeySequence(x) for x in v] - - self.custom = XMLConfig(config_file_base_name) - - def rowCount(self, parent): - return len(self.order) - - def get_sequences(self, key): - custom = self.custom.get(key, []) - if custom: - return [QKeySequence(x) for x in custom] - return self.sequences[key] - - def get_match(self, event_or_sequence, ignore=tuple()): - q = event_or_sequence - if isinstance(q, QKeyEvent): - q = QKeySequence(q.key()|(int(q.modifiers()) & (~Qt.KeyboardModifier.KeypadModifier))) - for key in self.order: - if key not in ignore: - for seq in self.get_sequences(key): - if seq.matches(q) == QKeySequence.SequenceMatch.ExactMatch: - return key - return None - - def duplicate_check(self, seq, ignore): - key = self.get_match(seq, ignore=[ignore]) - if key is not None: - return self.descriptions[key] - - def get_shortcuts(self, key): - return [str(x.toString(QKeySequence.SequenceFormat.NativeText)) for x in - self.get_sequences(key)] - - def data(self, index, role): - row = index.row() - if row < 0 or row >= len(self.order): - return None - key = self.order[row] - if role == Qt.ItemDataRole.DisplayRole: - return self.TEMPLATE.format(self.descriptions[key], - _(' or ').join(self.get_shortcuts(key)), _('Keys')) - if role == Qt.ItemDataRole.ToolTipRole: - return _('Double click to change') - if role == DEFAULTS: - return self.sequences[key] - if role == DESCRIPTION: - return self.descriptions[key] - if role == CUSTOM: - if key in self.custom: - return self.custom[key] - else: - return [] - if role == KEY: - return key - return None - - def set_data(self, index, custom): - key = self.order[index.row()] - if custom: - self.custom[key] = [str(x.toString(QKeySequence.SequenceFormat.PortableText)) for x in custom] - elif key in self.custom: - del self.custom[key] - - def flags(self, index): - if not index.isValid(): - return Qt.ItemFlag.ItemIsEnabled - return QAbstractListModel.flags(self, index) | Qt.ItemFlag.ItemIsEditable - - -class ShortcutConfig(QWidget): - - def __init__(self, model, parent=None): - QWidget.__init__(self, parent) - self._layout = QHBoxLayout() - self.setLayout(self._layout) - self.view = QListView(self) - self._layout.addWidget(self.view) - self.view.setModel(model) - self.delegate = Delegate() - self.view.setItemDelegate(self.delegate) - self.delegate.sizeHintChanged.connect(self.scrollTo, - type=Qt.ConnectionType.QueuedConnection) - - def scrollTo(self, index): - self.view.scrollTo(index, QAbstractItemView.ScrollHint.EnsureVisible) - - @property - def is_editing(self): - return self.view.state() == QAbstractItemView.State.EditingState - - -if __name__ == '__main__': - app = QApplication([]) - from calibre.gui2.viewer.keys import SHORTCUTS - model = Shortcuts(SHORTCUTS, 'shortcuts/viewer') - conf = ShortcutConfig(model) - conf.resize(400, 500) - conf.show() - app.exec()