From b34db29178fad6344c0a1f0adb6ffb0638253f68 Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Sun, 11 Jul 2021 11:33:12 -0700 Subject: [PATCH] widgets: use qtutils.BlockSignals to manage blockSignals(bool) We have nice things. The BlockSignals context manager makes it easy to manage a widget's blockSignals(bool) state. Leverage qtutils.BlockSignals to make the code more robust. Make it impossible to forget to call blockSignals(True) after doing blockSignals(False) by using the context manager. Signed-off-by: David Aguilar --- cola/widgets/commitmsg.py | 12 +++--- cola/widgets/dag.py | 6 +-- cola/widgets/diff.py | 15 +++----- cola/widgets/editremotes.py | 16 ++++---- cola/widgets/prefs.py | 38 +++++++++---------- cola/widgets/status.py | 89 +++++++++++++++++++++++++++++---------------- cola/widgets/text.py | 22 +++++++---- 7 files changed, 114 insertions(+), 84 deletions(-) diff --git a/cola/widgets/commitmsg.py b/cola/widgets/commitmsg.py index 6d9f0410..21820057 100644 --- a/cola/widgets/commitmsg.py +++ b/cola/widgets/commitmsg.py @@ -399,9 +399,8 @@ class CommitMessageEditor(QtWidgets.QFrame): def set_linebreak(self, brk): self._linebreak = brk self.description.set_linebreak(brk) - blocksignals = self.autowrap_action.blockSignals(True) - self.autowrap_action.setChecked(brk) - self.autowrap_action.blockSignals(blocksignals) + with qtutils.BlockSignals(self.autowrap_action): + self.autowrap_action.setChecked(brk) def setFont(self, font): """Pass the setFont() calls down to the text widgets""" @@ -411,10 +410,9 @@ class CommitMessageEditor(QtWidgets.QFrame): def set_mode(self, mode): can_amend = not self.model.is_merging checked = mode == self.model.mode_amend - blocksignals = self.amend_action.blockSignals(True) - self.amend_action.setEnabled(can_amend) - self.amend_action.setChecked(checked) - self.amend_action.blockSignals(blocksignals) + with qtutils.BlockSignals(self.amend_action): + self.amend_action.setEnabled(can_amend) + self.amend_action.setChecked(checked) def commit(self): """Attempt to create a commit from the index and commit message.""" diff --git a/cola/widgets/dag.py b/cola/widgets/dag.py index 26456824..936eb41b 100644 --- a/cola/widgets/dag.py +++ b/cola/widgets/dag.py @@ -1209,6 +1209,7 @@ class Commit(QtWidgets.QGraphicsItem): self.edges = {} def blockSignals(self, blocked): + """Disable notifications during sections that cause notification loops""" self.notifier.notification_enabled = not blocked def itemChange(self, change, value): @@ -1504,9 +1505,8 @@ class GraphView(QtWidgets.QGraphicsView, ViewerMixin): item = self.items[oid] except KeyError: continue - item.blockSignals(True) - item.setSelected(True) - item.blockSignals(False) + with qtutils.BlockSignals(item): + item.setSelected(True) item_rect = item.sceneTransform().mapRect(item.boundingRect()) self.ensureVisible(item_rect) diff --git a/cola/widgets/diff.py b/cola/widgets/diff.py index c7f7b23d..02a07225 100644 --- a/cola/widgets/diff.py +++ b/cola/widgets/diff.py @@ -774,9 +774,8 @@ class DiffEditor(DiffTextEdit): """Enable/disable the diff line number display""" self.numbers.setVisible(enabled) if update: - signals = self.options.show_line_numbers.blockSignals(True) - self.options.show_line_numbers.setChecked(enabled) - self.options.show_line_numbers.blockSignals(signals) + with qtutils.BlockSignals(self.options.show_line_numbers): + self.options.show_line_numbers.setChecked(enabled) # Refresh the display. Not doing this results in the display not # correctly displaying the line numbers widget until the text scrolls. self.set_value(self.value()) @@ -784,9 +783,8 @@ class DiffEditor(DiffTextEdit): def set_word_wrapping(self, enabled, update=False): """Enable/disable word wrapping""" if update: - signals = self.options.enable_word_wrapping.blockSignals(True) - self.options.enable_word_wrapping.setChecked(enabled) - self.options.enable_word_wrapping.blockSignals(signals) + with qtutils.BlockSignals(self.options.enable_word_wrapping): + self.options.enable_word_wrapping.setChecked(enabled) if enabled: self.setWordWrapMode(QtGui.QTextOption.WordWrap) self.setLineWrapMode(QtWidgets.QPlainTextEdit.WidgetWidth) @@ -1164,9 +1162,8 @@ class TextLabel(QtWidgets.QLabel): def resizeEvent(self, event): if self._elide: self.update_text(event.size().width()) - block = self.blockSignals(True) - self.setText(self._display) - self.blockSignals(block) + with qtutils.BlockSignals(self): + self.setText(self._display) QtWidgets.QLabel.resizeEvent(self, event) diff --git a/cola/widgets/editremotes.py b/cola/widgets/editremotes.py index 04ed1a67..631fe89b 100644 --- a/cola/widgets/editremotes.py +++ b/cola/widgets/editremotes.py @@ -237,16 +237,16 @@ class RemoteEditor(standard.Dialog): def refresh(self, select=True): git = self.context.git remotes = git.remote()[STDOUT].splitlines() - self.remotes.blockSignals(True) # ignore selection change signals - self.remotes.clear() - self.remotes.addItems(remotes) - self.remote_list = remotes + # Ignore notifications from self.remotes while mutating. + with qtutils.BlockSignals(self.remotes): + self.remotes.clear() + self.remotes.addItems(remotes) + self.remote_list = remotes - for idx in range(len(remotes)): - item = self.remotes.item(idx) - item.setFlags(item.flags() | Qt.ItemIsEditable) + for idx in range(len(remotes)): + item = self.remotes.item(idx) + item.setFlags(item.flags() | Qt.ItemIsEditable) - self.remotes.blockSignals(False) if select: if not self.current_name and remotes: # Nothing is selected; select the first item diff --git a/cola/widgets/prefs.py b/cola/widgets/prefs.py index 5f2dc941..2b714e23 100644 --- a/cola/widgets/prefs.py +++ b/cola/widgets/prefs.py @@ -104,16 +104,16 @@ class FormWidget(QtWidgets.QWidget): def set_widget_value(widget, value): - widget.blockSignals(True) - if isinstance(widget, QtWidgets.QSpinBox): - widget.setValue(value) - elif isinstance(widget, QtWidgets.QLineEdit): - widget.setText(value) - elif isinstance(widget, QtWidgets.QCheckBox): - widget.setChecked(value) - elif isinstance(widget, qtutils.ComboBox): - widget.set_value(value) - widget.blockSignals(False) + """Set a value on a widget without emitting notifications""" + with qtutils.BlockSignals(widget): + if isinstance(widget, QtWidgets.QSpinBox): + widget.setValue(value) + elif isinstance(widget, QtWidgets.QLineEdit): + widget.setText(value) + elif isinstance(widget, QtWidgets.QCheckBox): + widget.setChecked(value) + elif isinstance(widget, qtutils.ComboBox): + widget.set_value(value) class RepoFormWidget(FormWidget): @@ -272,17 +272,17 @@ class SettingsFormWidget(FormWidget): self.font_size.valueChanged.connect(self.font_size_changed) def update_from_config(self): + """Update widgets to the current config values""" FormWidget.update_from_config(self) context = self.context - block = self.fixed_font.blockSignals(True) - font = qtutils.diff_font(context) - self.fixed_font.setCurrentFont(font) - self.fixed_font.blockSignals(block) - - block = self.font_size.blockSignals(True) - font_size = font.pointSize() - self.font_size.setValue(font_size) - self.font_size.blockSignals(block) + + with qtutils.BlockSignals(self.fixed_font): + font = qtutils.diff_font(context) + self.fixed_font.setCurrentFont(font) + + with qtutils.BlockSignals(self.font_size): + font_size = font.pointSize() + self.font_size.setValue(font_size) def font_size_changed(self, size): font = self.fixed_font.currentFont() diff --git a/cola/widgets/status.py b/cola/widgets/status.py index 32a3bd96..ee2fbef9 100644 --- a/cola/widgets/status.py +++ b/cola/widgets/status.py @@ -324,20 +324,32 @@ class StatusTreeWidget(QtWidgets.QTreeWidget): item.setHidden(True) def _restore_selection(self): + """Apply the old selection to the newly updated items""" + # This function is called after a new set of items have been added to + # the per-category file lists. Its purpose is to either restore the + # existing selection or to create a new intuitive selection based on + # a combination of the old items, the old selection and the new items. if not self.old_selection or not self.old_contents: return + # The old set of categorized files. old_c = self.old_contents + # The old selection. old_s = self.old_selection + # The current/new set of categorized files. new_c = self.contents() def mkselect(lst, widget_getter): + """Return a closure to select items in the specified category""" def select(item, current=False): + """Select the widget item based on the list index""" + # The item lists and widget indexes have a 1:1 correspondence. + # Lookup the item filename in the list and use that index to + # retrieve the widget item and select it. idx = lst.index(item) item = widget_getter(idx) if current: self.setCurrentItem(item) item.setSelected(True) - return select select_staged = mkselect(new_c.staged, self._staged_item) @@ -363,10 +375,9 @@ class StatusTreeWidget(QtWidgets.QTreeWidget): if category == self.idx_header: item = self.invisibleRootItem().child(idx) if item is not None: - self.blockSignals(True) - self.setCurrentItem(item) - item.setSelected(True) - self.blockSignals(False) + with qtutils.BlockSignals(self): + self.setCurrentItem(item) + item.setSelected(True) self.show_selection() return # Reselect the current item @@ -385,13 +396,24 @@ class StatusTreeWidget(QtWidgets.QTreeWidget): # When reselecting we only care that the items are selected; # we do not need to rerun the callbacks which were triggered # above. Block signals to skip the callbacks. - self.blockSignals(True) - for (new, old, sel, reselect) in saved_selection: - for item in sel: - if item in new: - reselect(item, current=False) - self.blockSignals(False) + with qtutils.BlockSignals(self): + for (new, old, sel, reselect) in saved_selection: + for item in sel: + if item in new: + reselect(item, current=False) + + # The status widget is used to interactively work your way down the + # list of Staged, Unmerged, Modified and Untracked items and perform + # an operation on them. + # + # For Staged items we intend to work our way down the list of Staged + # items while we unstage each item. For every other category we work + # our way down the list of {Unmerged,Modified,Untracked} items while + # we stage each item. + # + # The following block of code implements the behavior of selecting + # the next item based on the previous selection. for (new, old, sel, reselect) in saved_selection: # When modified is staged, select the next modified item # When unmerged is staged, select the next unmerged item @@ -524,7 +546,7 @@ class StatusTreeWidget(QtWidgets.QTreeWidget): current = self.currentItem() if not current: return None - idx = self.indexFromItem(current, 0) + idx = self.indexFromItem(current) if idx.parent().isValid(): parent_idx = idx.parent() entry = (parent_idx.row(), idx.row()) @@ -555,39 +577,44 @@ class StatusTreeWidget(QtWidgets.QTreeWidget): def _set_staged(self, items): """Adds items to the 'Staged' subtree.""" - self._set_subtree( - items, - self.idx_staged, - N_('Staged'), - staged=True, - deleted_set=self.m.staged_deleted, - ) + with qtutils.BlockSignals(self): + self._set_subtree( + items, + self.idx_staged, + N_('Staged'), + staged=True, + deleted_set=self.m.staged_deleted, + ) def _set_modified(self, items): """Adds items to the 'Modified' subtree.""" - self._set_subtree( - items, - self.idx_modified, - N_('Modified'), - deleted_set=self.m.unstaged_deleted, - ) + with qtutils.BlockSignals(self): + self._set_subtree( + items, + self.idx_modified, + N_('Modified'), + deleted_set=self.m.unstaged_deleted, + ) def _set_unmerged(self, items): """Adds items to the 'Unmerged' subtree.""" deleted_set = set([path for path in items if not core.exists(path)]) - self._set_subtree( - items, self.idx_unmerged, N_('Unmerged'), deleted_set=deleted_set - ) + with qtutils.BlockSignals(self): + self._set_subtree( + items, self.idx_unmerged, N_('Unmerged'), deleted_set=deleted_set + ) def _set_untracked(self, items): """Adds items to the 'Untracked' subtree.""" - self._set_subtree(items, self.idx_untracked, N_('Untracked'), untracked=True) + with qtutils.BlockSignals(self): + self._set_subtree( + items, self.idx_untracked, N_('Untracked'), untracked=True + ) def _set_subtree( self, items, idx, parent_title, staged=False, untracked=False, deleted_set=None ): """Add a list of items to a treewidget item.""" - self.blockSignals(True) parent = self.topLevelItem(idx) hide = not bool(items) parent.setHidden(hide) @@ -604,7 +631,7 @@ class StatusTreeWidget(QtWidgets.QTreeWidget): ) parent.addChild(treeitem) self._expand_items(idx, items) - self.blockSignals(False) + if prefs.status_show_totals(self.context): parent.setText(0, '%s (%s)' % (parent_title, len(items))) diff --git a/cola/widgets/text.py b/cola/widgets/text.py index 0844da51..8757499b 100644 --- a/cola/widgets/text.py +++ b/cola/widgets/text.py @@ -45,13 +45,18 @@ class LineEdit(QtWidgets.QLineEdit): return self._get_value(self) def set_value(self, value, block=False): + """Update the widget to the specified value""" if block: - blocksig = self.blockSignals(True) + with qtutils.BlockSignals(self): + self._set_value(value) + else: + self._set_value(value) + + def _set_value(self, value): + """Implementation helper to update the widget to the specified value""" pos = self.cursorPosition() self.setText(value) self.setCursorPosition(pos) - if block: - self.blockSignals(blocksig) class LineEditCursorPosition(object): @@ -109,9 +114,15 @@ class BaseTextEditExtension(QtCore.QObject): return self._get_value(self.widget) def set_value(self, value, block=False): + """Update the widget to the specified value""" if block: - blocksig = self.widget.blockSignals(True) + with qtutils.BlockSignals(self): + self._set_value(value) + else: + self._set_value(value) + def _set_value(self, value): + """Implementation helper to update the widget to the specified value""" # Save cursor position offset, selection_text = self.offset_and_selection() old_value = get(self.widget) @@ -144,9 +155,6 @@ class BaseTextEditExtension(QtCore.QObject): cursor.movePosition(QtGui.QTextCursor.StartOfLine) self.widget.setTextCursor(cursor) - if block: - self.widget.blockSignals(blocksig) - def set_cursor_position(self, new_position): cursor = self.widget.textCursor() cursor.setPosition(new_position) -- 2.11.4.GIT