From 0eb9e65a73ab36eede77163db4640d04dd420766 Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Sun, 10 Jul 2016 18:56:23 -0700 Subject: [PATCH] qtpy: make git-cola pyside-compatible Pyside segfaults if the argv passed to it contains unicode, so avoid the core.abspath() wrapper when starting the application. Pyside's hotkey wrappers are less featureful and do not support binding hotkeys with ints or Qt.Key_XXX | Qt.XXX_Modifier. Use QKeySequence for all hotkeys to make them compatible across all Qt binding libraries. Pyside requires that QTreeWidgetItem subclasses implement __eq__ in order to use `item in list` expressions, so we provide a minimal implementation that checks for item identity. Checking the sha1s would makes it less robust, e.g. it would fail if given a specially crafted file todo with duplicate sha1s, so object identity is used for both robustness and speed. Signed-off-by: David Aguilar --- cola/app.py | 2 +- cola/hotkeys.py | 199 ++++++++++++++++++++++--------------------- cola/models/browse.py | 6 +- cola/widgets/diff.py | 2 +- cola/widgets/text.py | 17 ++-- share/git-cola/bin/git-xbase | 18 ++-- 6 files changed, 130 insertions(+), 114 deletions(-) rewrite cola/hotkeys.py (87%) diff --git a/cola/app.py b/cola/app.py index a6e47944..e573c86c 100644 --- a/cola/app.py +++ b/cola/app.py @@ -68,7 +68,7 @@ def setup_environment(): signal.signal(signal.SIGINT, signal.SIG_DFL) # Session management wants an absolute path when restarting - sys.argv[0] = sys_argv0 = core.abspath(sys.argv[0]) + sys.argv[0] = sys_argv0 = os.path.abspath(sys.argv[0]) # Spoof an X11 display for SSH os.environ.setdefault('DISPLAY', ':0') diff --git a/cola/hotkeys.py b/cola/hotkeys.py dissimilarity index 87% index 7e166dc3..75befaf6 100644 --- a/cola/hotkeys.py +++ b/cola/hotkeys.py @@ -1,96 +1,103 @@ -from __future__ import absolute_import - -from qtpy.QtGui import QKeySequence -from qtpy.QtCore import Qt - -# A-G -STAGE_MODIFIED = Qt.AltModifier + Qt.Key_A -WORD_LEFT = Qt.Key_B -BLAME = Qt.ControlModifier + Qt.ShiftModifier + Qt.Key_B -BRANCH = Qt.ControlModifier + Qt.Key_B -CHECKOUT = Qt.AltModifier + Qt.Key_B -CHERRY_PICK = Qt.ControlModifier + Qt.ShiftModifier + Qt.Key_C -DIFFSTAT = Qt.AltModifier + Qt.Key_D -DIFF = Qt.ControlModifier + Qt.Key_D -DIFF_SECONDARY = Qt.ControlModifier + Qt.ShiftModifier + Qt.Key_D -EDIT = Qt.ControlModifier + Qt.Key_E -EDIT_SECONDARY = Qt.ControlModifier + Qt.ShiftModifier + Qt.Key_E -EXPORT = Qt.AltModifier + Qt.Key_E -FIT = Qt.Key_F -FETCH = Qt.ControlModifier + Qt.Key_F -FILTER = Qt.ControlModifier + Qt.ShiftModifier + Qt.Key_F -GREP = Qt.ControlModifier + Qt.Key_G -# H-P -MOVE_LEFT = Qt.Key_H -HISTORY = Qt.ControlModifier + Qt.ShiftModifier + Qt.Key_H -SIGNOFF = Qt.ControlModifier + Qt.Key_I -MOVE_DOWN = Qt.Key_J -MOVE_DOWN_SECONDARY = Qt.AltModifier + Qt.Key_J -MOVE_DOWN_TERTIARY = Qt.ShiftModifier + Qt.Key_J -MOVE_UP = Qt.Key_K -MOVE_UP_SECONDARY = Qt.AltModifier + Qt.Key_K -MOVE_UP_TERTIARY = Qt.ShiftModifier + Qt.Key_K -MOVE_RIGHT = Qt.Key_L -FOCUS = Qt.ControlModifier + Qt.Key_L -FOCUS_STATUS = Qt.ControlModifier + Qt.Key_K -AMEND = Qt.ControlModifier + Qt.Key_M -MERGE = Qt.ControlModifier + Qt.ShiftModifier + Qt.Key_M -PUSH = Qt.ControlModifier + Qt.Key_P -PULL = Qt.ControlModifier + Qt.ShiftModifier + Qt.Key_P -# Q-Z -QUIT = Qt.ControlModifier + Qt.Key_Q -REFRESH = Qt.ControlModifier + Qt.Key_R -REFRESH_SECONDARY = Qt.Key_F5 -REFRESH_HOTKEYS = (REFRESH, REFRESH_SECONDARY) -STAGE_DIFF = Qt.Key_S -STAGE_SELECTION = Qt.ControlModifier + Qt.Key_S -STASH = Qt.AltModifier + Qt.ShiftModifier + Qt.Key_S -FINDER = Qt.ControlModifier + Qt.Key_T -FINDER_SECONDARY = Qt.Key_T -TERMINAL = Qt.ControlModifier + Qt.ShiftModifier + Qt.Key_T -STAGE_UNTRACKED = Qt.AltModifier + Qt.Key_U -REVERT = Qt.ControlModifier + Qt.Key_U -WORD_RIGHT = Qt.Key_W -UNDO = Qt.ControlModifier + Qt.Key_Z - -# Numbers -START_OF_LINE = Qt.Key_0 - -# Special keys -BACKSPACE = Qt.Key_Backspace -TRASH = Qt.ControlModifier + Qt.Key_Backspace -DELETE_FILE = Qt.ControlModifier + Qt.ShiftModifier + Qt.Key_Backspace -DELETE_FILE_SECONDARY = Qt.ControlModifier + Qt.Key_Backspace -PREFERENCES = Qt.ControlModifier + Qt.Key_Comma -END_OF_LINE = Qt.Key_Dollar -DOWN = Qt.Key_Down -ENTER = Qt.Key_Enter -ZOOM_OUT = Qt.Key_Minus -REMOVE_ITEM = Qt.Key_Minus -ADD_ITEM = Qt.Key_Plus -ZOOM_IN = Qt.Key_Plus -ZOOM_IN_SECONDARY = Qt.Key_Equal - -QUESTION = Qt.Key_Question -RETURN = Qt.Key_Return -ACCEPT = (ENTER, RETURN) -COMMIT = Qt.ControlModifier + Qt.Key_Return -PRIMARY_ACTION = Qt.Key_Space -SECONDARY_ACTION = Qt.ShiftModifier + Qt.Key_Space -LEAVE = Qt.ShiftModifier + Qt.Key_Tab -UP = Qt.Key_Up - -# Rebase -REBASE_PICK = (Qt.Key_1, Qt.Key_P) -REBASE_REWORD = (Qt.Key_2, Qt.Key_R) -REBASE_EDIT = (Qt.Key_3, Qt.Key_E) -REBASE_FIXUP = (Qt.Key_4, Qt.Key_F) -REBASE_SQUASH = (Qt.Key_5, Qt.Key_S) - -# Key Sequences -COPY = QKeySequence.Copy -CLOSE = QKeySequence.Close -CUT = QKeySequence.Cut -DELETE = QKeySequence.Delete -NEW = QKeySequence.New -OPEN = QKeySequence.Open +from __future__ import absolute_import + +from qtpy.QtGui import QKeySequence +from qtpy.QtCore import Qt + +# A-G +STAGE_MODIFIED = QKeySequence(Qt.ALT + Qt.Key_A) +WORD_LEFT = QKeySequence(Qt.Key_B) +BLAME = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_B) +BRANCH = QKeySequence(Qt.CTRL + Qt.Key_B) +CHECKOUT = QKeySequence(Qt.ALT + Qt.Key_B) +CHERRY_PICK = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_C) +DIFFSTAT = QKeySequence(Qt.ALT + Qt.Key_D) +DIFF = QKeySequence(Qt.CTRL + Qt.Key_D) +DIFF_SECONDARY = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_D) +EDIT = QKeySequence(Qt.CTRL + Qt.Key_E) +EDIT_SECONDARY = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_E) +EXPORT = QKeySequence(Qt.ALT + Qt.Key_E) +FIT = QKeySequence(Qt.Key_F) +FETCH = QKeySequence(Qt.CTRL + Qt.Key_F) +FILTER = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_F) +GREP = QKeySequence(Qt.CTRL + Qt.Key_G) +# H-P +MOVE_LEFT = QKeySequence(Qt.Key_H) +MOVE_LEFT_SHIFT = QKeySequence(Qt.SHIFT + Qt.Key_H) +HISTORY = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_H) +SIGNOFF = QKeySequence(Qt.CTRL + Qt.Key_I) +MOVE_DOWN = QKeySequence(Qt.Key_J) +MOVE_DOWN_SHIFT = QKeySequence(Qt.SHIFT + Qt.Key_J) +MOVE_DOWN_SECONDARY = QKeySequence(Qt.ALT + Qt.Key_J) +MOVE_DOWN_TERTIARY = QKeySequence(Qt.SHIFT + Qt.Key_J) +MOVE_UP = QKeySequence(Qt.Key_K) +MOVE_UP_SHIFT = QKeySequence(Qt.SHIFT + Qt.Key_K) +MOVE_UP_SECONDARY = QKeySequence(Qt.ALT + Qt.Key_K) +MOVE_UP_TERTIARY = QKeySequence(Qt.SHIFT + Qt.Key_K) +MOVE_RIGHT = QKeySequence(Qt.Key_L) +MOVE_RIGHT_SHIFT = QKeySequence(Qt.SHIFT + Qt.Key_L) +FOCUS = QKeySequence(Qt.CTRL + Qt.Key_L) +FOCUS_STATUS = QKeySequence(Qt.CTRL + Qt.Key_K) +AMEND = QKeySequence(Qt.CTRL + Qt.Key_M) +MERGE = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_M) +PUSH = QKeySequence(Qt.CTRL + Qt.Key_P) +PULL = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_P) +# Q-Z +QUIT = QKeySequence(Qt.CTRL + Qt.Key_Q) +REFRESH = QKeySequence(Qt.CTRL + Qt.Key_R) +REFRESH_SECONDARY = QKeySequence(Qt.Key_F5) +REFRESH_HOTKEYS = (REFRESH, REFRESH_SECONDARY) +STAGE_DIFF = QKeySequence(Qt.Key_S) +STAGE_SELECTION = QKeySequence(Qt.CTRL + Qt.Key_S) +STASH = QKeySequence(Qt.ALT + Qt.SHIFT + Qt.Key_S) +FINDER = QKeySequence(Qt.CTRL + Qt.Key_T) +FINDER_SECONDARY = QKeySequence(Qt.Key_T) +TERMINAL = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_T) +STAGE_UNTRACKED = QKeySequence(Qt.ALT + Qt.Key_U) +REVERT = QKeySequence(Qt.CTRL + Qt.Key_U) +WORD_RIGHT = QKeySequence(Qt.Key_W) +UNDO = QKeySequence(Qt.CTRL + Qt.Key_Z) + +# Numbers +START_OF_LINE = QKeySequence(Qt.Key_0) + +# Special keys +BACKSPACE = QKeySequence(Qt.Key_Backspace) +TRASH = QKeySequence(Qt.CTRL + Qt.Key_Backspace) +DELETE_FILE = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_Backspace) +DELETE_FILE_SECONDARY = QKeySequence(Qt.CTRL + Qt.Key_Backspace) +PREFERENCES = QKeySequence(Qt.CTRL + Qt.Key_Comma) +END_OF_LINE = QKeySequence(Qt.Key_Dollar) +DOWN = QKeySequence(Qt.Key_Down) +ENTER = QKeySequence(Qt.Key_Enter) +ZOOM_OUT = QKeySequence(Qt.Key_Minus) +REMOVE_ITEM = QKeySequence(Qt.Key_Minus) +ADD_ITEM = QKeySequence(Qt.Key_Plus) +ZOOM_IN = QKeySequence(Qt.Key_Plus) +ZOOM_IN_SECONDARY = QKeySequence(Qt.Key_Equal) + +QUESTION = QKeySequence(Qt.Key_Question) +RETURN = QKeySequence(Qt.Key_Return) +ACCEPT = (ENTER, RETURN) +COMMIT = QKeySequence(Qt.CTRL + Qt.Key_Return) +PRIMARY_ACTION = QKeySequence(QKeySequence(Qt.Key_Space)) +SECONDARY_ACTION = QKeySequence(Qt.SHIFT + Qt.Key_Space) +LEAVE = QKeySequence(Qt.SHIFT + Qt.Key_Tab) +UP = QKeySequence(Qt.Key_Up) + +CTRL_RETURN = QKeySequence(Qt.CTRL + Qt.Key_Return) +CTRL_ENTER = QKeySequence(Qt.CTRL + Qt.Key_Enter) + +# Rebase +REBASE_PICK = (QKeySequence(Qt.Key_1), QKeySequence(Qt.Key_P)) +REBASE_REWORD = (QKeySequence(Qt.Key_2), QKeySequence(Qt.Key_R)) +REBASE_EDIT = (QKeySequence(Qt.Key_3), QKeySequence(Qt.Key_E)) +REBASE_FIXUP = (QKeySequence(Qt.Key_4), QKeySequence(Qt.Key_F)) +REBASE_SQUASH = (QKeySequence(Qt.Key_5), QKeySequence(Qt.Key_S)) + +# Key Sequences +COPY = QKeySequence.Copy +CLOSE = QKeySequence.Close +CUT = QKeySequence.Cut +DELETE = QKeySequence.Delete +NEW = QKeySequence.New +OPEN = QKeySequence.Open diff --git a/cola/models/browse.py b/cola/models/browse.py index 017335e0..1d311a9a 100644 --- a/cola/models/browse.py +++ b/cola/models/browse.py @@ -21,7 +21,7 @@ from cola.models import main # Custom event type for GitRepoInfoEvents -INFO_EVENT_TYPE = QtCore.QEvent.User + 42 +INFO_EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType()) class Columns(object): @@ -430,7 +430,7 @@ class GitRepoInfoTask(qtutils.Task): class GitRepoInfoEvent(QtCore.QEvent): """Transport mechanism for communicating from a GitRepoInfoTask.""" def __init__(self, signal, *data): - QtCore.QEvent.__init__(self, QtCore.QEvent.User + 1) + QtCore.QEvent.__init__(self, INFO_EVENT_TYPE) self.signal = signal self.data = data @@ -472,7 +472,7 @@ class GitRepoItem(QtGui.QStandardItem): class GitRepoNameItem(GitRepoItem): """Subclass GitRepoItem to provide a custom type().""" - TYPE = QtGui.QStandardItem.UserType + 1 + TYPE = QtGui.QStandardItem.ItemType(QtGui.QStandardItem.UserType + 1) def __init__(self, path, parent, runtask): GitRepoItem.__init__(self, Columns.NAME, path, parent, runtask) diff --git a/cola/widgets/diff.py b/cola/widgets/diff.py index 1ea85bc6..e3c50e0a 100644 --- a/cola/widgets/diff.py +++ b/cola/widgets/diff.py @@ -191,7 +191,7 @@ class DiffEditor(DiffTextEdit): self, 'Revert', self.revert_selection, hotkeys.REVERT) self.action_revert_selection.setIcon(icons.undo()) - self.launch_editor = actions.launch_editor(self, 'Return', 'Enter') + self.launch_editor = actions.launch_editor(self, *hotkeys.ACCEPT) self.launch_difftool = actions.launch_difftool(self) self.stage_or_unstage = actions.stage_or_unstage(self) diff --git a/cola/widgets/text.py b/cola/widgets/text.py index 74502a53..6bbf3479 100644 --- a/cola/widgets/text.py +++ b/cola/widgets/text.py @@ -341,10 +341,14 @@ class VimMixin(object): self.widget = widget self.Base = widget.Base # Common vim/unix-ish keyboard actions - self.add_navigation('Up', hotkeys.MOVE_UP, shift=True) - self.add_navigation('Down', hotkeys.MOVE_DOWN, shift=True) - self.add_navigation('Left', hotkeys.MOVE_LEFT, shift=True) - self.add_navigation('Right', hotkeys.MOVE_RIGHT, shift=True) + self.add_navigation('Up', hotkeys.MOVE_UP, + shift=hotkeys.MOVE_UP_SHIFT) + self.add_navigation('Down', hotkeys.MOVE_DOWN, + shift=hotkeys.MOVE_DOWN_SHIFT) + self.add_navigation('Left', hotkeys.MOVE_LEFT, + shift=hotkeys.MOVE_LEFT_SHIFT) + self.add_navigation('Right', hotkeys.MOVE_RIGHT, + shift=hotkeys.MOVE_RIGHT_SHIFT) self.add_navigation('WordLeft', hotkeys.WORD_LEFT) self.add_navigation('WordRight', hotkeys.WORD_RIGHT) self.add_navigation('StartOfLine', hotkeys.START_OF_LINE) @@ -358,7 +362,7 @@ class VimMixin(object): lambda: widget.page(widget.height()//2), hotkeys.PRIMARY_ACTION) - def add_navigation(self, name, hotkey, shift=False): + def add_navigation(self, name, hotkey, shift=None): """Add a hotkey along with a shift-variant""" widget = self.widget direction = getattr(QtGui.QTextCursor, name) @@ -366,8 +370,7 @@ class VimMixin(object): lambda: self.move(direction), hotkey) if shift: qtutils.add_action(widget, 'Shift' + name, - lambda: self.move(direction, True), - Qt.ShiftModifier | hotkey) + lambda: self.move(direction, True), shift) def move(self, direction, select=False, n=1): widget = self.widget diff --git a/share/git-cola/bin/git-xbase b/share/git-cola/bin/git-xbase index 1f82c40c..988b64b8 100755 --- a/share/git-cola/bin/git-xbase +++ b/share/git-cola/bin/git-xbase @@ -172,7 +172,8 @@ class Editor(QtWidgets.QWidget): self.setLayout(layout) self.action_rebase = qtutils.add_action( - self, N_('Rebase'), self.rebase, 'Ctrl+Return') + self, N_('Rebase'), self.rebase, + hotkeys.CTRL_RETURN, hotkeys.CTRL_ENTER) notifier.add_observer(diff.COMMITS_SELECTED, self.commits_selected) self.tree.external_diff.connect(self.external_diff) @@ -488,6 +489,9 @@ class RebaseTreeWidgetItem(QtWidgets.QTreeWidgetItem): flags = flags & ~Qt.ItemIsDropEnabled self.setFlags(flags) + def __eq__(self, other): + return self is other + def copy(self): return self.__class__(self.idx, self.is_enabled(), self.command, sha1hex=self.sha1hex, summary=self.summary, @@ -506,8 +510,8 @@ class RebaseTreeWidgetItem(QtWidgets.QTreeWidgetItem): combo.setCurrentIndex(idx) combo.setEnabled(self.is_commit()) - signal = combo.currentIndexChanged['QString'] - signal.connect(self.set_command_and_validate) + signal = combo.currentIndexChanged + signal.connect(lambda x: self.set_command_and_validate(combo)) combo.validate.connect(parent.validate) parent.setItemWidget(self, self.COMMAND_COLUMN, combo) @@ -547,15 +551,17 @@ class RebaseTreeWidgetItem(QtWidgets.QTreeWidgetItem): def refresh(self): """Update the view to match the updated state""" - command = self.command - self.combo.setCurrentIndex(COMMAND_IDX[command]) + if self.is_commit(): + command = self.command + self.combo.setCurrentIndex(COMMAND_IDX[command]) def reset_command(self, command): """Set and refresh the item in one shot""" self.set_command(command) self.refresh() - def set_command_and_validate(self, command): + def set_command_and_validate(self, combo): + command = COMMANDS[combo.currentIndex()] self.set_command(command) self.combo.validate.emit() -- 2.11.4.GIT