diff --git a/src/editor.cpp b/src/editor.cpp index 2b313d1..81c2177 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -2574,7 +2574,7 @@ bool RcxEditor::beginInlineEdit(EditTarget target, int line, int col) { m_editState.commentCol = cs.valid ? cs.start : -1; m_editState.lastValidationOk = true; // original value is always valid } else if (target == EditTarget::BaseAddress) { - m_editState.commentCol = norm.end + 2; // command row has no column layout + m_editState.commentCol = (int)lineText.size() + 2; // after full command row content } else { m_editState.commentCol = -1; } @@ -2590,8 +2590,9 @@ bool RcxEditor::beginInlineEdit(EditTarget target, int line, int col) { // (comment padding is no longer baked into every line to avoid unnecessary scroll width) if ((target == EditTarget::Value || target == EditTarget::BaseAddress) && m_editState.commentCol >= 0) { - int commentStart = norm.end + 2; - int neededLen = commentStart + kColComment; + int commentStart = m_editState.commentCol; + int commentWidth = (target == EditTarget::BaseAddress) ? 60 : kColComment; + int neededLen = commentStart + commentWidth; int currentLen = (int)lineText.size(); if (currentLen < neededLen) { int extend = neededLen - currentLen; @@ -3540,7 +3541,7 @@ void RcxEditor::setEditComment(const QString& comment) { // Place comment 2 spaces after current value, prefixed with // int valueEnd = editEndCol(); - int startCol = valueEnd + 2; // 2 spaces after value + int startCol = qMax(valueEnd + 2, m_editState.commentCol); int endCol = lineText.size(); int availWidth = endCol - startCol; if (availWidth <= 0) { m_updatingComment = false; return; } @@ -3589,7 +3590,12 @@ void RcxEditor::validateEditLive() { if (isValid) { m_sci->markerDelete(m_editState.line, M_ERR); if (isSelected) m_sci->markerAdd(m_editState.line, M_SELECTED); - if (stateChanged) setEditComment("Enter=Save Esc=Cancel"); + if (stateChanged) { + if (m_editState.target == EditTarget::BaseAddress) + setEditComment(QStringLiteral("e.g. + 0xFF | [0x1000 + 0x10] | 7ff6`1234ABCD")); + else + setEditComment("Enter=Save Esc=Cancel"); + } } else { if (isSelected) m_sci->markerDelete(m_editState.line, M_SELECTED); m_sci->markerAdd(m_editState.line, M_ERR); diff --git a/src/main.cpp b/src/main.cpp index b9852a7..23fde32 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -605,24 +605,6 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) { createWorkspaceDock(); createScannerDock(); - // Hidden sentinel dock — never visible, only used to force Qt to create a - // QTabBar when the first document dock is added (Qt only creates tab bars - // via tabifyDockWidget). Immediately hidden after tabification so it takes - // zero layout space. An event filter on the QTabBar keeps it visible. - { - m_sentinelDock = new QDockWidget(this); - m_sentinelDock->setObjectName(QStringLiteral("_sentinel")); - m_sentinelDock->setFeatures(QDockWidget::NoDockWidgetFeatures); - auto* sw = new QWidget(m_sentinelDock); - sw->setFixedSize(0, 0); - m_sentinelDock->setWidget(sw); - auto* stb = new QWidget(m_sentinelDock); - stb->setFixedHeight(0); - m_sentinelDock->setTitleBarWidget(stb); - addDockWidget(Qt::TopDockWidgetArea, m_sentinelDock); - m_sentinelDock->hide(); // hidden = zero layout space - } - createMenus(); createStatusBar(); @@ -779,13 +761,22 @@ void MainWindow::createMenus() { // View auto* view = m_menuBar->addMenu("&View"); Qt5Qt6AddAction(view, "&Reset Windows", QKeySequence::UnknownKey, QIcon(), this, [this](bool) { - // Re-tabify all doc docks into a single group - if (m_docDocks.size() < 2) return; + // Re-tabify all doc docks into a single group (collapses splits) + if (m_docDocks.isEmpty()) return; auto* first = m_docDocks.first(); for (int i = 1; i < m_docDocks.size(); ++i) { tabifyDockWidget(first, m_docDocks[i]); m_docDocks[i]->show(); } + // Merge all sentinels back; keep only the first, delete extras + for (int i = 0; i < m_sentinelDocks.size(); ++i) { + if (i == 0) + tabifyDockWidget(first, m_sentinelDocks[i]); + else + delete m_sentinelDocks[i]; + } + if (m_sentinelDocks.size() > 1) + m_sentinelDocks.resize(1); if (m_activeDocDock) m_activeDocDock->raise(); QTimer::singleShot(0, this, [this]() { setupDockTabBars(); }); }); @@ -1538,6 +1529,21 @@ QString MainWindow::tabTitle(const TabState& tab) const { return name; } +// Create a sentinel dock — invisible tab that keeps Qt's tab bar on-screen +// when only 1 real dock remains in a group. +QDockWidget* MainWindow::createSentinelDock() { + auto* sentinel = new QDockWidget(this); + sentinel->setObjectName(QStringLiteral("_sentinel_%1").arg(quintptr(sentinel), 0, 16)); + sentinel->setFeatures(QDockWidget::NoDockWidgetFeatures); + sentinel->setWidget(new QWidget(sentinel)); + auto* stb = new QWidget(sentinel); + stb->setFixedHeight(0); + sentinel->setTitleBarWidget(stb); + sentinel->setWindowTitle(QStringLiteral("\u200B")); + m_sentinelDocks.append(sentinel); + return sentinel; +} + QDockWidget* MainWindow::createTab(RcxDocument* doc) { auto* splitter = new QSplitter(Qt::Horizontal); splitter->setHandleWidth(1); @@ -1658,19 +1664,22 @@ QDockWidget* MainWindow::createTab(RcxDocument* doc) { }); // Tabify with existing doc docks, or add to top area - if (!m_docDocks.isEmpty()) + if (!m_docDocks.isEmpty()) { tabifyDockWidget(m_docDocks.last(), dock); - else + } else { addDockWidget(Qt::TopDockWidgetArea, dock); - - // Bootstrap: tabify the hidden sentinel with the first doc dock so Qt - // creates a QTabBar. Then hide sentinel (zero layout space). The event - // filter in eventFilter() keeps the tab bar visible even at count==1. - if (m_sentinelDock && m_docDocks.isEmpty()) { - m_sentinelDock->show(); - tabifyDockWidget(dock, m_sentinelDock); - m_sentinelDock->hide(); - dock->raise(); + // Deferred sentinel — must wait for Qt to finish laying out the + // first doc dock before tabifyDockWidget can merge them into tabs. + QTimer::singleShot(0, this, [this, dock]() { + if (!dock->isVisible()) return; + // Check if this dock already has a sentinel (e.g. second createTab raced) + for (auto* td : tabifiedDockWidgets(dock)) + if (m_sentinelDocks.contains(static_cast(td))) return; + auto* sentinel = createSentinelDock(); + tabifyDockWidget(dock, sentinel); + dock->raise(); + setupDockTabBars(); + }); } m_docDocks.append(dock); @@ -1908,11 +1917,19 @@ void MainWindow::setupDockTabBars() { .arg(theme.background.name(), theme.border.name(), theme.hover.name())); } - // Force tab bar visible (event filter keeps it alive, belt-and-suspenders) - tabBar->show(); + // Hide sentinel tabs so user sees only real doc tabs. + // Qt's updateTabBar() rebuilds tabs each layout pass, resetting + // visibility, so we must re-hide every call. + static const QString sentinelTitle = QStringLiteral("\u200B"); + for (int i = 0; i < tabBar->count(); ++i) { + if (tabBar->tabText(i) == sentinelTitle) + tabBar->setTabVisible(i, false); + } // Install tab buttons for any tab that doesn't have them yet for (int i = 0; i < tabBar->count(); ++i) { + if (tabBar->tabText(i) == sentinelTitle) + continue; auto* existing = qobject_cast( tabBar->tabButton(i, QTabBar::RightSide)); if (existing) continue; @@ -1996,8 +2013,11 @@ void MainWindow::setupDockTabBars() { menu.addSeparator(); - // New Document Groups (only if >1 tab) - if (tabBar->count() > 1) { + // New Document Groups (only if >1 visible tab — excludes sentinels) + int visibleTabs = 0; + for (int i = 0; i < tabBar->count(); ++i) + if (tabBar->isTabVisible(i)) ++visibleTabs; + if (visibleTabs > 1) { menu.addAction(makeIcon(":/vsicons/split-horizontal.svg"), "New Horizontal Document Group", [this, target]() { @@ -2016,7 +2036,12 @@ void MainWindow::setupDockTabBars() { } if (docks.size() >= 2) resizeDocks(docks, sizes, Qt::Horizontal); - QTimer::singleShot(0, this, [this]() { setupDockTabBars(); }); + QTimer::singleShot(0, this, [this, target]() { + auto* s = createSentinelDock(); + tabifyDockWidget(target, s); + target->raise(); + QTimer::singleShot(0, this, [this]() { setupDockTabBars(); }); + }); }); menu.addAction(makeIcon(":/vsicons/split-vertical.svg"), "New Vertical Document Group", @@ -2036,7 +2061,12 @@ void MainWindow::setupDockTabBars() { } if (docks.size() >= 2) resizeDocks(docks, sizes, Qt::Vertical); - QTimer::singleShot(0, this, [this]() { setupDockTabBars(); }); + QTimer::singleShot(0, this, [this, target]() { + auto* s = createSentinelDock(); + tabifyDockWidget(target, s); + target->raise(); + QTimer::singleShot(0, this, [this]() { setupDockTabBars(); }); + }); }); } @@ -2046,25 +2076,6 @@ void MainWindow::setupDockTabBars() { } bool MainWindow::eventFilter(QObject* obj, QEvent* event) { - // Keep dock tab bars visible even when Qt wants to hide them (count==1). - // Qt's QMainWindowLayout calls setVisible(false) on the QTabBar when only - // one dock remains in a tab group. We catch the resulting Hide event and - // immediately re-show the tab bar, provided at least one doc dock is docked. - if (event->type() == QEvent::Hide && !m_tabBarShowGuard) { - if (auto* tabBar = qobject_cast(obj)) { - if (tabBar->parent() == this && tabBar->count() >= 1) { - bool hasDockedDoc = false; - for (auto* d : m_docDocks) - if (!d->isFloating() && d->isVisible()) { hasDockedDoc = true; break; } - if (hasDockedDoc) { - m_tabBarShowGuard = true; - tabBar->show(); - m_tabBarShowGuard = false; - return true; - } - } - } - } if (event->type() == QEvent::MouseButtonPress) { auto* me = static_cast(event); if (me->button() == Qt::MiddleButton) { diff --git a/src/mainwindow.h b/src/mainwindow.h index 6099cc6..960d232 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -120,10 +120,9 @@ private: QMap m_tabs; QVector m_docDocks; // ordered list for tabByIndex QDockWidget* m_activeDocDock = nullptr; // tracks active document dock - QDockWidget* m_sentinelDock = nullptr; // hidden dock to bootstrap tab bar creation + QVector m_sentinelDocks; // permanent sentinels for always-visible tab bars QVector m_allDocs; // all open docs, shared with controllers bool m_closingAll = false; // guards spurious project_new during batch close - bool m_tabBarShowGuard = false; // prevents recursion in event filter re-show struct ClosingGuard { bool& flag; ClosingGuard(bool& f) : flag(f) { flag = true; } @@ -144,6 +143,7 @@ private: TabState* activeTab(); TabState* tabByIndex(int index); int tabCount() const { return m_tabs.size(); } + QDockWidget* createSentinelDock(); QDockWidget* createTab(RcxDocument* doc); QString tabTitle(const TabState& tab) const; void setupDockTabBars();