[PATCH] close #5709

35 views
Skip to first unread message

KPOP

unread,
Apr 10, 2021, 5:43:06 AM4/10/21
to TortoiseHg Developers
# HG changeset patch
# User KPOP
# Date 1618047729 -7200
#      Sat Apr 10 11:42:09 2021 +0200
# Branch #5709
# Node ID 7f1c3432d69831a3e008e88469a1c7dea661a268
# Parent  26fd4ed553f4347a7805b070563498fb200efa11
Shift-click certain actions to apply them to all opened repositories (#5709)

diff -r 26fd4ed553f4 -r 7f1c3432d698 tortoisehg/hgqt/repotab.py
--- a/tortoisehg/hgqt/repotab.py Fri Apr 09 18:50:21 2021 -0400
+++ b/tortoisehg/hgqt/repotab.py Sat Apr 10 11:42:09 2021 +0200
@@ -343,6 +343,9 @@
     def currentWidget(self):
         return self._stack.currentWidget()
 
+    def widgets(self):
+        return [self._stack.widget(i) for i in range(0, self.count())]
+
     @pyqtSlot(int)
     def setCurrentIndex(self, index):
         self._tabbar.setCurrentIndex(index)
diff -r 26fd4ed553f4 -r 7f1c3432d698 tortoisehg/hgqt/workbench.py
--- a/tortoisehg/hgqt/workbench.py Fri Apr 09 18:50:21 2021 -0400
+++ b/tortoisehg/hgqt/workbench.py Sat Apr 10 11:42:09 2021 +0200
@@ -646,8 +646,8 @@
 
     @pyqtSlot(QAction)
     def _onSwitchRepoTaskTab(self, action):
-        rw = self._currentRepoWidget()
-        if rw:
+        rws = self._currentRepoShiftForAll()
+        for rw in rws:
             rw.switchToNamedTaskTab(str(action.data()))
 
     @pyqtSlot(bool)
@@ -842,8 +842,8 @@
     def _repotogglefwd(self, name):
         """Return function to forward action to the current repo tab"""
         def forwarder(checked):
-            w = self._currentRepoWidget()
-            if w:
+            ws = self._currentRepoShiftForAll()
+            for w in ws:
                 getattr(w, name)(checked)
         return forwarder
 
@@ -877,9 +877,9 @@
 
     @pyqtSlot(QAction)
     def _runSyncAction(self, action):
-        w = self._currentRepoWidget()
-        if w:
-            op = str(action.data())
+        op = str(action.data())
+        ws = self._currentRepoShiftForAll(op != "push")
+        for w in ws:
             w.setSyncUrl(self._syncUrlFor(op) or '')
             getattr(w, op)()
 
@@ -995,6 +995,13 @@
     def _currentRepoWidget(self):
         return self.repoTabsWidget.currentWidget()
 
+    def _currentRepoShiftForAll(self, allowAll=True):
+        if allowAll and QApplication.keyboardModifiers() == Qt.ShiftModifier:
+            return self.repoTabsWidget.widgets()
+        if self._currentRepoWidget():
+            return [self._currentRepoWidget()]
+        return []
+
     def currentRepoRootPath(self):
         return self.repoTabsWidget.currentRepoRootPath()
 

Yuya Nishihara

unread,
Apr 10, 2021, 10:43:16 PM4/10/21
to thg...@googlegroups.com, KPOP, mharb...@gmail.com
On Sat, 10 Apr 2021 02:43:06 -0700 (PDT), KPOP wrote:
> # HG changeset patch
> # User KPOP
> # Date 1618047729 -7200
> # Sat Apr 10 11:42:09 2021 +0200
> # Branch #5709
> # Node ID 7f1c3432d69831a3e008e88469a1c7dea661a268
> # Parent 26fd4ed553f4347a7805b070563498fb200efa11
> Shift-click certain actions to apply them to all opened repositories (#5709)

It's probably better to add a separate menu action (e.g. "Push All Tabs".)
Shift+click has discoverability problem in general, so I think it should be
avoided for instant mutable operation like push/pull.

Toolbar push/pull buttons can be set to QToolButton::DelayedPopup to provide
"Push All Tabs" action.

For the other view switching actions, Shift+click may be acceptable. But I
think it's better to add a global option to keep view state synchronized
across all tabs. If I preferred this behavior, I wouldn't want to selectively
turn it on/off by Shift key, but rather want to stick to it.

Matt Harbison

unread,
Apr 10, 2021, 11:24:24 PM4/10/21
to TortoiseHg Developers
I'd be a big fan of a mode to keep all views synchronized.  It's really annoying when workbench doesn't exit normally and save the settings, to have to re-open all tabs, adjust a bunch of column widths and turn on the search bar again on each repo.

KPOP

unread,
Apr 11, 2021, 1:44:14 PM4/11/21
to TortoiseHg Developers
Thank you for the feedback, both suggestions make a lot more sense. I'll rework #5709 with the delayed popup and have created #5710 for the synchronised views.

Op zondag 11 april 2021 om 05:24:24 UTC+2 schreef Matt Harbison:

KPOP

unread,
Apr 11, 2021, 2:54:14 PM4/11/21
to TortoiseHg Developers
# HG changeset patch
# User kpop
# Date 1618167139 -7200
#      Sun Apr 11 20:52:19 2021 +0200
# Node ID 9533513d9ecfbb7795429e871a3b8c71ea8a3277
# Parent  04d18324d40e452b6c5f1eb3cdb598c6a96761ce
Use delayed menu's to apply an action to all open repositories (#5709)

diff -r 04d18324d40e -r 9533513d9ecf tortoisehg/hgqt/repotab.py
--- a/tortoisehg/hgqt/repotab.py Fri Apr 09 22:38:34 2021 -0400
+++ b/tortoisehg/hgqt/repotab.py Sun Apr 11 20:52:19 2021 +0200
@@ -343,6 +343,9 @@
     def currentWidget(self):
         return self._stack.currentWidget()
 
+    def widgets(self):
+        return [self._stack.widget(i) for i in range(0, self.count())]
+
     @pyqtSlot(int)
     def setCurrentIndex(self, index):
         self._tabbar.setCurrentIndex(index)
diff -r 04d18324d40e -r 9533513d9ecf tortoisehg/hgqt/workbench.py
--- a/tortoisehg/hgqt/workbench.py Fri Apr 09 22:38:34 2021 -0400
+++ b/tortoisehg/hgqt/workbench.py Sun Apr 11 20:52:19 2021 +0200
@@ -11,6 +11,7 @@
 from __future__ import absolute_import
 
 import os
+import re
 import subprocess
 import sys
 
@@ -394,6 +395,12 @@
                  enabled='repoopen', toolbar='sync')
         newnamed('Repository.push', data='push', icon='hg-push',
                  enabled='repoopen', toolbar='sync')
+        for action in self.synctbar.actions():  # excluding push:
+            menu = QMenu(self)
+            menu.addAction(newaction(_('&All Open Repositories'),
+                                     data=action.data() + '-all'))
+            tbb = self.synctbar.widgetForAction(action)
+            tbb.setMenu(menu)
         menuSync.addActions(self.synctbar.actions())
         menuSync.addSeparator()
 
@@ -877,9 +884,13 @@
 
     @pyqtSlot(QAction)
     def _runSyncAction(self, action):
-        w = self._currentRepoWidget()
-        if w:
-            op = str(action.data())
+        m = re.search("^(?P<op>[^\\-]+)(?P<all>-all)?$", action.data())
+        op = m.group("op")
+        all = m.group("all") is not None
+
+        ws = self._currentRepoWidgets(all)
+        for w in ws:
+            self.openRepo(w.repoRootPath(), True) # switch
             w.setSyncUrl(self._syncUrlFor(op) or '')
             getattr(w, op)()
 
@@ -995,6 +1006,17 @@
     def _currentRepoWidget(self):
         return self.repoTabsWidget.currentWidget()
 
+    def _currentRepoWidgets(self, all=False):
+        w = self._currentRepoWidget()
+        ws = []
+        if all:
+            ws = self.repoTabsWidget.widgets()
+            if w:
+                ws.remove(w)  # do current last
+        if w:
+            ws.append(w)
+        return ws
+
     def currentRepoRootPath(self):
         return self.repoTabsWidget.currentRepoRootPath()
 


Op zondag 11 april 2021 om 19:44:14 UTC+2 schreef KPOP:

Yuya Nishihara

unread,
Apr 12, 2021, 7:42:49 AM4/12/21
to thg...@googlegroups.com, KPOP
On Sun, 11 Apr 2021 11:54:14 -0700 (PDT), KPOP wrote:
> # HG changeset patch
> # User kpop
> # Date 1618167139 -7200
> # Sun Apr 11 20:52:19 2021 +0200
> # Node ID 9533513d9ecfbb7795429e871a3b8c71ea8a3277
> # Parent 04d18324d40e452b6c5f1eb3cdb598c6a96761ce
> Use delayed menu's to apply an action to all open repositories (#5709)

> --- a/tortoisehg/hgqt/workbench.py Fri Apr 09 22:38:34 2021 -0400
> +++ b/tortoisehg/hgqt/workbench.py Sun Apr 11 20:52:19 2021 +0200
> @@ -11,6 +11,7 @@
> from __future__ import absolute_import
>
> import os
> +import re
> import subprocess
> import sys
>
> @@ -394,6 +395,12 @@
> enabled='repoopen', toolbar='sync')
> newnamed('Repository.push', data='push', icon='hg-push',
> enabled='repoopen', toolbar='sync')
> + for action in self.synctbar.actions(): # excluding push:
> + menu = QMenu(self)
> + menu.addAction(newaction(_('&All Open Repositories'),
> + data=action.data() + '-all'))
> + tbb = self.synctbar.widgetForAction(action)
> + tbb.setMenu(menu)

Instead of populating "-all" actions dynamically, let's make them named
actions and add to both drop-down menu and the main Repository ->
Synchronize menu:

Incoming
Pull
Outgoing
Push
--
Pull All Open Repositories
Push All Open Repositories
...
--
Sync Bookmarks...

I'm skeptical about incoming/outgoing-all, but please feel free to add them
if you think they're useful.

> def _runSyncAction(self, action):
> - w = self._currentRepoWidget()
> - if w:
> - op = str(action.data())
> + m = re.search("^(?P<op>[^\\-]+)(?P<all>-all)?$", action.data())
> + op = m.group("op")
> + all = m.group("all") is not None

This kind of string hack is source of bug. Maybe you can use QActionGroup
to connect QAction instances to e.g. _runSyncActionsOfAllTabs(action) in
a similar way. Another option is to simply connect each action to separate
slot.

Other than that, the code looks good to me, thanks.

KPOP

unread,
Apr 12, 2021, 10:40:18 AM4/12/21
to TortoiseHg Developers
Thank you for the valuable feedback: adding the items in the menu makes total sense and, indeed, the code works but isn't that robust. I hadn't used the QActionGroups yet, but that might also improve the code; I'll look into it.
Op maandag 12 april 2021 om 13:42:49 UTC+2 schreef yu...@tcha.org:

KPOP

unread,
Apr 18, 2021, 3:58:37 AM4/18/21
to TortoiseHg Developers
# HG changeset patch
# User KPOP
# Date 1618732619 -7200
#      Sun Apr 18 09:56:59 2021 +0200
# Node ID 78b331b42fdec874d9cd202c5d1b5a42f78ef67e
# Parent  b4a1ab073d5990827d26b2fb64e874469edc2f53
Use delayed menu's to apply to action to all open repositories (#5709)

diff -r b4a1ab073d59 -r 78b331b42fde tortoisehg/hgqt/shortcutregistry.py
--- a/tortoisehg/hgqt/shortcutregistry.py Fri Apr 16 19:12:27 2021 -0400
+++ b/tortoisehg/hgqt/shortcutregistry.py Sun Apr 18 09:56:59 2021 +0200
@@ -113,10 +113,12 @@
     'Repository.pickRevision': (_('Pick...'), None),
     'Repository.pruneRevisions': (_('&Prune...'), None),
     'Repository.pull': (_('&Pull'), None),
+    'Repository.pullAllTabs': (_('Pull &All Tabs'), None),
     'Repository.pullToRevision': (_('Pull to here...'), None),
     'Repository.purge': (_('&Purge...'), None),
     'Repository.push': (_('P&ush'), None),
     'Repository.pushAll': (_('Push &All'), None),
+    'Repository.pushAllTabs': (_('Push A&ll Tabs'), None),
     'Repository.pushBranch': (_('Push Selected &Branch'), None),
     'Repository.pushToRevision': (_('Push to &Here'), None),
     'Repository.rebaseRevision': (_('&Rebase...'), None),
diff -r b4a1ab073d59 -r 78b331b42fde tortoisehg/hgqt/workbench.py
--- a/tortoisehg/hgqt/workbench.py Fri Apr 16 19:12:27 2021 -0400
+++ b/tortoisehg/hgqt/workbench.py Sun Apr 18 09:56:59 2021 +0200
@@ -386,15 +386,35 @@
         newnamed('Workbench.aboutQt', QApplication.aboutQt, menu='help')
         newnamed('Workbench.about', self.onAbout, menu='help', icon='thg')
 
+        syncActionGroup = QActionGroup(self)
+        syncActionGroup.triggered.connect(self._runSyncAction)
         newnamed('Repository.incoming', data='incoming', icon='hg-incoming',
-                 enabled='repoopen', toolbar='sync')
-        newnamed('Repository.pull', data='pull', icon='hg-pull',
-                 enabled='repoopen', toolbar='sync')
+                 enabled='repoopen', toolbar='sync', group=syncActionGroup)
+        pullAction = newnamed('Repository.pull', data='pull', icon='hg-pull',
+                              enabled='repoopen', toolbar='sync',
+                              group=syncActionGroup)
         newnamed('Repository.outgoing', data='outgoing', icon='hg-outgoing',
-                 enabled='repoopen', toolbar='sync')
-        newnamed('Repository.push', data='push', icon='hg-push',
-                 enabled='repoopen', toolbar='sync')
-        menuSync.addActions(self.synctbar.actions())
+                 enabled='repoopen', toolbar='sync', group=syncActionGroup)
+        pushAction = newnamed('Repository.push', data='push', icon='hg-push',
+                              enabled='repoopen', toolbar='sync',
+                              group=syncActionGroup)
+        menuSync.addActions(syncActionGroup.actions())
+        menuSync.addSeparator()
+
+        def addSyncActionMenu(parentAction, action):
+            tbb = self.synctbar.widgetForAction(parentAction)
+            menu = QMenu(self)
+            menu.addAction(action)
+            tbb.setMenu(menu)
+        syncAllTabsActionGroup = QActionGroup(self)
+        syncAllTabsActionGroup.triggered.connect(self._runSyncAllTabsAction)
+        addSyncActionMenu(pullAction, newnamed('Repository.pullAllTabs',
+                          data='pull', icon='hg-pull', enabled='repoopen',
+                          group=syncAllTabsActionGroup))
+        addSyncActionMenu(pushAction, newnamed('Repository.pushAllTabs',
+                          data='push', icon='hg-push', enabled='repoopen',
+                          group=syncAllTabsActionGroup))
+        menuSync.addActions(syncAllTabsActionGroup.actions())
         menuSync.addSeparator()
 
         action = QAction(self)
@@ -411,7 +431,6 @@
         self.urlComboAction = self.synctbar.addWidget(self.urlCombo)
         # hide it because workbench could be started without open repo
         self.urlComboAction.setVisible(False)
-        self.synctbar.actionTriggered.connect(self._runSyncAction)
 
     def _setupUrlCombo(self, repoagent):
         """repository has been switched, fill urlCombo with URLs"""
@@ -567,7 +586,7 @@
 
     def _addNewAction(self, text, slot=None, icon=None, shortcut=None,
                   checkable=False, tooltip=None, data=None, enabled=None,
-                  visible=None, menu=None, toolbar=None):
+                  visible=None, menu=None, toolbar=None, group=None):
         """Create new action and register it
 
         :slot: function called if action triggered or toggled.
@@ -611,15 +630,18 @@
             getattr(self, 'menu%s' % menu.title()).addAction(action)
         if toolbar:
             getattr(self, '%stbar' % toolbar).addAction(action)
+        if group:
+            group.addAction(action)
         return action
 
     def _addNewNamedAction(self, name, slot=None, icon=None, checkable=False,
                            tooltip=None, data=None, enabled=None,
-                           visible=None, menu=None, toolbar=None):
+                           visible=None, menu=None, toolbar=None, group=None):
         """Create new action and register it as user-configurable"""
         a = self._addNewAction('', slot=slot, icon=icon, checkable=checkable,
                                tooltip=tooltip, data=data, enabled=enabled,
-                               visible=visible, menu=menu, toolbar=toolbar)
+                               visible=visible, menu=menu, toolbar=toolbar,
+                               group=group)
         self._actionregistry.registerAction(name, a)
         return a
 
@@ -883,6 +905,14 @@
             w.setSyncUrl(self._syncUrlFor(op) or '')
             getattr(w, op)()
 
+    @pyqtSlot(QAction)
+    def _runSyncAllTabsAction(self, action):
+        originalIndex = self.repoTabsWidget.currentIndex()
+        for index in range(0, self.repoTabsWidget.count()):
+            self.repoTabsWidget.setCurrentIndex(index)
+            self._runSyncAction(action)
+        self.repoTabsWidget.setCurrentIndex(originalIndex)
+
     @pyqtSlot()
     def _runSyncBookmarks(self):
         w = self._currentRepoWidget()


Op maandag 12 april 2021 om 16:40:18 UTC+2 schreef KPOP:

Yuya Nishihara

unread,
Apr 19, 2021, 6:10:59 AM4/19/21
to thg...@googlegroups.com, KPOP
On Sun, 18 Apr 2021 00:58:37 -0700 (PDT), KPOP wrote:
> # HG changeset patch
> # User KPOP
> # Date 1618732619 -7200
> # Sun Apr 18 09:56:59 2021 +0200
> # Node ID 78b331b42fdec874d9cd202c5d1b5a42f78ef67e
> # Parent b4a1ab073d5990827d26b2fb64e874469edc2f53
> Use delayed menu's to apply to action to all open repositories (#5709)

Queued, thanks.

Next time, please consider using Email Patch dialog (or "hg email" command.)
Otherwise the patch content would get whitespace damaged by your email client.

> + @pyqtSlot(QAction)
> + def _runSyncAllTabsAction(self, action):
> + originalIndex = self.repoTabsWidget.currentIndex()
> + for index in range(0, self.repoTabsWidget.count()):
> + self.repoTabsWidget.setCurrentIndex(index)
> + self._runSyncAction(action)
> + self.repoTabsWidget.setCurrentIndex(originalIndex)

It's better to not switch the current tabs because various repository-dependent
UI state would be reset.

If this is intended to load all inactive tabs, maybe you can add loadAllRepos()
method to RepoTabWidget.

If this tries to switch the URL combobox, there's a fundamental problem that
no repository is guaranteed to have the path alias of the selected name.
It might be better to just runCommand(['push', alias_name]) for each repo
without using the sync widget, but I don't know.

KPOP

unread,
Apr 20, 2021, 2:07:00 AM4/20/21
to TortoiseHg Developers
Thanks again for the feedback and the guidance in getting me up to speed with development here.

Indeed: I switch tabs just to be sure to push/pull to the correct remote. I tried it without, but then the sync used the remote selected in the dropdown, which is from the active tab. But if that can be remedied by using runCommand, then I'm happy to give that a try. I must have overlooked the fact of inactive tabs, nice way of keeping the resources down.

Yuya Nishihara

unread,
Apr 20, 2021, 8:19:48 AM4/20/21
to thg...@googlegroups.com, KPOP
On Mon, 19 Apr 2021 23:07:00 -0700 (PDT), KPOP wrote:
> Indeed: I switch tabs just to be sure to push/pull to the correct remote. I
> tried it without, but then the sync used the remote selected in the
> dropdown, which is from the active tab.

Yep, and the alias name (e.g. "default") is resolved to URL there, which
can't be passed in to the other repositories.

> But if that can be remedied by
> using runCommand, then I'm happy to give that a try.

SyncWidget has some configured options to be appended to push/pull commands.
If you don't care about these options, runCommand() should work fine. I'm
not sure whether or not these options can/should be applied to bulk push/pull
operations.

KPOP

unread,
Jul 24, 2021, 3:00:31 AM7/24/21
to TortoiseHg Developers
This feature is available since TortoiseHG 5.8. I've tested this for several weeks and have found it adequate for my most common workflows. Also, several attempts to get an alternative solution using the runCommand required more drastic changes which I'm trying to avoid.
Reply all
Reply to author
Forward
0 new messages