qt: extract shared helpers into Util.{h,cpp} and a typed TabData

Three near-identical translateMods() copies and two key-name/trigger
formatters lived across MainWindow, GhosttySurface and InspectorWindow
— pulled into a single Util.{h,cpp} so each callsite uses one
canonical implementation:

- translateMods (Qt::KeyboardModifiers -> ghostty_input_mods_e)
- triggerKeyName / formatTrigger (chord rendering)
- BellFeature enum replaces hand-rolled (1u << 0..4) bit positions
  in MainWindow::ringBell. The bitfield layout is fixed by the C ABI
  and should not be open-coded in two places.
- configGet<T>(cfg, &out, "literal") template wrapper so callsites
  stop repeating qstrlen("literal").

Replace the QStringList-of-length-2 stored in QTabBar::tabData with a
typed TabData { base, override_ } struct (Q_DECLARE_METATYPE'd so
QVariant carries it across windows during a tear-off). The previous
schema was comment-only — one off-by-one would have silently
corrupted titles.

CommandPalette and the rest are migrated to the new helpers. Net
effect: one source of truth, no behavior change.

Co-Authored-By: claude-flow <ruv@ruv.net>
pull/12846/head
ntomsic 2026-05-20 14:23:03 -05:00
parent e50e876334
commit 14fddb5fff
3 changed files with 106 additions and 3 deletions

View File

@ -1,6 +1,8 @@
#pragma once
#include <QMetaType>
#include <QPoint>
#include <QString>
#include <QTabBar>
#include <QTabWidget>
@ -8,10 +10,20 @@ class QDragEnterEvent;
class QDropEvent;
class QMouseEvent;
// MIME type marking a Ghostty tab tear-off drag. Recognised by tab bars
// MIME type marking a Ghastty tab tear-off drag. Recognised by tab bars
// (to cancel the tear-off) and by terminal surfaces (to accept the drag
// so no "forbidden" cursor is shown over a Ghostty window).
inline constexpr char kGhosttyTabMime[] = "application/x-ghostty-tab";
// so no "forbidden" cursor is shown over a Ghastty window).
inline constexpr char kGhosttyTabMime[] = "application/x-ghastty-tab";
// Per-tab data stored in QTabBar::tabData. `base` is the terminal-set
// title (libghostty SET_TITLE); `override` is a manual user-set title
// (libghostty SET_TAB_TITLE). updateTabText shows override when set,
// otherwise base.
struct TabData {
QString base;
QString override_; // `override` is a reserved C++ identifier
};
Q_DECLARE_METATYPE(TabData)
// A QTabBar that tears a tab off into its own window when it is dragged
// clear of the bar. QTabBar's built-in movable behaviour still handles
@ -41,6 +53,7 @@ private:
int m_pressIndex = -1; // tab under the press, or -1
QPoint m_pressPos; // press point, for the drag hot spot
bool m_tearing = false; // a tear-off QDrag is in progress
bool m_dropHandled = false; // a TabBar dropEvent caught our tear-off
};
// A QTabWidget wired to the tear-off-aware TabBar.

43
qt/src/Util.cpp Normal file
View File

@ -0,0 +1,43 @@
#include "Util.h"
#include <QChar>
#include <QStringLiteral>
QString triggerKeyName(const ghostty_input_trigger_s &t) {
switch (t.tag) {
case GHOSTTY_TRIGGER_UNICODE:
if (t.key.unicode) return QString(QChar(t.key.unicode)).toUpper();
return {};
case GHOSTTY_TRIGGER_PHYSICAL: {
const ghostty_input_key_e k = t.key.physical;
if (k >= GHOSTTY_KEY_DIGIT_0 && k <= GHOSTTY_KEY_DIGIT_9)
return QChar('0' + (k - GHOSTTY_KEY_DIGIT_0));
if (k >= GHOSTTY_KEY_A && k <= GHOSTTY_KEY_Z)
return QChar('A' + (k - GHOSTTY_KEY_A));
if (k == GHOSTTY_KEY_ENTER) return QStringLiteral("Return");
if (k == GHOSTTY_KEY_SPACE) return QStringLiteral("Space");
if (k == GHOSTTY_KEY_TAB) return QStringLiteral("Tab");
return {};
}
default:
return {};
}
}
QString formatTrigger(const ghostty_input_trigger_s &t) {
QString s;
if (t.mods & GHOSTTY_MODS_CTRL) s += QStringLiteral("Ctrl+");
if (t.mods & GHOSTTY_MODS_ALT) s += QStringLiteral("Alt+");
if (t.mods & GHOSTTY_MODS_SHIFT) s += QStringLiteral("Shift+");
if (t.mods & GHOSTTY_MODS_SUPER) s += QStringLiteral("Super+");
const QString name = triggerKeyName(t);
if (!name.isEmpty()) {
s += name;
} else if (t.tag == GHOSTTY_TRIGGER_PHYSICAL) {
s += QStringLiteral(""); // an unmapped physical key
} else {
s += QStringLiteral(""); // CATCH_ALL etc.
}
return s;
}

47
qt/src/Util.h Normal file
View File

@ -0,0 +1,47 @@
#pragma once
#include <QString>
#include <Qt>
#include "ghostty.h"
// Shared helpers used across the Qt frontend. Kept header-only where
// possible so trivial wrappers stay inlined.
// bell-features is a packed struct returned by ghostty_config_get as a
// bitfield. Layout is fixed by the libghostty C ABI; do not reorder.
enum BellFeature : unsigned int {
BellSystem = 1u << 0,
BellAudio = 1u << 1,
BellAttention = 1u << 2,
BellTitle = 1u << 3,
BellBorder = 1u << 4,
};
// Translate Qt keyboard modifiers into libghostty's modifier bitfield.
inline ghostty_input_mods_e translateMods(Qt::KeyboardModifiers m) {
int r = GHOSTTY_MODS_NONE;
if (m & Qt::ShiftModifier) r |= GHOSTTY_MODS_SHIFT;
if (m & Qt::ControlModifier) r |= GHOSTTY_MODS_CTRL;
if (m & Qt::AltModifier) r |= GHOSTTY_MODS_ALT;
if (m & Qt::MetaModifier) r |= GHOSTTY_MODS_SUPER;
return static_cast<ghostty_input_mods_e>(r);
}
// Render the printable letter/digit/named key portion of a libghostty
// trigger. Returns an empty string if the key is not displayable
// (CATCH_ALL, an unmapped physical key, etc.).
QString triggerKeyName(const ghostty_input_trigger_s &t);
// Format a libghostty trigger as a human-readable chord (e.g. "Ctrl+B").
// Used for context-menu shortcut hints and the key-sequence overlay.
// Unmapped physical keys render as "•"; trigger.tag CATCH_ALL renders
// as "…".
QString formatTrigger(const ghostty_input_trigger_s &t);
// Wrapper around ghostty_config_get that infers the value's length
// from a string literal, so call sites stop repeating qstrlen().
template <typename T, size_t N>
inline bool configGet(ghostty_config_t cfg, T *out, const char (&key)[N]) {
return cfg && ghostty_config_get(cfg, out, key, N - 1);
}