Skip to content

Commit b88e603

Browse files
fix(ui): constrain tabs and preserve focus errors
- Prevent long editor and preview titles from monopolizing the tab bar while keeping their full text available through tooltips. - Preserve the SetFocus result and error code before asynchronous activation messages can overwrite diagnostic state.
1 parent 93bd1c4 commit b88e603

2 files changed

Lines changed: 39 additions & 9 deletions

File tree

src/DockedEditor.cpp

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,30 @@
2929
#include "ScintillaNext.h"
3030

3131
#include <QEvent>
32+
#include <QFontMetrics>
3233
#include <QTimer>
3334
#include <QUuid>
3435

3536

37+
namespace {
38+
39+
// Upper bound for a center tab's width. File and preview tabs (browser, embed,
40+
// file preview) can carry long titles; left uncapped a single tab expands to
41+
// its full text and monopolises the tab bar. Derived from the tab's own font so
42+
// it scales with the UI font / DPI rather than a hard-coded pixel value: ~28
43+
// average glyph widths plus fixed slack for the icon, close button and layout
44+
// margins. Cheap — one QFontMetrics query per tab creation, never on a hot path.
45+
int centerTabMaxWidth(const QWidget *tab)
46+
{
47+
const QFontMetrics fm(tab->font());
48+
constexpr int kTitleChars = 28;
49+
constexpr int kChromeSlack = 56; // icon + close button + margins
50+
return fm.averageCharWidth() * kTitleChars + kChromeSlack;
51+
}
52+
53+
} // namespace
54+
55+
3656
class DockedEditorComponentsFactory : public ads::CDockComponentsFactory
3757
{
3858
public:
@@ -260,8 +280,10 @@ void DockedEditor::addEditor(ScintillaNext *editor)
260280
// Create the dock widget for the editor
261281
ads::CDockWidget *dockWidget = dockManager->createDockWidget(editor->getName());
262282

263-
// Disable elide, elided file names not readable when lots of files opened
264-
dockWidget->tabWidget()->setElideMode(Qt::ElideNone);
283+
// Keep long file names readable via the tooltip without letting one tab
284+
// consume the entire tab bar.
285+
dockWidget->tabWidget()->setElideMode(Qt::ElideRight);
286+
dockWidget->tabWidget()->setMaximumWidth(centerTabMaxWidth(dockWidget->tabWidget()));
265287

266288
// We need a unique object name. Can't use the name or file path so use a uuid
267289
dockWidget->setObjectName(QUuid::createUuid().toString());
@@ -400,9 +422,17 @@ ads::CDockWidget *DockedEditor::addPreviewTab(QWidget *widget, const QString &ti
400422
dockWidget->setFeature(ads::CDockWidget::DockWidgetFeature::DockWidgetDeleteOnClose, true);
401423
dockWidget->setFeature(ads::CDockWidget::DockWidgetFeature::DockWidgetFloatable, false);
402424

403-
dockWidget->tabWidget()->setElideMode(Qt::ElideNone);
425+
// Preview tabs (browser/embed/file-preview) can carry long, dynamic titles
426+
// such as web page <title>s. Cap the tab width and elide from the right so a
427+
// single tab can't monopolise the tab bar; the full title stays reachable as
428+
// a hover tooltip, kept in sync with subsequent dock-title changes.
429+
auto *tab = dockWidget->tabWidget();
430+
tab->setElideMode(Qt::ElideRight);
431+
tab->setMaximumWidth(centerTabMaxWidth(tab));
432+
tab->setToolTip(title);
433+
connect(dockWidget, &QWidget::windowTitleChanged, tab, &QWidget::setToolTip);
404434
if (!icon.isNull())
405-
dockWidget->tabWidget()->setIcon(icon);
435+
tab->setIcon(icon);
406436

407437
latestDockArea = dockManager->addDockWidget(ads::CenterDockWidgetArea, dockWidget, currentDockArea());
408438

src/EmbeddedWindowWin32.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -326,23 +326,23 @@ bool focusForeign(quintptr targetHandle, quintptr token)
326326
// top-level window, so there is no separate top-level to SetActiveWindow.
327327
SetLastError(ERROR_SUCCESS);
328328
SetFocus(target);
329+
const bool focused = GetFocus() == target;
330+
const DWORD focusError = GetLastError();
329331
// Self-drawn apps (Godot, Chromium/Edge, ...) gate keyboard/IME input on
330332
// their OWN activation flag, which they update only from WM_NCACTIVATE /
331333
// WM_ACTIVATE. A WS_CHILD window never receives those (only top-levels do),
332334
// so after reparenting they believe they are inactive and silently drop
333-
// every keystroke even though GetFocus() already points at them (proven by
334-
// the diagnostic log: Godot/Edge keep GetActiveWindow on our Qt window).
335+
// every keystroke even when Win32 focus already points at them.
335336
// Synthesize activation to re-enable their input path. PostMessage (async) so
336337
// a hung foreign message loop can never block our GUI thread — matches
337338
// syncGeometry. lParam (the "other" window) is left 0; these apps ignore it.
338339
if (!PostMessageW(target, WM_NCACTIVATE, TRUE, 0))
339340
qWarning("EmbeddedWindow: WM_NCACTIVATE post failed (err=%lu)", GetLastError());
340341
if (!PostMessageW(target, WM_ACTIVATE, MAKEWPARAM(WA_ACTIVE, 0), 0))
341342
qWarning("EmbeddedWindow: WM_ACTIVATE post failed (err=%lu)", GetLastError());
342-
if (GetFocus() == target)
343+
if (focused)
343344
return true;
344-
const DWORD error = GetLastError();
345-
qWarning("EmbeddedWindow: SetFocus on foreign child failed (err=%lu)", error);
345+
qWarning("EmbeddedWindow: SetFocus on foreign child failed (err=%lu)", focusError);
346346
return false;
347347
#else
348348
Q_UNUSED(targetHandle) Q_UNUSED(token)

0 commit comments

Comments
 (0)