Caret on new workarea (part of comparison feature)
Pavel Sanda <[email protected]> Wed, 22 Jul 2026 16:49:46 +0200
| Newsgroups | gmane.editors.lyx.devel |
|---|---|
| Message-ID | <[email protected]> |
--3chXHPTr03MmBE4s
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Hi, continuing on comparison feature cleanup...
I guess this is mainly for JMarc.
There is a small long-term glitch when comparison feature is used together with
version control (comparing to older version of the same document, we have icons
for this in VC toolbar).
When the changes are very small the diff runs faster than comparison progress
dialog has even chance to appear.
The result: new "differences" buffer has no caret and does not respond to
keyboard, because the focus is gone.
I always need to go with mouse out of lyx window and back so that the "focus
follows mouse" policy of my WM forces lyx workarea to wake up. Not a super big
deal, but annoyance.
The cause is the mix of opening a new buffer while a dialog closes. Normally
dialog just returns focus to the work area that already had it. But here we
create a new work area and switch to it from within the dialog's 'finished'
handler, as the window-modal dialog is being hidden. Dialog closing restores
the focus of old work area, while we visually sit in the new one. So GUI is
blocked now.
The exact symptoms evolve over time, eg. for some time at least the dead caret
was visible, but even that stopped after yours 21dcb4782f (#12762 - Caret
re-appears when mouse hovers inset) long time ago.
I tried several different approaches how to fix it, but don't like any of them much to be honest:
- Forcing dialog in any case so normal focus handling is processed.
Structurally cleanest, lenghtier patch, forcing dialog activation can
backfire in untested WMs - and it did - in my first test my WM got crazy and
sent dialog with focus to completely new desktop ;)
- Artificially wait in comparison routine ~200ms or so, so the dialog shows on it's
own. Ugly heuristic, might not work in every setup, i didn't even properly test.
- Force the activation ourselves. activateWindow() alone (the polite "please
activate me") is not enough - my WM ignores it and the workarea stays dead -
so I had to use QApplication::setActiveWindow(), which forces Qt's own
activation without the WM.
Two things I don't like:
1) it has to be deferred with a QTimer single-shot otherwise finish handler
will undo it.
2) setActiveWindow() is deprecated in Qt6 (hence the suppression clause
around it), so we may need to revisit once Qt7 is out.
I converged to the opinion that last option is least bad from others, see attached.
Do you have some better idea?
Pavel
--3chXHPTr03MmBE4s
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="cmp.patch"
Show the caret in the comparison result under focus-follows-mouse
When comparing the current document against an earlier version (the
version-control comparison, LFUN_VC_COMPARE), the comparison is started
automatically as soon as its dialog is invoked, and the resulting
"differences" buffer is opened by switching to it from the dialog's
finished handler. Under a focus-follows-mouse policy, hiding that
(window-modal) dialog does not return input focus to the main window, so
the newly created work area never gets it: the caret is invisible (or,
before commit 21dcb4782f, drawn but dead) and the keyboard is
unresponsive until the pointer is moved out of and back into the window.
Force Qt's activation of the result window ourselves.
Several cleaner-looking approaches were tried and rejected:
- reverting the caret guard in resetCaret() (21dcb4782f): draws the caret
again but it stays dead (no blinking, no keyboard) because the window
still lacks focus -- it treats the symptom, not the cause;
- running the comparison in blocking mode: freezes the whole UI and
disables the Cancel button for the entire comparison, unacceptable for
large documents;
- keeping the dialog on screen for a moment (a delay) or deferring the
buffer switch: relies on window-manager timing and is fragile;
- deferring the close until the dialog is actually shown and activated:
requires the modal dialog to really map, which on some window managers
causes it to pop up on a different workspace and switch there.
So this deferred activation is the least-bad option: deterministic,
independent of window-manager behaviour, and leaving resetCaret() and the
responsive/cancelable comparison untouched.
Assisted-by: Claude Opus 4.8
---
diff --git a/src/frontends/qt/GuiCompare.cpp b/src/frontends/qt/GuiCompare.cpp
index 1a0a005adc..b765a4e9d7 100644
--- a/src/frontends/qt/GuiCompare.cpp
+++ b/src/frontends/qt/GuiCompare.cpp
@@ -21,6 +21,7 @@
#include "Compare.h"
#include "FuncRequest.h"
#include "GuiView.h"
+#include "GuiWorkArea.h"
#include "LyXRC.h"
#include "qt_helpers.h"
@@ -31,8 +32,10 @@
#include "support/FileName.h"
#include "support/gettext.h"
+#include <QApplication>
#include <QDialogButtonBox>
#include <QThread>
+#include <QTimer>
using namespace std;
@@ -228,6 +231,30 @@ void GuiCompare::finished(bool aborted)
dispatch(FuncRequest(LFUN_CHANGES_OUTPUT));
dispatch(FuncRequest(LFUN_CHANGES_TRACK));
}
+ // The comparison dialog is window-modal. Under a
+ // focus-follows-mouse policy, hiding it does not return
+ // input focus to the main window, so the caret in the
+ // freshly created differences buffer would stay hidden
+ // until the pointer re-enters the window. Re-activate that
+ // window ourselves. This has to be delayed until the event
+ // loop has processed the dialog being hidden, otherwise the
+ // pending deactivation undoes it again.
+ QTimer::singleShot(0, this, []() {
+ GuiView * view = guiApp->currentView();
+ GuiWorkArea * wa = view ? view->currentWorkArea() : nullptr;
+ if (!wa)
+ return;
+ // activateWindow() only asks the window manager, which a
+ // focus-follows-mouse policy may ignore; setActiveWindow()
+ // forces Qt's own activation. It is deprecated in Qt6 but
+ // remains the only way to do this without the window
+ // manager's cooperation.
+ QT_WARNING_PUSH
+ QT_WARNING_DISABLE_DEPRECATED
+ QApplication::setActiveWindow(view);
+ QT_WARNING_POP
+ wa->setFocus();
+ });
}
statusBar->showMessage(qt_("Finished"), 5000);
}
--3chXHPTr03MmBE4s
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
--
lyx-devel mailing list
[email protected]
https://lists.lyx.org/mailman/listinfo/lyx-devel
--3chXHPTr03MmBE4s--