1
# -*- coding: utf-8 -*-
3
# Copyright © 2010 Pierre Raybaut
4
# Licensed under the terms of the MIT License
5
# (see spyderlib/__init__.py for details)
7
"""Shortcut management"""
9
from spyderlib.qt.QtGui import (QVBoxLayout, QComboBox, QItemDelegate,
10
QTableView, QMessageBox, QPushButton)
11
from spyderlib.qt.QtCore import (Qt, QSize, QAbstractTableModel, QModelIndex,
13
from spyderlib.qt.compat import to_qvariant, from_qvariant
18
from spyderlib.baseconfig import _
19
from spyderlib.config import (get_icon, get_shortcut, set_shortcut,
20
iter_shortcuts, reset_shortcuts)
21
from spyderlib.plugins.configdialog import GeneralConfigPage
24
KEYSTRINGS = ["Escape", "Tab", "Backtab", "Backspace", "Return", "Enter",
25
"Delete", "Pause", "Print", "Clear", "Home", "End", "Left",
26
"Up", "Right", "Down", "PageUp", "PageDown"] + \
27
["F%d" % _i for _i in range(1, 36)] + \
28
["Space", "Exclam", "QuoteDbl", "NumberSign", "Dollar", "Percent",
29
"Ampersand", "Apostrophe", "ParenLeft", "ParenRight", "Asterisk",
30
"Plus", "Comma", "Minus", "Period", "Slash"] + \
31
[str(_i) for _i in range(10)] + \
32
["Colon", "Semicolon", "Less", "Equal", "Greater", "Question",
33
"At"] + [chr(_i) for _i in range(65, 91)] + \
34
["BracketLeft", "Backslash", "BracketRight", "Underscore"]
38
MODIFIERS = {Qt.NoModifier: "", Qt.ShiftModifier: "Shift",
39
Qt.ControlModifier: "Ctrl", Qt.AltModifier: "Alt",
40
Qt.MetaModifier: "Meta"}
41
if sys.platform == 'darwin':
42
MODIFIERNAMES = {Qt.NoModifier: "", Qt.ShiftModifier: "Shift",
43
Qt.ControlModifier: "Cmd", Qt.AltModifier: "Alt",
44
Qt.MetaModifier: "Ctrl"}
45
elif sys.platform == 'win32':
46
MODIFIERNAMES = {Qt.NoModifier: "", Qt.ShiftModifier: "Shift",
47
Qt.ControlModifier: "Ctrl", Qt.AltModifier: "Alt",
48
Qt.MetaModifier: "Win"}
50
MODIFIERNAMES = {Qt.NoModifier: "", Qt.ShiftModifier: "Shift",
51
Qt.ControlModifier: "Ctrl", Qt.AltModifier: "Alt",
52
Qt.MetaModifier: "Meta"}
54
for attr in KEYSTRINGS:
55
KEYS[getattr(Qt, "Key_"+attr)] = attr
57
def __init__(self, key, mod1=Qt.NoModifier, mod2=Qt.NoModifier,
59
modifiers = [mod1, mod2, mod3]
60
assert all([mod in self.MODIFIERS for mod in modifiers])
61
self.modifiers = sorted(modifiers)
62
assert key in self.KEYS
67
for mod in sorted(list(set(self.modifiers))):
68
if mod != Qt.NoModifier:
69
tlist.append(self.MODIFIERS[mod])
70
tlist.append(self.KEYS[self.key])
71
return "+".join(tlist)
73
def __unicode__(self):
74
return unicode(self.__str__())
77
def modifier_from_str(modstr):
78
for k, v in Key.MODIFIERS.iteritems():
79
if v.lower() == modstr.lower():
83
def key_from_str(keystr):
84
for k, v in Key.KEYS.iteritems():
85
if v.lower() == keystr.lower():
89
def modifier_from_name(modname):
90
for k, v in Key.MODIFIERNAMES.iteritems():
91
if v.lower() == modname.lower():
94
def keystr2key(keystr):
95
keylist = keystr.split("+")
98
for modstr in keylist[:-1]:
99
mods.append(Key.modifier_from_str(modstr))
100
return Key(Key.key_from_str(keylist[-1]), *mods)
102
class Shortcut(object):
103
def __init__(self, context, name, key=None):
104
self.context = context
106
if isinstance(key, basestring):
107
key = keystr2key(key)
111
return "%s/%s: %s" % (self.context, self.name, self.key)
114
self.key = keystr2key(get_shortcut(self.context, self.name))
117
set_shortcut(self.context, self.name, str(self.key))
120
CONTEXT, NAME, MOD1, MOD2, MOD3, KEY = range(6)
122
class ShortcutsModel(QAbstractTableModel):
124
QAbstractTableModel.__init__(self)
127
def sortByName(self):
128
self.shortcuts = sorted(self.shortcuts,
129
key=lambda x: x.context+'/'+x.name)
132
def flags(self, index):
133
if not index.isValid():
134
return Qt.ItemIsEnabled
135
column = index.column()
136
if column in (MOD1, MOD2, MOD3, KEY):
137
return Qt.ItemFlags(QAbstractTableModel.flags(self, index)|
140
return Qt.ItemFlags(QAbstractTableModel.flags(self, index))
142
def data(self, index, role=Qt.DisplayRole):
143
if not index.isValid() or \
144
not (0 <= index.row() < len(self.shortcuts)):
146
shortcut = self.shortcuts[index.row()]
148
column = index.column()
149
if role == Qt.DisplayRole:
150
if column == CONTEXT:
151
return to_qvariant(shortcut.context)
153
return to_qvariant(shortcut.name)
155
return to_qvariant(Key.MODIFIERNAMES[key.modifiers[0]])
157
return to_qvariant(Key.MODIFIERNAMES[key.modifiers[1]])
159
return to_qvariant(Key.MODIFIERNAMES[key.modifiers[2]])
161
return to_qvariant(Key.KEYS[key.key])
162
elif role == Qt.TextAlignmentRole:
163
return to_qvariant(int(Qt.AlignHCenter|Qt.AlignVCenter))
166
def headerData(self, section, orientation, role=Qt.DisplayRole):
167
if role == Qt.TextAlignmentRole:
168
if orientation == Qt.Horizontal:
169
return to_qvariant(int(Qt.AlignHCenter|Qt.AlignVCenter))
170
return to_qvariant(int(Qt.AlignRight|Qt.AlignVCenter))
171
if role != Qt.DisplayRole:
173
if orientation == Qt.Horizontal:
174
if section == CONTEXT:
175
return to_qvariant(_("Context"))
176
elif section == NAME:
177
return to_qvariant(_("Name"))
178
elif section == MOD1:
179
return to_qvariant(_("Mod1"))
180
elif section == MOD2:
181
return to_qvariant(_("Mod2"))
182
elif section == MOD3:
183
return to_qvariant(_("Mod3"))
185
return to_qvariant(_("Key"))
188
def rowCount(self, index=QModelIndex()):
189
return len(self.shortcuts)
191
def columnCount(self, index=QModelIndex()):
194
def setData(self, index, value, role=Qt.EditRole):
195
if index.isValid() and 0 <= index.row() < len(self.shortcuts):
196
shortcut = self.shortcuts[index.row()]
198
column = index.column()
199
text = from_qvariant(value, str)
201
key.modifiers[0] = Key.modifier_from_name(text)
203
key.modifiers[1] = Key.modifier_from_name(text)
205
key.modifiers[2] = Key.modifier_from_name(text)
207
key.key = Key.key_from_str(text)
208
self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
214
class ShortcutsDelegate(QItemDelegate):
215
def __init__(self, parent=None):
216
QItemDelegate.__init__(self, parent)
217
self.modifiers = sorted(Key.MODIFIERNAMES.values())
219
self.keys = sorted(Key.KEYS.values())
222
def sizeHint(self, option, index):
223
fm = option.fontMetrics
224
if index.column() in (MOD1, MOD2, MOD3):
227
for mod in self.modifiers:
233
w = fm.width(self.mod)
234
return QSize(w+20, fm.height())
235
elif index.column() == KEY:
238
for key in self.keys:
244
w = fm.width(self.key)
245
return QSize(w+20, fm.height())
246
return QItemDelegate.sizeHint(self, option, index)
248
def createEditor(self, parent, option, index):
249
if index.column() in (MOD1, MOD2, MOD3):
250
combobox = QComboBox(parent)
251
combobox.addItems(self.modifiers)
253
elif index.column() == KEY:
254
combobox = QComboBox(parent)
255
combobox.addItems(self.keys)
258
return QItemDelegate.createEditor(self, parent, option,
261
def setEditorData(self, editor, index):
262
text = from_qvariant(index.model().data(index, Qt.DisplayRole), str)
263
if index.column() in (MOD1, MOD2, MOD3, KEY):
264
i = editor.findText(text)
267
editor.setCurrentIndex(i)
269
QItemDelegate.setEditorData(self, editor, index)
271
def setModelData(self, editor, model, index):
272
if index.column() in (MOD1, MOD2, MOD3, KEY):
273
model.setData(index, to_qvariant(editor.currentText()))
275
QItemDelegate.setModelData(self, editor, model, index)
278
class ShortcutsTable(QTableView):
279
def __init__(self, parent=None):
280
QTableView.__init__(self, parent)
281
self.model = ShortcutsModel()
282
self.setModel(self.model)
283
self.setItemDelegate(ShortcutsDelegate(self))
284
self.load_shortcuts()
286
def adjust_cells(self):
287
self.resizeColumnsToContents()
288
# self.resizeRowsToContents()
289
self.horizontalHeader().setStretchLastSection(True)
291
def load_shortcuts(self):
293
for context, name, keystr in iter_shortcuts():
294
shortcut = Shortcut(context, name, keystr)
295
shortcuts.append(shortcut)
296
shortcuts = sorted(shortcuts, key=lambda x: x.context+x.name)
297
self.model.shortcuts = shortcuts
301
def check_shortcuts(self):
302
"""Check shortcuts for conflicts"""
304
for index, sh1 in enumerate(self.model.shortcuts):
305
if index == len(self.model.shortcuts)-1:
307
for sh2 in self.model.shortcuts[index+1:]:
310
if str(sh2.key) == str(sh1.key) \
311
and (sh1.context == sh2.context or sh1.context == '_'
312
or sh2.context == '_'):
313
conflicts.append((sh1, sh2))
315
self.parent().emit(SIGNAL('show_this_page()'))
316
cstr = "\n".join(['%s <---> %s' % (sh1, sh2)
317
for sh1, sh2 in conflicts])
318
QMessageBox.warning(self, _( "Conflicts"),
319
_("The following conflicts have been "
320
"detected:")+"\n"+cstr, QMessageBox.Ok)
322
def save_shortcuts(self):
323
self.check_shortcuts()
324
for shortcut in self.model.shortcuts:
328
class ShortcutsConfigPage(GeneralConfigPage):
329
CONF_SECTION = "shortcuts"
331
return _("Keyboard shortcuts")
334
return get_icon("genprefs.png")
336
def setup_page(self):
337
self.table = ShortcutsTable(self)
338
self.connect(self.table.model,
339
SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
340
lambda i1, i2, opt='': self.has_been_modified(opt))
341
vlayout = QVBoxLayout()
342
vlayout.addWidget(self.table)
343
reset_btn = QPushButton(_("Reset to default values"))
344
self.connect(reset_btn, SIGNAL('clicked()'), self.reset_to_default)
345
vlayout.addWidget(reset_btn)
346
self.setLayout(vlayout)
348
def check_settings(self):
349
self.table.check_shortcuts()
351
def reset_to_default(self):
353
self.main.apply_shortcuts()
354
self.table.load_shortcuts()
355
self.load_from_conf()
356
self.set_modified(False)
358
def apply_settings(self, options):
359
self.table.save_shortcuts()
360
self.main.apply_shortcuts()
364
from spyderlib.utils.qthelpers import qapplication
366
table = ShortcutsTable()
369
print [str(s) for s in table.model.shortcuts]
370
table.check_shortcuts()
372
if __name__ == '__main__':
1
# -*- coding: utf-8 -*-
3
# Copyright © 2010 Pierre Raybaut
4
# Licensed under the terms of the MIT License
5
# (see spyderlib/__init__.py for details)
7
"""Shortcut management"""
9
from spyderlib.qt.QtGui import (QVBoxLayout, QComboBox, QItemDelegate,
10
QTableView, QMessageBox, QPushButton)
11
from spyderlib.qt.QtCore import (Qt, QSize, QAbstractTableModel, QModelIndex,
13
from spyderlib.qt.compat import to_qvariant, from_qvariant
18
from spyderlib.baseconfig import _
19
from spyderlib.guiconfig import (get_icon, get_shortcut, set_shortcut,
20
iter_shortcuts, reset_shortcuts)
21
from spyderlib.plugins.configdialog import GeneralConfigPage
24
KEYSTRINGS = ["Escape", "Tab", "Backtab", "Backspace", "Return", "Enter",
25
"Delete", "Pause", "Print", "Clear", "Home", "End", "Left",
26
"Up", "Right", "Down", "PageUp", "PageDown"] + \
27
["F%d" % _i for _i in range(1, 36)] + \
28
["Space", "Exclam", "QuoteDbl", "NumberSign", "Dollar", "Percent",
29
"Ampersand", "Apostrophe", "ParenLeft", "ParenRight", "Asterisk",
30
"Plus", "Comma", "Minus", "Period", "Slash"] + \
31
[str(_i) for _i in range(10)] + \
32
["Colon", "Semicolon", "Less", "Equal", "Greater", "Question",
33
"At"] + [chr(_i) for _i in range(65, 91)] + \
34
["BracketLeft", "Backslash", "BracketRight", "Underscore"]
38
MODIFIERS = {Qt.NoModifier: "", Qt.ShiftModifier: "Shift",
39
Qt.ControlModifier: "Ctrl", Qt.AltModifier: "Alt",
40
Qt.MetaModifier: "Meta"}
41
if sys.platform == 'darwin':
42
MODIFIERNAMES = {Qt.NoModifier: "", Qt.ShiftModifier: "Shift",
43
Qt.ControlModifier: "Cmd", Qt.AltModifier: "Alt",
44
Qt.MetaModifier: "Ctrl"}
45
elif sys.platform == 'win32':
46
MODIFIERNAMES = {Qt.NoModifier: "", Qt.ShiftModifier: "Shift",
47
Qt.ControlModifier: "Ctrl", Qt.AltModifier: "Alt",
48
Qt.MetaModifier: "Win"}
50
MODIFIERNAMES = {Qt.NoModifier: "", Qt.ShiftModifier: "Shift",
51
Qt.ControlModifier: "Ctrl", Qt.AltModifier: "Alt",
52
Qt.MetaModifier: "Meta"}
54
for attr in KEYSTRINGS:
55
KEYS[getattr(Qt, "Key_"+attr)] = attr
57
def __init__(self, key, mod1=Qt.NoModifier, mod2=Qt.NoModifier,
59
modifiers = [mod1, mod2, mod3]
60
assert all([mod in self.MODIFIERS for mod in modifiers])
61
self.modifiers = sorted(modifiers)
62
assert key in self.KEYS
67
for mod in sorted(list(set(self.modifiers))):
68
if mod != Qt.NoModifier:
69
tlist.append(self.MODIFIERS[mod])
70
tlist.append(self.KEYS[self.key])
71
return "+".join(tlist)
73
def __unicode__(self):
74
return unicode(self.__str__())
77
def modifier_from_str(modstr):
78
for k, v in Key.MODIFIERS.iteritems():
79
if v.lower() == modstr.lower():
83
def key_from_str(keystr):
84
for k, v in Key.KEYS.iteritems():
85
if v.lower() == keystr.lower():
89
def modifier_from_name(modname):
90
for k, v in Key.MODIFIERNAMES.iteritems():
91
if v.lower() == modname.lower():
94
def keystr2key(keystr):
95
keylist = keystr.split("+")
98
for modstr in keylist[:-1]:
99
mods.append(Key.modifier_from_str(modstr))
100
return Key(Key.key_from_str(keylist[-1]), *mods)
102
class Shortcut(object):
103
def __init__(self, context, name, key=None):
104
self.context = context
106
if isinstance(key, basestring):
107
key = keystr2key(key)
111
return "%s/%s: %s" % (self.context, self.name, self.key)
114
self.key = keystr2key(get_shortcut(self.context, self.name))
117
set_shortcut(self.context, self.name, str(self.key))
120
CONTEXT, NAME, MOD1, MOD2, MOD3, KEY = range(6)
122
class ShortcutsModel(QAbstractTableModel):
124
QAbstractTableModel.__init__(self)
127
def sortByName(self):
128
self.shortcuts = sorted(self.shortcuts,
129
key=lambda x: x.context+'/'+x.name)
132
def flags(self, index):
133
if not index.isValid():
134
return Qt.ItemIsEnabled
135
column = index.column()
136
if column in (MOD1, MOD2, MOD3, KEY):
137
return Qt.ItemFlags(QAbstractTableModel.flags(self, index)|
140
return Qt.ItemFlags(QAbstractTableModel.flags(self, index))
142
def data(self, index, role=Qt.DisplayRole):
143
if not index.isValid() or \
144
not (0 <= index.row() < len(self.shortcuts)):
146
shortcut = self.shortcuts[index.row()]
148
column = index.column()
149
if role == Qt.DisplayRole:
150
if column == CONTEXT:
151
return to_qvariant(shortcut.context)
153
return to_qvariant(shortcut.name)
155
return to_qvariant(Key.MODIFIERNAMES[key.modifiers[0]])
157
return to_qvariant(Key.MODIFIERNAMES[key.modifiers[1]])
159
return to_qvariant(Key.MODIFIERNAMES[key.modifiers[2]])
161
return to_qvariant(Key.KEYS[key.key])
162
elif role == Qt.TextAlignmentRole:
163
return to_qvariant(int(Qt.AlignHCenter|Qt.AlignVCenter))
166
def headerData(self, section, orientation, role=Qt.DisplayRole):
167
if role == Qt.TextAlignmentRole:
168
if orientation == Qt.Horizontal:
169
return to_qvariant(int(Qt.AlignHCenter|Qt.AlignVCenter))
170
return to_qvariant(int(Qt.AlignRight|Qt.AlignVCenter))
171
if role != Qt.DisplayRole:
173
if orientation == Qt.Horizontal:
174
if section == CONTEXT:
175
return to_qvariant(_("Context"))
176
elif section == NAME:
177
return to_qvariant(_("Name"))
178
elif section == MOD1:
179
return to_qvariant(_("Mod1"))
180
elif section == MOD2:
181
return to_qvariant(_("Mod2"))
182
elif section == MOD3:
183
return to_qvariant(_("Mod3"))
185
return to_qvariant(_("Key"))
188
def rowCount(self, index=QModelIndex()):
189
return len(self.shortcuts)
191
def columnCount(self, index=QModelIndex()):
194
def setData(self, index, value, role=Qt.EditRole):
195
if index.isValid() and 0 <= index.row() < len(self.shortcuts):
196
shortcut = self.shortcuts[index.row()]
198
column = index.column()
199
text = from_qvariant(value, str)
201
key.modifiers[0] = Key.modifier_from_name(text)
203
key.modifiers[1] = Key.modifier_from_name(text)
205
key.modifiers[2] = Key.modifier_from_name(text)
207
key.key = Key.key_from_str(text)
208
self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
214
class ShortcutsDelegate(QItemDelegate):
215
def __init__(self, parent=None):
216
QItemDelegate.__init__(self, parent)
217
self.modifiers = sorted(Key.MODIFIERNAMES.values())
219
self.keys = sorted(Key.KEYS.values())
222
def sizeHint(self, option, index):
223
fm = option.fontMetrics
224
if index.column() in (MOD1, MOD2, MOD3):
227
for mod in self.modifiers:
233
w = fm.width(self.mod)
234
return QSize(w+20, fm.height())
235
elif index.column() == KEY:
238
for key in self.keys:
244
w = fm.width(self.key)
245
return QSize(w+20, fm.height())
246
return QItemDelegate.sizeHint(self, option, index)
248
def createEditor(self, parent, option, index):
249
if index.column() in (MOD1, MOD2, MOD3):
250
combobox = QComboBox(parent)
251
combobox.addItems(self.modifiers)
253
elif index.column() == KEY:
254
combobox = QComboBox(parent)
255
combobox.addItems(self.keys)
258
return QItemDelegate.createEditor(self, parent, option,
261
def setEditorData(self, editor, index):
262
text = from_qvariant(index.model().data(index, Qt.DisplayRole), str)
263
if index.column() in (MOD1, MOD2, MOD3, KEY):
264
i = editor.findText(text)
267
editor.setCurrentIndex(i)
269
QItemDelegate.setEditorData(self, editor, index)
271
def setModelData(self, editor, model, index):
272
if index.column() in (MOD1, MOD2, MOD3, KEY):
273
model.setData(index, to_qvariant(editor.currentText()))
275
QItemDelegate.setModelData(self, editor, model, index)
278
class ShortcutsTable(QTableView):
279
def __init__(self, parent=None):
280
QTableView.__init__(self, parent)
281
self.model = ShortcutsModel()
282
self.setModel(self.model)
283
self.setItemDelegate(ShortcutsDelegate(self))
284
self.load_shortcuts()
286
def adjust_cells(self):
287
self.resizeColumnsToContents()
288
# self.resizeRowsToContents()
289
self.horizontalHeader().setStretchLastSection(True)
291
def load_shortcuts(self):
293
for context, name, keystr in iter_shortcuts():
294
shortcut = Shortcut(context, name, keystr)
295
shortcuts.append(shortcut)
296
shortcuts = sorted(shortcuts, key=lambda x: x.context+x.name)
297
self.model.shortcuts = shortcuts
301
def check_shortcuts(self):
302
"""Check shortcuts for conflicts"""
304
for index, sh1 in enumerate(self.model.shortcuts):
305
if index == len(self.model.shortcuts)-1:
307
for sh2 in self.model.shortcuts[index+1:]:
310
if str(sh2.key) == str(sh1.key) \
311
and (sh1.context == sh2.context or sh1.context == '_'
312
or sh2.context == '_'):
313
conflicts.append((sh1, sh2))
315
self.parent().emit(SIGNAL('show_this_page()'))
316
cstr = "\n".join(['%s <---> %s' % (sh1, sh2)
317
for sh1, sh2 in conflicts])
318
QMessageBox.warning(self, _( "Conflicts"),
319
_("The following conflicts have been "
320
"detected:")+"\n"+cstr, QMessageBox.Ok)
322
def save_shortcuts(self):
323
self.check_shortcuts()
324
for shortcut in self.model.shortcuts:
328
class ShortcutsConfigPage(GeneralConfigPage):
329
CONF_SECTION = "shortcuts"
331
return _("Keyboard shortcuts")
334
return get_icon("genprefs.png")
336
def setup_page(self):
337
self.table = ShortcutsTable(self)
338
self.connect(self.table.model,
339
SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
340
lambda i1, i2, opt='': self.has_been_modified(opt))
341
vlayout = QVBoxLayout()
342
vlayout.addWidget(self.table)
343
reset_btn = QPushButton(_("Reset to default values"))
344
self.connect(reset_btn, SIGNAL('clicked()'), self.reset_to_default)
345
vlayout.addWidget(reset_btn)
346
self.setLayout(vlayout)
348
def check_settings(self):
349
self.table.check_shortcuts()
351
def reset_to_default(self):
353
self.main.apply_shortcuts()
354
self.table.load_shortcuts()
355
self.load_from_conf()
356
self.set_modified(False)
358
def apply_settings(self, options):
359
self.table.save_shortcuts()
360
self.main.apply_shortcuts()
364
from spyderlib.utils.qthelpers import qapplication
366
table = ShortcutsTable()
369
print [str(s) for s in table.model.shortcuts]
370
table.check_shortcuts()
372
if __name__ == '__main__':
b'\\ No newline at end of file'