Merge lp:~craighewetson-deactivatedaccount/bzr-explorer/shorten-refresh-time into lp:bzr-explorer

Proposed by Craig Hewetson on 2010-02-08
Status: Rejected
Rejected by: Alexander Belchenko on 2010-09-28
Proposed branch: lp:~craighewetson-deactivatedaccount/bzr-explorer/shorten-refresh-time
Merge into: lp:bzr-explorer
Diff against target: 127 lines (+57/-4)
3 files modified
lib/custom_dialogs.py (+10/-0)
lib/explorer.py (+41/-4)
lib/explorer_preferences.py (+6/-0)
To merge this branch: bzr merge lp:~craighewetson-deactivatedaccount/bzr-explorer/shorten-refresh-time
Reviewer Review Type Date Requested Status
Alexander Belchenko 2010-02-08 Needs Information on 2010-09-04
Craig Hewetson (community) Resubmit on 2010-09-03
Review via email: mp+18822@code.launchpad.net

Description of the change

After every action in bzr explorer the working tree gets refreshed and for large trees this makes the ui rather unresponsive. So instead of initiating a refresh of the working tree
after the user closes an action's dialog. The refresh will take place once the action has completed. If this is done the time between the user realizing that the action has completed
and when user closes the dialog can now be used for refreshing the working tree.
This means the user will effectively have to wait less for refreshes of the working tree and hence the app will feel more responsive for working larger trees.

To post a comment you must log in.

This feature comes from the email discussion on how to reduce the time taken from when a qbzr dialog is closed to when the working tree is refreshed.

email subject:
Bzr-Explorer: Embedding status output after executing qbzr dialogs.

The only dialogs I didn't do is:
qconflicts, pull, push and merge.

My only problem i have is that i would like to call self.do_refresh() from a separate Thread. Because the dialog now takes longer to "complete" since its holding the ui to do the refresh work.

I'm not a python pro so is there any way to call the do_refresh() method from a Thread so that the ui does not get held up.
I've tried but I get all sorts of errors.
(QObject: Cannot create children for a parent that is in a different thread.
(Parent is QTextDocument(0x9cb6b18), parent's thread is QThread(0x98f7f00), current thread is QThread(0x9e58db8))

In java I'll easily be able to do this, but thats just because I know java better.

Alexander Belchenko (bialix) wrote :

Craig Hewetson пишет:
> My only problem i have is that i would like to call self.do_refresh() from a separate Thread. Because the dialog now takes longer to "complete" since its holding the ui to do the refresh work.

In PyQt you should not call methods directly but instead you should send
a signal to do what you need. Signals are asynchronous, and it won't
block your dialog.

>
>
> In PyQt you should not call methods directly but instead you should send
> a signal to do what you need. Signals are asynchronous, and it won't
> block your dialog.
>
>

It seems the bzr explorer already listeners to the finished() signal when
showing the dialog. The following code snippet comes from explorer.py line
1388 to 1396:

                    if finisher:
                        # We keep track of the most recently opened dialog
for
                        # each command-id so we can find it during the
finisher.
                        self._custom_modeless_dialogs[cmd_id] = dialog
                        # NOTE: This isn't actually the close event but the
                        # subprocess competion event. We may want to tweak
                        # that in the future.
                        self.connect(dialog.process_widget,
                            QtCore.SIGNAL("finished()"), finisher)

I assume from this that the finisher method gets called when the
"finished()" signal is sent. But the dialog seems to get blocked i.e the OK
and Cancel buttons take a while before they get replaced with the "Close"
button.

Its very likely that I'm missing something here...

Alexander Belchenko (bialix) wrote :

> This feature comes from the email discussion on how to reduce the time taken
> from when a qbzr dialog is closed to when the working tree is refreshed.
>
> email subject:
> Bzr-Explorer: Embedding status output after executing qbzr dialogs.

The discussion is here: http://groups.google.com/group/qbzr/browse_thread/thread/274cf3859382f928#

Alexander Belchenko (bialix) wrote :

What is the status of this merge proposal today?

review: Needs Information

This branch isn't ready yet, I would have to find some time to look at it
again. I think i had an issue with the "finished()" signal... it was being
called too late

On Tue, Aug 24, 2010 at 7:57 PM, Alexander Belchenko <email address hidden> wrote:

> Review: Needs Information
> What is the status of this merge proposal today?
> --
>
> https://code.launchpad.net/~craighewetson/bzr-explorer/shorten-refresh-time/+merge/18822
> You are the owner of lp:~craighewetson/bzr-explorer/shorten-refresh-time.
>

I've found a problem and it has to do with qbzr.

The SubProcessWindowBase class has 3 buttons (ok, cancel and close) and these buttons change state or get hidden/shown when the "subprocessFinished(bool)" signal gets emitted which only get emitted after the "finished()" signal gets emitted.

The problem is that when I connect with the "finished()" from bzr-explorer to run a refresh of the working tree. But I want the "close" button to show BEFORE my refresh work takes place. Currently it only gets shown AFTER the refresh has completed thus the dialog stays open while the working tree gets refreshed and this defeats the purpose of my feature.

I found a workaround to this and it requires me to change the do_finish method in SubProcessWindowBase to look the like the following:

    def on_finished(self):
        if hasattr(self, 'setResult'):
            self.setResult(QtGui.QDialog.Accepted)

        self.emit(QtCore.SIGNAL("subprocessFinished(bool)"), True)
        self.emit(QtCore.SIGNAL("disableUi(bool)"), False)

        self.return_code = 0

        if not self.ui_mode:
            self.close()
        else: # This is where the change happens
            self.okButton.setHidden(True)
            self.closeButton.setHidden(False)

NOTE: I explicitly change the state of the buttons while not solely relying on the "subprocessFinished(bool)" signal to change the buttons states... Also it would mean that the buttons would have to be accessible from the method. okButton => self.okButton

This does feel like a hack so if there is a better suggestion please let me know ...

Alexander Belchenko (bialix) wrote :

See related proposal where Simon did similar changes (okButton => self.okButton) https://code.launchpad.net/~simon-kersey/qbzr/590229-fix/+merge/33901

Alexander Belchenko (bialix) wrote :

> This does feel like a hack so if there is a better suggestion please let me know ...

What do you mean by "this"?

The idea of directly hiding and showing buttons and allowing the signal to
be emitted to do the same thing.
BUT, based on the comments on Simon's merge proposal and with me rethinking
my comment, I think its probably not a hack.

So I'll make the change and propose a merge...
Thanks

This patch will still will perform better when the code changes from this branch:
lp:~craighewetson/qbzr/subprocess_buttons_statechange
is merged into the qbzr project.

Note these changes can still be merged into bzr-explorer without the qbzr changes, because it will only result in the user having to wait for the subprocess dialog's close button getting enabled while the working tree is refreshed.

Once the qbzr change is merged the dialog can be closed as soon as the subprocess is finished and even while the refresh is taking place.

review: Resubmit
Alexander Belchenko (bialix) wrote :

Craig, why you voted "Resubmit"? In Launchpad terms it means you asking yourself to rework the patch. Instead you should change status of proposal from "Work In Progress" to "Needs Review".

Anyway, I need something to put into NEWS file about this improvement.

Also, your changes for QBzr will be merged into QBzr 0.20. If it helps you can check the version of QBzr and don't block it if QBzr version < 0.20?

Another question: while Close button is not active user should still be able to close the dialog with Alt+F4 or Close icon in the dialog title. Can you check that it still works?

review: Needs Fixing
Alexander Belchenko (bialix) wrote :

Craig, I think I know how to not block Close button.
You should not execute self.do_refresh() immediately, instead you should send additional signal to yourself with

QtCore.QTimer.singleShot(1, self.do_refresh)

The timer will start refresh once all other signals will be processed, as I understand. So buttons should be activated and won't blocked by refresh.

Craig, please test this change and say if it works as expected without patching QBzr.

review: Needs Information
Alexander Belchenko (bialix) wrote :

@Craig, we need to decide something about this patch.

Hi Alexander
The problem is that none of the solutions actually freed up the UI thread enough to allow the user to close the dialog while the refresh is taking place. So to do the refresh after the dialog closes (currently happens in released version) is probably better than doing the refresh before its closed (this is what currently happens in this branch).

I don't think we can use this patch now. Until we can get bzr-explorer to call "refresh" (access bzrlib) from another thread other than the UI Thread.

Alexander Belchenko (bialix) wrote :

I'm marking this merge proposal as Rejected for now, in hope we invent better solution later.

Unmerged revisions

486. By Craig Hewetson on 2010-09-03

Refresh the bzr-explorer working tree once the "custom" qbzr dialogs have completed their specific operations.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/custom_dialogs.py'
2--- lib/custom_dialogs.py 2010-02-04 08:55:56 +0000
3+++ lib/custom_dialogs.py 2010-09-03 21:53:39 +0000
4@@ -36,6 +36,11 @@
5 import workspace_dialogs
6 import checkout_dialog
7 import switch_dialog
8+from bzrlib.plugins.qbzr.lib.update import QBzrUpdateWindow
9+from bzrlib.plugins.qbzr.lib.add import AddWindow
10+from bzrlib.plugins.qbzr.lib.commit import CommitWindow
11+from bzrlib.plugins.qbzr.lib.revert import RevertWindow
12+from bzrlib.plugins.qbzr.lib.uncommit import QBzrUncommitWindow
13
14 class CustomDialogRegistry(registry.Registry):
15 """Registry for custom dialogs.
16@@ -54,3 +59,8 @@
17 checkout_dialog.QCheckoutExplorerStyleDialog)
18 custom_dialog_registry.register('switch',
19 switch_dialog.QSwitchExplorerStyleDialog)
20+custom_dialog_registry.register('update', QBzrUpdateWindow)
21+custom_dialog_registry.register('add', AddWindow)
22+custom_dialog_registry.register('commit', CommitWindow)
23+custom_dialog_registry.register('revert', RevertWindow)
24+custom_dialog_registry.register('uncommit', QBzrUncommitWindow)
25
26=== modified file 'lib/explorer.py'
27--- lib/explorer.py 2010-08-24 17:34:38 +0000
28+++ lib/explorer.py 2010-09-03 21:53:39 +0000
29@@ -26,6 +26,7 @@
30 trace,
31 urlutils,
32 )
33+from bzrlib.workingtree import WorkingTree
34 from bzrlib.commands import shlex_split_unicode
35 from bzrlib.plugins import explorer
36 from bzrlib.plugins.explorer.lib import (
37@@ -94,12 +95,11 @@
38
39 # Actions that should automatically refresh the page on successful completion
40 _AUTO_REFRESH_CMDS = [
41- 'add', 'commit',
42- 'pull', 'merge', 'update', 'push', 'send',
43- 'conflicts', 'switch', 'bind', 'unbind', 'revert', 'uncommit',
44- # This one really ought to be handled by a custom dialog so that the new
45+ 'send', 'bind', 'unbind', 'conflicts', 'pull', 'merge', 'push',
46+ # These really ought to be handled by a custom dialog so that the new
47 # location can be opened. Refreshing the current page (often the
48 # repository view) is better than nothing though.
49+ 'add', 'commit', 'switch', 'update', 'revert', 'uncommit',
50 'branch',
51 ]
52
53@@ -1430,6 +1430,11 @@
54 'branch': (self._start_branch, False, self._finish_branch),
55 'checkout': (self._start_checkout, False, self._finish_checkout),
56 'switch': (self._start_switch, False, self._finish_switch),
57+ 'update': (self._start_update, False, self._finish_do_refresh),
58+ 'add': (self._start_add, False, self._finish_do_refresh),
59+ 'commit': (self._start_commit, False, self._finish_do_refresh),
60+ 'revert': (self._start_revert, False, self._finish_do_refresh),
61+ 'uncommit': (self._start_uncommit, False, self._finish_do_refresh),
62 }
63 if cmd_id in internal_handlers and cmd_id in custom_dialog_registry:
64 if self.dry_run:
65@@ -1497,6 +1502,38 @@
66 new_location = osutils.abspath(to_location)
67 self.open_location(new_location)
68
69+ def _start_update(self, context=None):
70+ location = context.get('branch_root')
71+ tree = WorkingTree.open_containing(location)[0]
72+ dialog = custom_dialog_registry.get('update')
73+ return dialog(tree, parent=self)
74+
75+ def _start_add(self, context=None):
76+ location = context.get('branch_root')
77+ tree = WorkingTree.open_containing(location)[0]
78+ dialog = custom_dialog_registry.get('add')
79+ return dialog(tree, None, parent=self)
80+
81+ def _start_commit(self, context=None):
82+ location = context.get('branch_root')
83+ tree = WorkingTree.open_containing(location)[0]
84+ dialog = custom_dialog_registry.get('commit')
85+ return dialog(tree, None, parent=self)
86+
87+ def _start_revert(self, context=None):
88+ location = context.get('branch_root')
89+ tree = WorkingTree.open_containing(location)[0]
90+ dialog = custom_dialog_registry.get('revert')
91+ return dialog(tree, None, parent=self)
92+
93+ def _start_uncommit(self, context=None):
94+ location = context.get('branch_root')
95+ dialog = custom_dialog_registry.get('uncommit')
96+ return dialog(location, parent=self)
97+
98+ def _finish_do_refresh(self):
99+ self.do_refresh()
100+
101 def _start_checkout(self, context=None):
102 return custom_dialog_registry.get('checkout')(parent=self)
103
104
105=== modified file 'lib/explorer_preferences.py'
106--- lib/explorer_preferences.py 2010-08-06 15:08:29 +0000
107+++ lib/explorer_preferences.py 2010-09-03 21:53:39 +0000
108@@ -43,6 +43,11 @@
109 "custom-dialog-branch": True,
110 "custom-dialog-checkout": True,
111 "custom-dialog-switch": True,
112+ "custom-dialog-update": True,
113+ "custom-dialog-add": True,
114+ "custom-dialog-commit": True,
115+ "custom-dialog-revert": True,
116+ "custom-dialog-uncommit": True,
117 "advanced-commands-terminal": False,
118 "workingtree-style": "qbrowse",
119 "workspace-model": "feature-branches",
120@@ -65,6 +70,7 @@
121 "custom-dialog-branch",
122 "custom-dialog-checkout",
123 "custom-dialog-switch",
124+ "custom-dialog-update",
125 "advanced-commands-terminal",
126 "bind-branches-by-default",
127 "toolbox-on-status-view",

Subscribers

People subscribed via source and target branches