mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
fix: per-group sentinel docks, editor inline-edit comment alignment
Sentinel dock refactored to per-tab-group model — each split group gets its own hidden sentinel so tab bars stay visible without the Hide event filter hack. Editor inline-edit comment column now anchors correctly for base-address edits and shows expression hint instead of generic text.
This commit is contained in:
@@ -2574,7 +2574,7 @@ bool RcxEditor::beginInlineEdit(EditTarget target, int line, int col) {
|
|||||||
m_editState.commentCol = cs.valid ? cs.start : -1;
|
m_editState.commentCol = cs.valid ? cs.start : -1;
|
||||||
m_editState.lastValidationOk = true; // original value is always valid
|
m_editState.lastValidationOk = true; // original value is always valid
|
||||||
} else if (target == EditTarget::BaseAddress) {
|
} 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 {
|
} else {
|
||||||
m_editState.commentCol = -1;
|
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)
|
// (comment padding is no longer baked into every line to avoid unnecessary scroll width)
|
||||||
if ((target == EditTarget::Value || target == EditTarget::BaseAddress)
|
if ((target == EditTarget::Value || target == EditTarget::BaseAddress)
|
||||||
&& m_editState.commentCol >= 0) {
|
&& m_editState.commentCol >= 0) {
|
||||||
int commentStart = norm.end + 2;
|
int commentStart = m_editState.commentCol;
|
||||||
int neededLen = commentStart + kColComment;
|
int commentWidth = (target == EditTarget::BaseAddress) ? 60 : kColComment;
|
||||||
|
int neededLen = commentStart + commentWidth;
|
||||||
int currentLen = (int)lineText.size();
|
int currentLen = (int)lineText.size();
|
||||||
if (currentLen < neededLen) {
|
if (currentLen < neededLen) {
|
||||||
int extend = neededLen - currentLen;
|
int extend = neededLen - currentLen;
|
||||||
@@ -3540,7 +3541,7 @@ void RcxEditor::setEditComment(const QString& comment) {
|
|||||||
|
|
||||||
// Place comment 2 spaces after current value, prefixed with //
|
// Place comment 2 spaces after current value, prefixed with //
|
||||||
int valueEnd = editEndCol();
|
int valueEnd = editEndCol();
|
||||||
int startCol = valueEnd + 2; // 2 spaces after value
|
int startCol = qMax(valueEnd + 2, m_editState.commentCol);
|
||||||
int endCol = lineText.size();
|
int endCol = lineText.size();
|
||||||
int availWidth = endCol - startCol;
|
int availWidth = endCol - startCol;
|
||||||
if (availWidth <= 0) { m_updatingComment = false; return; }
|
if (availWidth <= 0) { m_updatingComment = false; return; }
|
||||||
@@ -3589,7 +3590,12 @@ void RcxEditor::validateEditLive() {
|
|||||||
if (isValid) {
|
if (isValid) {
|
||||||
m_sci->markerDelete(m_editState.line, M_ERR);
|
m_sci->markerDelete(m_editState.line, M_ERR);
|
||||||
if (isSelected) m_sci->markerAdd(m_editState.line, M_SELECTED);
|
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. <mod.exe> + 0xFF | [0x1000 + 0x10] | 7ff6`1234ABCD"));
|
||||||
|
else
|
||||||
|
setEditComment("Enter=Save Esc=Cancel");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isSelected) m_sci->markerDelete(m_editState.line, M_SELECTED);
|
if (isSelected) m_sci->markerDelete(m_editState.line, M_SELECTED);
|
||||||
m_sci->markerAdd(m_editState.line, M_ERR);
|
m_sci->markerAdd(m_editState.line, M_ERR);
|
||||||
|
|||||||
123
src/main.cpp
123
src/main.cpp
@@ -605,24 +605,6 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
|
|||||||
createWorkspaceDock();
|
createWorkspaceDock();
|
||||||
createScannerDock();
|
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();
|
createMenus();
|
||||||
createStatusBar();
|
createStatusBar();
|
||||||
|
|
||||||
@@ -779,13 +761,22 @@ void MainWindow::createMenus() {
|
|||||||
// View
|
// View
|
||||||
auto* view = m_menuBar->addMenu("&View");
|
auto* view = m_menuBar->addMenu("&View");
|
||||||
Qt5Qt6AddAction(view, "&Reset Windows", QKeySequence::UnknownKey, QIcon(), this, [this](bool) {
|
Qt5Qt6AddAction(view, "&Reset Windows", QKeySequence::UnknownKey, QIcon(), this, [this](bool) {
|
||||||
// Re-tabify all doc docks into a single group
|
// Re-tabify all doc docks into a single group (collapses splits)
|
||||||
if (m_docDocks.size() < 2) return;
|
if (m_docDocks.isEmpty()) return;
|
||||||
auto* first = m_docDocks.first();
|
auto* first = m_docDocks.first();
|
||||||
for (int i = 1; i < m_docDocks.size(); ++i) {
|
for (int i = 1; i < m_docDocks.size(); ++i) {
|
||||||
tabifyDockWidget(first, m_docDocks[i]);
|
tabifyDockWidget(first, m_docDocks[i]);
|
||||||
m_docDocks[i]->show();
|
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();
|
if (m_activeDocDock) m_activeDocDock->raise();
|
||||||
QTimer::singleShot(0, this, [this]() { setupDockTabBars(); });
|
QTimer::singleShot(0, this, [this]() { setupDockTabBars(); });
|
||||||
});
|
});
|
||||||
@@ -1538,6 +1529,21 @@ QString MainWindow::tabTitle(const TabState& tab) const {
|
|||||||
return name;
|
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) {
|
QDockWidget* MainWindow::createTab(RcxDocument* doc) {
|
||||||
auto* splitter = new QSplitter(Qt::Horizontal);
|
auto* splitter = new QSplitter(Qt::Horizontal);
|
||||||
splitter->setHandleWidth(1);
|
splitter->setHandleWidth(1);
|
||||||
@@ -1658,19 +1664,22 @@ QDockWidget* MainWindow::createTab(RcxDocument* doc) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Tabify with existing doc docks, or add to top area
|
// Tabify with existing doc docks, or add to top area
|
||||||
if (!m_docDocks.isEmpty())
|
if (!m_docDocks.isEmpty()) {
|
||||||
tabifyDockWidget(m_docDocks.last(), dock);
|
tabifyDockWidget(m_docDocks.last(), dock);
|
||||||
else
|
} else {
|
||||||
addDockWidget(Qt::TopDockWidgetArea, dock);
|
addDockWidget(Qt::TopDockWidgetArea, dock);
|
||||||
|
// Deferred sentinel — must wait for Qt to finish laying out the
|
||||||
// Bootstrap: tabify the hidden sentinel with the first doc dock so Qt
|
// first doc dock before tabifyDockWidget can merge them into tabs.
|
||||||
// creates a QTabBar. Then hide sentinel (zero layout space). The event
|
QTimer::singleShot(0, this, [this, dock]() {
|
||||||
// filter in eventFilter() keeps the tab bar visible even at count==1.
|
if (!dock->isVisible()) return;
|
||||||
if (m_sentinelDock && m_docDocks.isEmpty()) {
|
// Check if this dock already has a sentinel (e.g. second createTab raced)
|
||||||
m_sentinelDock->show();
|
for (auto* td : tabifiedDockWidgets(dock))
|
||||||
tabifyDockWidget(dock, m_sentinelDock);
|
if (m_sentinelDocks.contains(static_cast<QDockWidget*>(td))) return;
|
||||||
m_sentinelDock->hide();
|
auto* sentinel = createSentinelDock();
|
||||||
dock->raise();
|
tabifyDockWidget(dock, sentinel);
|
||||||
|
dock->raise();
|
||||||
|
setupDockTabBars();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
m_docDocks.append(dock);
|
m_docDocks.append(dock);
|
||||||
@@ -1908,11 +1917,19 @@ void MainWindow::setupDockTabBars() {
|
|||||||
.arg(theme.background.name(), theme.border.name(), theme.hover.name()));
|
.arg(theme.background.name(), theme.border.name(), theme.hover.name()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force tab bar visible (event filter keeps it alive, belt-and-suspenders)
|
// Hide sentinel tabs so user sees only real doc tabs.
|
||||||
tabBar->show();
|
// 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
|
// Install tab buttons for any tab that doesn't have them yet
|
||||||
for (int i = 0; i < tabBar->count(); ++i) {
|
for (int i = 0; i < tabBar->count(); ++i) {
|
||||||
|
if (tabBar->tabText(i) == sentinelTitle)
|
||||||
|
continue;
|
||||||
auto* existing = qobject_cast<DockTabButtons*>(
|
auto* existing = qobject_cast<DockTabButtons*>(
|
||||||
tabBar->tabButton(i, QTabBar::RightSide));
|
tabBar->tabButton(i, QTabBar::RightSide));
|
||||||
if (existing) continue;
|
if (existing) continue;
|
||||||
@@ -1996,8 +2013,11 @@ void MainWindow::setupDockTabBars() {
|
|||||||
|
|
||||||
menu.addSeparator();
|
menu.addSeparator();
|
||||||
|
|
||||||
// New Document Groups (only if >1 tab)
|
// New Document Groups (only if >1 visible tab — excludes sentinels)
|
||||||
if (tabBar->count() > 1) {
|
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"),
|
menu.addAction(makeIcon(":/vsicons/split-horizontal.svg"),
|
||||||
"New Horizontal Document Group",
|
"New Horizontal Document Group",
|
||||||
[this, target]() {
|
[this, target]() {
|
||||||
@@ -2016,7 +2036,12 @@ void MainWindow::setupDockTabBars() {
|
|||||||
}
|
}
|
||||||
if (docks.size() >= 2)
|
if (docks.size() >= 2)
|
||||||
resizeDocks(docks, sizes, Qt::Horizontal);
|
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"),
|
menu.addAction(makeIcon(":/vsicons/split-vertical.svg"),
|
||||||
"New Vertical Document Group",
|
"New Vertical Document Group",
|
||||||
@@ -2036,7 +2061,12 @@ void MainWindow::setupDockTabBars() {
|
|||||||
}
|
}
|
||||||
if (docks.size() >= 2)
|
if (docks.size() >= 2)
|
||||||
resizeDocks(docks, sizes, Qt::Vertical);
|
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) {
|
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<QTabBar*>(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) {
|
if (event->type() == QEvent::MouseButtonPress) {
|
||||||
auto* me = static_cast<QMouseEvent*>(event);
|
auto* me = static_cast<QMouseEvent*>(event);
|
||||||
if (me->button() == Qt::MiddleButton) {
|
if (me->button() == Qt::MiddleButton) {
|
||||||
|
|||||||
@@ -120,10 +120,9 @@ private:
|
|||||||
QMap<QDockWidget*, TabState> m_tabs;
|
QMap<QDockWidget*, TabState> m_tabs;
|
||||||
QVector<QDockWidget*> m_docDocks; // ordered list for tabByIndex
|
QVector<QDockWidget*> m_docDocks; // ordered list for tabByIndex
|
||||||
QDockWidget* m_activeDocDock = nullptr; // tracks active document dock
|
QDockWidget* m_activeDocDock = nullptr; // tracks active document dock
|
||||||
QDockWidget* m_sentinelDock = nullptr; // hidden dock to bootstrap tab bar creation
|
QVector<QDockWidget*> m_sentinelDocks; // permanent sentinels for always-visible tab bars
|
||||||
QVector<RcxDocument*> m_allDocs; // all open docs, shared with controllers
|
QVector<RcxDocument*> m_allDocs; // all open docs, shared with controllers
|
||||||
bool m_closingAll = false; // guards spurious project_new during batch close
|
bool m_closingAll = false; // guards spurious project_new during batch close
|
||||||
bool m_tabBarShowGuard = false; // prevents recursion in event filter re-show
|
|
||||||
struct ClosingGuard {
|
struct ClosingGuard {
|
||||||
bool& flag;
|
bool& flag;
|
||||||
ClosingGuard(bool& f) : flag(f) { flag = true; }
|
ClosingGuard(bool& f) : flag(f) { flag = true; }
|
||||||
@@ -144,6 +143,7 @@ private:
|
|||||||
TabState* activeTab();
|
TabState* activeTab();
|
||||||
TabState* tabByIndex(int index);
|
TabState* tabByIndex(int index);
|
||||||
int tabCount() const { return m_tabs.size(); }
|
int tabCount() const { return m_tabs.size(); }
|
||||||
|
QDockWidget* createSentinelDock();
|
||||||
QDockWidget* createTab(RcxDocument* doc);
|
QDockWidget* createTab(RcxDocument* doc);
|
||||||
QString tabTitle(const TabState& tab) const;
|
QString tabTitle(const TabState& tab) const;
|
||||||
void setupDockTabBars();
|
void setupDockTabBars();
|
||||||
|
|||||||
Reference in New Issue
Block a user