mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Compare commits
9 Commits
msvc-fix-2
...
snapshot-0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ab6affa5e | ||
|
|
35b3cd9ac1 | ||
|
|
e5938f7e82 | ||
|
|
03c49d19dd | ||
|
|
b7eebedf50 | ||
|
|
9ff456a8d6 | ||
|
|
580f285edd | ||
|
|
d23a6c7656 | ||
|
|
25d8de95b7 |
@@ -346,11 +346,6 @@ if(BUILD_TESTING)
|
|||||||
endif()
|
endif()
|
||||||
add_test(NAME test_controller COMMAND test_controller)
|
add_test(NAME test_controller COMMAND test_controller)
|
||||||
|
|
||||||
add_executable(grab_tabs tests/grab_tabs.cpp
|
|
||||||
src/themes/theme.cpp src/themes/thememanager.cpp src/resources.qrc)
|
|
||||||
target_include_directories(grab_tabs PRIVATE src)
|
|
||||||
target_link_libraries(grab_tabs PRIVATE ${QT}::Widgets ${QT}::Svg ${QT}::Test)
|
|
||||||
|
|
||||||
add_executable(test_validation tests/test_validation.cpp
|
add_executable(test_validation tests/test_validation.cpp
|
||||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
||||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||||
|
|||||||
@@ -72,7 +72,8 @@
|
|||||||
<AdditionalDependencies>dwmapi.lib;dbghelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
<AdditionalDependencies>dwmapi.lib;dbghelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
</Link>
|
</Link>
|
||||||
<PostBuildEvent>
|
<PostBuildEvent>
|
||||||
<Command>$(QtToolsPath)/windeployqt $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).exe</Command>
|
<Command>$(QtToolsPath)/windeployqt $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).exe
|
||||||
|
xcopy /Y /I "$(SolutionDir)..\src\examples\*.rcx" "$(SolutionDir)$(Platform)\$(Configuration)\examples\"</Command>
|
||||||
</PostBuildEvent>
|
</PostBuildEvent>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
@@ -84,7 +85,8 @@
|
|||||||
<PreprocessorDefinitions>NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions>NOMINMAX;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<PostBuildEvent>
|
<PostBuildEvent>
|
||||||
<Command>$(QtToolsPath)/windeployqt $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).exe</Command>
|
<Command>$(QtToolsPath)/windeployqt $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName).exe
|
||||||
|
xcopy /Y /I "$(SolutionDir)..\src\examples\*.rcx" "$(SolutionDir)$(Platform)\$(Configuration)\examples\"</Command>
|
||||||
</PostBuildEvent>
|
</PostBuildEvent>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'" Label="Configuration">
|
||||||
|
|||||||
@@ -231,17 +231,7 @@ void RcxController::connectEditor(RcxEditor* editor) {
|
|||||||
TypePopupMode mode = TypePopupMode::FieldType;
|
TypePopupMode mode = TypePopupMode::FieldType;
|
||||||
if (target == EditTarget::ArrayElementType)
|
if (target == EditTarget::ArrayElementType)
|
||||||
mode = TypePopupMode::ArrayElement;
|
mode = TypePopupMode::ArrayElement;
|
||||||
else if (target == EditTarget::PointerTarget) {
|
// PointerTarget is handled as FieldType — modifiers * / ** will be pre-selected
|
||||||
// Primitive pointers (ptrDepth>0) should open FieldType with
|
|
||||||
// the base type selected and *//** preselected — not PointerTarget.
|
|
||||||
bool isPrimPtr = false;
|
|
||||||
if (nodeIdx >= 0 && nodeIdx < m_doc->tree.nodes.size()) {
|
|
||||||
const auto& n = m_doc->tree.nodes[nodeIdx];
|
|
||||||
isPrimPtr = n.ptrDepth > 0 && n.refId == 0;
|
|
||||||
}
|
|
||||||
mode = isPrimPtr ? TypePopupMode::FieldType
|
|
||||||
: TypePopupMode::PointerTarget;
|
|
||||||
}
|
|
||||||
showTypePopup(editor, mode, nodeIdx, globalPos);
|
showTypePopup(editor, mode, nodeIdx, globalPos);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1787,7 +1777,41 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx,
|
|||||||
&& !node.bitfieldMembers.isEmpty()
|
&& !node.bitfieldMembers.isEmpty()
|
||||||
&& subLine >= 0 && subLine < node.bitfieldMembers.size();
|
&& subLine >= 0 && subLine < node.bitfieldMembers.size();
|
||||||
|
|
||||||
|
bool isEnumNode = node.resolvedClassKeyword() == QStringLiteral("enum");
|
||||||
|
|
||||||
if (isEnumMember || isBitfieldMember) {
|
if (isEnumMember || isBitfieldMember) {
|
||||||
|
if (isEnumMember) {
|
||||||
|
menu.addAction(icon("diff-added.svg"), "Add Member Above", [this, nodeId, subLine]() {
|
||||||
|
int ni = m_doc->tree.indexOfId(nodeId);
|
||||||
|
if (ni < 0) return;
|
||||||
|
auto members = m_doc->tree.nodes[ni].enumMembers;
|
||||||
|
int64_t val = (subLine > 0) ? members[subLine - 1].second + 1 : 0;
|
||||||
|
auto oldMembers = members;
|
||||||
|
members.insert(subLine, {QStringLiteral("NewMember"), val});
|
||||||
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
|
cmd::ChangeEnumMembers{nodeId, oldMembers, members}));
|
||||||
|
});
|
||||||
|
menu.addAction(icon("diff-added.svg"), "Add Member Below", [this, nodeId, subLine]() {
|
||||||
|
int ni = m_doc->tree.indexOfId(nodeId);
|
||||||
|
if (ni < 0) return;
|
||||||
|
auto members = m_doc->tree.nodes[ni].enumMembers;
|
||||||
|
int64_t val = members[subLine].second + 1;
|
||||||
|
auto oldMembers = members;
|
||||||
|
members.insert(subLine + 1, {QStringLiteral("NewMember"), val});
|
||||||
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
|
cmd::ChangeEnumMembers{nodeId, oldMembers, members}));
|
||||||
|
});
|
||||||
|
menu.addAction(icon("trash.svg"), "Remove Member", [this, nodeId, subLine]() {
|
||||||
|
int ni = m_doc->tree.indexOfId(nodeId);
|
||||||
|
if (ni < 0) return;
|
||||||
|
auto members = m_doc->tree.nodes[ni].enumMembers;
|
||||||
|
auto oldMembers = members;
|
||||||
|
members.remove(subLine);
|
||||||
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
|
cmd::ChangeEnumMembers{nodeId, oldMembers, members}));
|
||||||
|
});
|
||||||
|
menu.addSeparator();
|
||||||
|
}
|
||||||
if (isBitfieldMember) {
|
if (isBitfieldMember) {
|
||||||
const auto& bm = node.bitfieldMembers[subLine];
|
const auto& bm = node.bitfieldMembers[subLine];
|
||||||
if (bm.bitWidth == 1) {
|
if (bm.bitWidth == 1) {
|
||||||
@@ -1802,6 +1826,28 @@ void RcxController::showContextMenu(RcxEditor* editor, int line, int nodeIdx,
|
|||||||
menu.addSeparator();
|
menu.addSeparator();
|
||||||
}
|
}
|
||||||
// Fall through to always-available actions
|
// Fall through to always-available actions
|
||||||
|
} else if (isEnumNode) {
|
||||||
|
// Enum header line — enum-specific actions only (no struct ops)
|
||||||
|
menu.addAction(icon("diff-added.svg"), "Add Member", [this, nodeId]() {
|
||||||
|
int ni = m_doc->tree.indexOfId(nodeId);
|
||||||
|
if (ni < 0) return;
|
||||||
|
auto members = m_doc->tree.nodes[ni].enumMembers;
|
||||||
|
int64_t nextVal = members.isEmpty() ? 0 : members.last().second + 1;
|
||||||
|
auto oldMembers = members;
|
||||||
|
members.append({QStringLiteral("NewMember"), nextVal});
|
||||||
|
m_doc->undoStack.push(new RcxCommand(this,
|
||||||
|
cmd::ChangeEnumMembers{nodeId, oldMembers, members}));
|
||||||
|
});
|
||||||
|
menu.addAction(icon("edit.svg"), "&Rename...", [this, editor, line]() {
|
||||||
|
editor->beginInlineEdit(EditTarget::Name, line);
|
||||||
|
});
|
||||||
|
menu.addSeparator();
|
||||||
|
menu.addAction(icon("trash.svg"), "&Delete", [this, nodeId]() {
|
||||||
|
int ni = m_doc->tree.indexOfId(nodeId);
|
||||||
|
if (ni >= 0) removeNode(ni);
|
||||||
|
});
|
||||||
|
menu.addSeparator();
|
||||||
|
// Fall through to always-available actions
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// ── Quick-convert suggestions (top-level for fast access) ──
|
// ── Quick-convert suggestions (top-level for fast access) ──
|
||||||
@@ -2775,7 +2821,7 @@ void RcxController::showTypePopup(RcxEditor* editor, TypePopupMode mode,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add types from other open documents
|
// Add types from other open documents
|
||||||
if (mode != TypePopupMode::Root && m_projectDocs) {
|
if (m_projectDocs) {
|
||||||
QSet<QString> localNames;
|
QSet<QString> localNames;
|
||||||
for (const auto& e : entries)
|
for (const auto& e : entries)
|
||||||
if (e.entryKind == TypeEntry::Composite)
|
if (e.entryKind == TypeEntry::Composite)
|
||||||
|
|||||||
@@ -5,28 +5,18 @@
|
|||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
|
||||||
// Dock tab button widget (pin + close)
|
// Dock tab button widget (close button)
|
||||||
// Placed on the right side of each dock tab via QTabBar::setTabButton.
|
// Placed on the right side of each dock tab via QTabBar::setTabButton.
|
||||||
class DockTabButtons : public QWidget {
|
class DockTabButtons : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
QToolButton* pinBtn;
|
|
||||||
QToolButton* closeBtn;
|
QToolButton* closeBtn;
|
||||||
bool pinned = false;
|
|
||||||
|
|
||||||
explicit DockTabButtons(QWidget* parent = nullptr) : QWidget(parent) {
|
explicit DockTabButtons(QWidget* parent = nullptr) : QWidget(parent) {
|
||||||
auto* hl = new QHBoxLayout(this);
|
auto* hl = new QHBoxLayout(this);
|
||||||
hl->setContentsMargins(0, 0, 0, 0);
|
hl->setContentsMargins(0, 0, 0, 0);
|
||||||
hl->setSpacing(0);
|
hl->setSpacing(0);
|
||||||
|
|
||||||
pinBtn = new QToolButton(this);
|
|
||||||
pinBtn->setAutoRaise(true);
|
|
||||||
pinBtn->setCursor(Qt::PointingHandCursor);
|
|
||||||
pinBtn->setFixedSize(16, 16);
|
|
||||||
pinBtn->setToolTip("Pin tab");
|
|
||||||
updatePinIcon();
|
|
||||||
hl->addWidget(pinBtn);
|
|
||||||
|
|
||||||
closeBtn = new QToolButton(this);
|
closeBtn = new QToolButton(this);
|
||||||
closeBtn->setAutoRaise(true);
|
closeBtn->setAutoRaise(true);
|
||||||
closeBtn->setCursor(Qt::PointingHandCursor);
|
closeBtn->setCursor(Qt::PointingHandCursor);
|
||||||
@@ -35,31 +25,12 @@ public:
|
|||||||
closeBtn->setIcon(QIcon(":/vsicons/close.svg"));
|
closeBtn->setIcon(QIcon(":/vsicons/close.svg"));
|
||||||
closeBtn->setIconSize(QSize(12, 12));
|
closeBtn->setIconSize(QSize(12, 12));
|
||||||
hl->addWidget(closeBtn);
|
hl->addWidget(closeBtn);
|
||||||
|
|
||||||
connect(pinBtn, &QToolButton::clicked, this, [this]() {
|
|
||||||
pinned = !pinned;
|
|
||||||
updatePinIcon();
|
|
||||||
emit pinToggled(pinned);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void applyTheme(const QColor& hover) {
|
void applyTheme(const QColor& hover) {
|
||||||
QString style = QStringLiteral(
|
QString style = QStringLiteral(
|
||||||
"QToolButton { border: none; padding: 1px; border-radius: 0px; }"
|
"QToolButton { border: none; padding: 1px; border-radius: 0px; }"
|
||||||
"QToolButton:hover { background: %1; }").arg(hover.name());
|
"QToolButton:hover { background: %1; }").arg(hover.name());
|
||||||
pinBtn->setStyleSheet(style);
|
|
||||||
closeBtn->setStyleSheet(style);
|
closeBtn->setStyleSheet(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setPinned(bool p) { pinned = p; updatePinIcon(); emit pinToggled(pinned); }
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void pinToggled(bool pinned);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void updatePinIcon() {
|
|
||||||
pinBtn->setIcon(QIcon(pinned ? ":/vsicons/pinned.svg" : ":/vsicons/pin.svg"));
|
|
||||||
pinBtn->setIconSize(QSize(12, 12));
|
|
||||||
pinBtn->setToolTip(pinned ? "Unpin tab" : "Pin tab");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
139
src/main.cpp
139
src/main.cpp
@@ -1441,8 +1441,7 @@ QDockWidget* MainWindow::createTab(RcxDocument* doc) {
|
|||||||
splitter->setHandleWidth(1);
|
splitter->setHandleWidth(1);
|
||||||
auto* ctrl = new RcxController(doc, splitter);
|
auto* ctrl = new RcxController(doc, splitter);
|
||||||
|
|
||||||
QString title = doc->filePath.isEmpty()
|
QString title = rootName(doc->tree);
|
||||||
? rootName(doc->tree) : QFileInfo(doc->filePath).fileName();
|
|
||||||
auto* dock = new QDockWidget(title, this);
|
auto* dock = new QDockWidget(title, this);
|
||||||
dock->setObjectName(QStringLiteral("DocDock_%1").arg(quintptr(dock), 0, 16));
|
dock->setObjectName(QStringLiteral("DocDock_%1").arg(quintptr(dock), 0, 16));
|
||||||
dock->setFeatures(QDockWidget::DockWidgetClosable |
|
dock->setFeatures(QDockWidget::DockWidgetClosable |
|
||||||
@@ -1532,7 +1531,7 @@ QDockWidget* MainWindow::createTab(RcxDocument* doc) {
|
|||||||
dockGrip->hide();
|
dockGrip->hide();
|
||||||
|
|
||||||
// Swap title bar when floating/docking, show/hide border + grip
|
// Swap title bar when floating/docking, show/hide border + grip
|
||||||
connect(dock, &QDockWidget::topLevelChanged, this, [dock, emptyTitleBar, floatTitleBar, dockBorder, dockGrip](bool floating) {
|
connect(dock, &QDockWidget::topLevelChanged, this, [this, dock, emptyTitleBar, floatTitleBar, dockBorder, dockGrip](bool floating) {
|
||||||
dock->setTitleBarWidget(floating ? floatTitleBar : emptyTitleBar);
|
dock->setTitleBarWidget(floating ? floatTitleBar : emptyTitleBar);
|
||||||
if (floating) {
|
if (floating) {
|
||||||
dockBorder->setGeometry(0, 0, dock->width(), dock->height());
|
dockBorder->setGeometry(0, 0, dock->width(), dock->height());
|
||||||
@@ -1544,6 +1543,8 @@ QDockWidget* MainWindow::createTab(RcxDocument* doc) {
|
|||||||
} else {
|
} else {
|
||||||
dockBorder->hide();
|
dockBorder->hide();
|
||||||
dockGrip->hide();
|
dockGrip->hide();
|
||||||
|
// Re-docking creates a new tab bar — reinstall pin/close buttons
|
||||||
|
QTimer::singleShot(0, this, [this]() { setupDockTabBars(); });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
dock->installEventFilter(new DockBorderFilter(dockBorder, dockGrip, dock));
|
dock->installEventFilter(new DockBorderFilter(dockBorder, dockGrip, dock));
|
||||||
@@ -1667,8 +1668,7 @@ QDockWidget* MainWindow::createTab(RcxDocument* doc) {
|
|||||||
auto it2 = m_tabs.find(dockGuard);
|
auto it2 = m_tabs.find(dockGuard);
|
||||||
if (it2 != m_tabs.end()) {
|
if (it2 != m_tabs.end()) {
|
||||||
updateAllRenderedPanes(*it2);
|
updateAllRenderedPanes(*it2);
|
||||||
if (it2->doc->filePath.isEmpty())
|
dockGuard->setWindowTitle(rootName(it2->doc->tree, it2->ctrl->viewRootId()));
|
||||||
dockGuard->setWindowTitle(rootName(it2->doc->tree, it2->ctrl->viewRootId()));
|
|
||||||
}
|
}
|
||||||
rebuildWorkspaceModel();
|
rebuildWorkspaceModel();
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
@@ -1684,8 +1684,7 @@ QDockWidget* MainWindow::createTab(RcxDocument* doc) {
|
|||||||
auto it2 = m_tabs.find(dockGuard);
|
auto it2 = m_tabs.find(dockGuard);
|
||||||
if (it2 != m_tabs.end()) {
|
if (it2 != m_tabs.end()) {
|
||||||
updateAllRenderedPanes(*it2);
|
updateAllRenderedPanes(*it2);
|
||||||
if (it2->doc->filePath.isEmpty())
|
dockGuard->setWindowTitle(rootName(it2->doc->tree, it2->ctrl->viewRootId()));
|
||||||
dockGuard->setWindowTitle(rootName(it2->doc->tree, it2->ctrl->viewRootId()));
|
|
||||||
}
|
}
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
rebuildWorkspaceModel();
|
rebuildWorkspaceModel();
|
||||||
@@ -1734,6 +1733,7 @@ void MainWindow::setupDockTabBars() {
|
|||||||
|
|
||||||
// No stylesheet — painting handled by MenuBarStyle
|
// No stylesheet — painting handled by MenuBarStyle
|
||||||
tabBar->setStyleSheet(QString());
|
tabBar->setStyleSheet(QString());
|
||||||
|
tabBar->setAttribute(Qt::WA_Hover, true);
|
||||||
tabBar->setElideMode(Qt::ElideNone);
|
tabBar->setElideMode(Qt::ElideNone);
|
||||||
tabBar->setExpanding(false);
|
tabBar->setExpanding(false);
|
||||||
// Set editor font so tab width sizing matches our label painting
|
// Set editor font so tab width sizing matches our label painting
|
||||||
@@ -1774,8 +1774,9 @@ void MainWindow::setupDockTabBars() {
|
|||||||
tabBar->setTabButton(i, QTabBar::RightSide, btns);
|
tabBar->setTabButton(i, QTabBar::RightSide, btns);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context menu (install only once)
|
// Middle-click close + context menu (install only once)
|
||||||
if (tabBar->contextMenuPolicy() == Qt::CustomContextMenu) continue;
|
if (tabBar->contextMenuPolicy() == Qt::CustomContextMenu) continue;
|
||||||
|
tabBar->installEventFilter(this);
|
||||||
tabBar->setContextMenuPolicy(Qt::CustomContextMenu);
|
tabBar->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
connect(tabBar, &QTabBar::customContextMenuRequested,
|
connect(tabBar, &QTabBar::customContextMenuRequested,
|
||||||
this, [this, tabBar](const QPoint& pos) {
|
this, [this, tabBar](const QPoint& pos) {
|
||||||
@@ -1790,9 +1791,6 @@ void MainWindow::setupDockTabBars() {
|
|||||||
if (!target) return;
|
if (!target) return;
|
||||||
|
|
||||||
auto tabIt = m_tabs.find(target);
|
auto tabIt = m_tabs.find(target);
|
||||||
auto* btns = qobject_cast<DockTabButtons*>(
|
|
||||||
tabBar->tabButton(idx, QTabBar::RightSide));
|
|
||||||
bool isPinned = btns && btns->pinned;
|
|
||||||
|
|
||||||
QMenu menu;
|
QMenu menu;
|
||||||
|
|
||||||
@@ -1816,28 +1814,6 @@ void MainWindow::setupDockTabBars() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close All But Pinned (only if any tab is pinned)
|
|
||||||
bool anyPinned = false;
|
|
||||||
for (int i = 0; i < tabBar->count(); ++i) {
|
|
||||||
auto* b = qobject_cast<DockTabButtons*>(
|
|
||||||
tabBar->tabButton(i, QTabBar::RightSide));
|
|
||||||
if (b && b->pinned) { anyPinned = true; break; }
|
|
||||||
}
|
|
||||||
if (anyPinned) {
|
|
||||||
menu.addAction("Close All But Pinned", [this, tabBar]() {
|
|
||||||
QVector<QDockWidget*> toClose;
|
|
||||||
for (int i = 0; i < tabBar->count(); ++i) {
|
|
||||||
auto* b = qobject_cast<DockTabButtons*>(
|
|
||||||
tabBar->tabButton(i, QTabBar::RightSide));
|
|
||||||
if (b && b->pinned) continue;
|
|
||||||
QString title = tabBar->tabText(i);
|
|
||||||
for (auto* d : m_docDocks)
|
|
||||||
if (d->windowTitle() == title) { toClose.append(d); break; }
|
|
||||||
}
|
|
||||||
for (auto* d : toClose) d->close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.addSeparator();
|
menu.addSeparator();
|
||||||
|
|
||||||
// Copy Full Path / Open Containing Folder (only if saved)
|
// Copy Full Path / Open Containing Folder (only if saved)
|
||||||
@@ -1859,14 +1835,6 @@ void MainWindow::setupDockTabBars() {
|
|||||||
|
|
||||||
menu.addSeparator();
|
menu.addSeparator();
|
||||||
|
|
||||||
// Pin / Unpin
|
|
||||||
if (btns) {
|
|
||||||
QIcon pinIcon = makeIcon(isPinned ? ":/vsicons/pinned.svg"
|
|
||||||
: ":/vsicons/pin.svg");
|
|
||||||
menu.addAction(pinIcon, isPinned ? "Unpin Tab" : "Pin Tab",
|
|
||||||
[btns, isPinned]() { btns->setPinned(!isPinned); });
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.addSeparator();
|
menu.addSeparator();
|
||||||
|
|
||||||
// New Document Groups (only if >1 tab)
|
// New Document Groups (only if >1 tab)
|
||||||
@@ -1918,8 +1886,47 @@ void MainWindow::setupDockTabBars() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MainWindow::eventFilter(QObject* obj, QEvent* event) {
|
||||||
|
if (event->type() == QEvent::MouseButtonPress) {
|
||||||
|
auto* me = static_cast<QMouseEvent*>(event);
|
||||||
|
if (me->button() == Qt::MiddleButton) {
|
||||||
|
if (auto* tabBar = qobject_cast<QTabBar*>(obj)) {
|
||||||
|
int idx = tabBar->tabAt(me->pos());
|
||||||
|
if (idx >= 0) {
|
||||||
|
QString title = tabBar->tabText(idx);
|
||||||
|
for (auto* d : m_docDocks) {
|
||||||
|
if (d->windowTitle() == title) { d->close(); break; }
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QMainWindow::eventFilter(obj, event);
|
||||||
|
}
|
||||||
|
|
||||||
// Build a minimal empty struct for new documents
|
// Build a minimal empty struct for new documents
|
||||||
static void buildEmptyStruct(NodeTree& tree, const QString& classKeyword = QString()) {
|
static void buildEmptyStruct(NodeTree& tree, const QString& classKeyword = QString()) {
|
||||||
|
// ── Enum: bare node with empty enumMembers, no hex children ──
|
||||||
|
if (classKeyword == QStringLiteral("enum")) {
|
||||||
|
Node root;
|
||||||
|
root.kind = NodeKind::Struct;
|
||||||
|
root.name = "Unnamed";
|
||||||
|
root.structTypeName = "Unnamed";
|
||||||
|
root.classKeyword = classKeyword;
|
||||||
|
root.parentId = 0;
|
||||||
|
root.offset = 0;
|
||||||
|
root.enumMembers = {
|
||||||
|
{QStringLiteral("Member0"), 0},
|
||||||
|
{QStringLiteral("Member1"), 1},
|
||||||
|
{QStringLiteral("Member2"), 2},
|
||||||
|
{QStringLiteral("Member3"), 3},
|
||||||
|
{QStringLiteral("Member4"), 4},
|
||||||
|
};
|
||||||
|
tree.addNode(root);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Node root;
|
Node root;
|
||||||
root.kind = NodeKind::Struct;
|
root.kind = NodeKind::Struct;
|
||||||
root.name = "instance";
|
root.name = "instance";
|
||||||
@@ -2317,6 +2324,7 @@ void MainWindow::applyTheme(const Theme& theme) {
|
|||||||
if (tabBar->parent() == this) {
|
if (tabBar->parent() == this) {
|
||||||
// No stylesheet — painting handled by MenuBarStyle (CE_TabBarTabShape/Label)
|
// No stylesheet — painting handled by MenuBarStyle (CE_TabBarTabShape/Label)
|
||||||
tabBar->setStyleSheet(QString());
|
tabBar->setStyleSheet(QString());
|
||||||
|
tabBar->setAttribute(Qt::WA_Hover, true);
|
||||||
tabBar->setElideMode(Qt::ElideNone);
|
tabBar->setElideMode(Qt::ElideNone);
|
||||||
tabBar->setExpanding(false);
|
tabBar->setExpanding(false);
|
||||||
// Set editor font so tab width sizing matches our label painting
|
// Set editor font so tab width sizing matches our label painting
|
||||||
@@ -2582,9 +2590,12 @@ void MainWindow::setEditorFont(const QString& fontName) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Sync workspace tree font
|
// Sync workspace tree font (match tab bar size)
|
||||||
if (m_workspaceTree)
|
if (m_workspaceTree) {
|
||||||
m_workspaceTree->setFont(f);
|
QFont wf(fontName, 10);
|
||||||
|
wf.setFixedPitch(true);
|
||||||
|
m_workspaceTree->setFont(wf);
|
||||||
|
}
|
||||||
// Sync dock titlebar font
|
// Sync dock titlebar font
|
||||||
if (m_dockTitleLabel)
|
if (m_dockTitleLabel)
|
||||||
m_dockTitleLabel->setFont(f);
|
m_dockTitleLabel->setFont(f);
|
||||||
@@ -2639,9 +2650,7 @@ void MainWindow::updateWindowTitle() {
|
|||||||
auto* activeDock = m_activeDocDock;
|
auto* activeDock = m_activeDocDock;
|
||||||
if (activeDock && m_tabs.contains(activeDock)) {
|
if (activeDock && m_tabs.contains(activeDock)) {
|
||||||
auto& tab = m_tabs[activeDock];
|
auto& tab = m_tabs[activeDock];
|
||||||
QString name = tab.doc->filePath.isEmpty()
|
QString name = rootName(tab.doc->tree, tab.ctrl->viewRootId());
|
||||||
? rootName(tab.doc->tree, tab.ctrl->viewRootId())
|
|
||||||
: QFileInfo(tab.doc->filePath).fileName();
|
|
||||||
if (tab.doc->modified) name += " *";
|
if (tab.doc->modified) name += " *";
|
||||||
title = name + " - Reclass";
|
title = name + " - Reclass";
|
||||||
} else {
|
} else {
|
||||||
@@ -3356,13 +3365,19 @@ void MainWindow::createWorkspaceDock() {
|
|||||||
m_workspaceTree->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
m_workspaceTree->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||||
m_workspaceTree->setExpandsOnDoubleClick(false);
|
m_workspaceTree->setExpandsOnDoubleClick(false);
|
||||||
m_workspaceTree->setMouseTracking(true);
|
m_workspaceTree->setMouseTracking(true);
|
||||||
|
{
|
||||||
|
QSettings s("Reclass", "Reclass");
|
||||||
|
QFont f(s.value("font", "JetBrains Mono").toString(), 10);
|
||||||
|
f.setFixedPitch(true);
|
||||||
|
m_workspaceTree->setFont(f);
|
||||||
|
}
|
||||||
|
|
||||||
connect(m_workspaceSearch, &QLineEdit::textChanged, this, [this](const QString& text) {
|
connect(m_workspaceSearch, &QLineEdit::textChanged, this, [this](const QString& text) {
|
||||||
m_workspaceProxy->setFilterFixedString(text);
|
m_workspaceProxy->setFilterFixedString(text);
|
||||||
if (!text.isEmpty())
|
if (!text.isEmpty())
|
||||||
m_workspaceTree->expandAll();
|
m_workspaceTree->expandAll();
|
||||||
else
|
else
|
||||||
m_workspaceTree->expandToDepth(0);
|
m_workspaceTree->collapseAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Override palette: selection + hover use theme colors (not default blue)
|
// Override palette: selection + hover use theme colors (not default blue)
|
||||||
@@ -3380,13 +3395,9 @@ void MainWindow::createWorkspaceDock() {
|
|||||||
m_workspaceTree->setContextMenuPolicy(Qt::CustomContextMenu);
|
m_workspaceTree->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
connect(m_workspaceTree, &QWidget::customContextMenuRequested, this, [this](const QPoint& pos) {
|
connect(m_workspaceTree, &QWidget::customContextMenuRequested, this, [this](const QPoint& pos) {
|
||||||
QModelIndex index = m_workspaceTree->indexAt(pos);
|
QModelIndex index = m_workspaceTree->indexAt(pos);
|
||||||
if (!index.isValid()) return;
|
|
||||||
|
|
||||||
auto structIdVar = index.data(Qt::UserRole + 1);
|
// Right-click on empty area → New Class / New Struct / New Enum
|
||||||
uint64_t structId = structIdVar.isValid() ? structIdVar.toULongLong() : 0;
|
if (!index.isValid()) {
|
||||||
|
|
||||||
// Right-click on "Project" group → New Class / New Struct / New Enum
|
|
||||||
if (structId == rcx::kGroupSentinel) {
|
|
||||||
QMenu menu;
|
QMenu menu;
|
||||||
auto* actClass = menu.addAction("New Class");
|
auto* actClass = menu.addAction("New Class");
|
||||||
auto* actStruct = menu.addAction("New Struct");
|
auto* actStruct = menu.addAction("New Struct");
|
||||||
@@ -3398,6 +3409,8 @@ void MainWindow::createWorkspaceDock() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto structIdVar = index.data(Qt::UserRole + 1);
|
||||||
|
uint64_t structId = structIdVar.isValid() ? structIdVar.toULongLong() : 0;
|
||||||
if (structId == 0) return;
|
if (structId == 0) return;
|
||||||
|
|
||||||
auto subVar = index.data(Qt::UserRole);
|
auto subVar = index.data(Qt::UserRole);
|
||||||
@@ -3502,12 +3515,6 @@ void MainWindow::createWorkspaceDock() {
|
|||||||
auto structIdVar = index.data(Qt::UserRole + 1);
|
auto structIdVar = index.data(Qt::UserRole + 1);
|
||||||
uint64_t structId = structIdVar.isValid() ? structIdVar.toULongLong() : 0;
|
uint64_t structId = structIdVar.isValid() ? structIdVar.toULongLong() : 0;
|
||||||
|
|
||||||
if (structId == rcx::kGroupSentinel) {
|
|
||||||
// "Project" folder: toggle expand/collapse
|
|
||||||
m_workspaceTree->setExpanded(index, !m_workspaceTree->isExpanded(index));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto subVar = index.data(Qt::UserRole);
|
auto subVar = index.data(Qt::UserRole);
|
||||||
if (!subVar.isValid()) return;
|
if (!subVar.isValid()) return;
|
||||||
auto* ownerDock = static_cast<QDockWidget*>(subVar.value<void*>());
|
auto* ownerDock = static_cast<QDockWidget*>(subVar.value<void*>());
|
||||||
@@ -3713,13 +3720,10 @@ void MainWindow::rebuildWorkspaceModel() {
|
|||||||
TabState& tab = it.value();
|
TabState& tab = it.value();
|
||||||
if (seenDocs.contains(tab.doc)) continue; // skip duplicate doc views
|
if (seenDocs.contains(tab.doc)) continue; // skip duplicate doc views
|
||||||
seenDocs.insert(tab.doc);
|
seenDocs.insert(tab.doc);
|
||||||
QString name = tab.doc->filePath.isEmpty()
|
QString name = rootName(tab.doc->tree, tab.ctrl->viewRootId());
|
||||||
? rootName(tab.doc->tree, tab.ctrl->viewRootId())
|
|
||||||
: QFileInfo(tab.doc->filePath).fileName();
|
|
||||||
tabs.append({ &tab.doc->tree, name, static_cast<void*>(it.key()) });
|
tabs.append({ &tab.doc->tree, name, static_cast<void*>(it.key()) });
|
||||||
}
|
}
|
||||||
rcx::buildProjectExplorer(m_workspaceModel, tabs);
|
rcx::buildProjectExplorer(m_workspaceModel, tabs);
|
||||||
m_workspaceTree->expandToDepth(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::addRecentFile(const QString& path) {
|
void MainWindow::addRecentFile(const QString& path) {
|
||||||
@@ -4010,9 +4014,10 @@ void MainWindow::showStartPage() {
|
|||||||
|
|
||||||
void MainWindow::dismissStartPage() {
|
void MainWindow::dismissStartPage() {
|
||||||
if (!m_startPage) return;
|
if (!m_startPage) return;
|
||||||
m_startPage->close();
|
auto* sp = m_startPage;
|
||||||
m_startPage->deleteLater();
|
m_startPage = nullptr; // null first — close() may re-enter via rejected signal
|
||||||
m_startPage = nullptr;
|
sp->close();
|
||||||
|
sp->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace rcx
|
} // namespace rcx
|
||||||
|
|||||||
@@ -178,6 +178,7 @@ private:
|
|||||||
protected:
|
protected:
|
||||||
void changeEvent(QEvent* event) override;
|
void changeEvent(QEvent* event) override;
|
||||||
void resizeEvent(QResizeEvent* event) override;
|
void resizeEvent(QResizeEvent* event) override;
|
||||||
|
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rcx
|
} // namespace rcx
|
||||||
|
|||||||
@@ -415,9 +415,9 @@ TypeSelectorPopup::TypeSelectorPopup(QWidget* parent)
|
|||||||
return btn;
|
return btn;
|
||||||
};
|
};
|
||||||
|
|
||||||
m_chipPrim = makeChip(QStringLiteral("P"));
|
m_chipPrim = makeChip(QStringLiteral("Built-in"));
|
||||||
m_chipTypes = makeChip(QStringLiteral("T"));
|
m_chipTypes = makeChip(QStringLiteral("Types"));
|
||||||
m_chipEnums = makeChip(QStringLiteral("E"));
|
m_chipEnums = makeChip(QStringLiteral("Enum"));
|
||||||
m_chipPrim->setAccessibleName(QStringLiteral("Show primitives"));
|
m_chipPrim->setAccessibleName(QStringLiteral("Show primitives"));
|
||||||
m_chipTypes->setAccessibleName(QStringLiteral("Show composites"));
|
m_chipTypes->setAccessibleName(QStringLiteral("Show composites"));
|
||||||
m_chipEnums->setAccessibleName(QStringLiteral("Show enums"));
|
m_chipEnums->setAccessibleName(QStringLiteral("Show enums"));
|
||||||
@@ -1080,9 +1080,9 @@ void TypeSelectorPopup::applyFilter(const QString& text) {
|
|||||||
auto updateChipLabel = [](QToolButton* btn, const QString& abbrev, int count) {
|
auto updateChipLabel = [](QToolButton* btn, const QString& abbrev, int count) {
|
||||||
btn->setText(QStringLiteral("%1 (%2)").arg(abbrev).arg(count));
|
btn->setText(QStringLiteral("%1 (%2)").arg(abbrev).arg(count));
|
||||||
};
|
};
|
||||||
if (m_chipPrim) updateChipLabel(m_chipPrim, QStringLiteral("P"), primCount);
|
if (m_chipPrim) updateChipLabel(m_chipPrim, QStringLiteral("Built-in"), primCount);
|
||||||
if (m_chipTypes) updateChipLabel(m_chipTypes, QStringLiteral("T"), typeCount);
|
if (m_chipTypes) updateChipLabel(m_chipTypes, QStringLiteral("Types"), typeCount);
|
||||||
if (m_chipEnums) updateChipLabel(m_chipEnums, QStringLiteral("E"), enumCount);
|
if (m_chipEnums) updateChipLabel(m_chipEnums, QStringLiteral("Enum"), enumCount);
|
||||||
|
|
||||||
if (m_statusLabel)
|
if (m_statusLabel)
|
||||||
m_statusLabel->setText(QStringLiteral("%1 results").arg(resultCount));
|
m_statusLabel->setText(QStringLiteral("%1 results").arg(resultCount));
|
||||||
|
|||||||
@@ -21,13 +21,6 @@ inline void buildProjectExplorer(QStandardItemModel* model,
|
|||||||
model->clear();
|
model->clear();
|
||||||
model->setHorizontalHeaderLabels({QStringLiteral("Name")});
|
model->setHorizontalHeaderLabels({QStringLiteral("Name")});
|
||||||
|
|
||||||
// Single "Project" root with folder icon
|
|
||||||
void* firstSub = tabs.isEmpty() ? nullptr : tabs[0].subPtr;
|
|
||||||
auto* projectItem = new QStandardItem(QIcon(":/vsicons/folder.svg"),
|
|
||||||
QStringLiteral("Project"));
|
|
||||||
projectItem->setData(QVariant::fromValue(firstSub), Qt::UserRole);
|
|
||||||
projectItem->setData(QVariant::fromValue(kGroupSentinel), Qt::UserRole + 1);
|
|
||||||
|
|
||||||
// Collect all top-level structs/enums across all tabs
|
// Collect all top-level structs/enums across all tabs
|
||||||
struct Entry { const Node* node; void* subPtr; const NodeTree* tree; };
|
struct Entry { const Node* node; void* subPtr; const NodeTree* tree; };
|
||||||
QVector<Entry> types, enums;
|
QVector<Entry> types, enums;
|
||||||
@@ -99,7 +92,7 @@ inline void buildProjectExplorer(QStandardItemModel* model,
|
|||||||
item->appendRow(childItem);
|
item->appendRow(childItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
projectItem->appendRow(item);
|
model->appendRow(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& e : enums) {
|
for (const auto& e : enums) {
|
||||||
@@ -111,10 +104,8 @@ inline void buildProjectExplorer(QStandardItemModel* model,
|
|||||||
QIcon(":/vsicons/symbol-enum.svg"), display);
|
QIcon(":/vsicons/symbol-enum.svg"), display);
|
||||||
item->setData(QVariant::fromValue(e.subPtr), Qt::UserRole);
|
item->setData(QVariant::fromValue(e.subPtr), Qt::UserRole);
|
||||||
item->setData(QVariant::fromValue(e.node->id), Qt::UserRole + 1);
|
item->setData(QVariant::fromValue(e.node->id), Qt::UserRole + 1);
|
||||||
projectItem->appendRow(item);
|
model->appendRow(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
model->appendRow(projectItem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace rcx
|
} // namespace rcx
|
||||||
|
|||||||
@@ -341,6 +341,19 @@ def parse_field_line(line, offset, parent_id, ids, struct_registry):
|
|||||||
line = re.sub(r'\bvolatile\b', '', line).strip()
|
line = re.sub(r'\bvolatile\b', '', line).strip()
|
||||||
line = re.sub(r'\s+', ' ', line)
|
line = re.sub(r'\s+', ' ', line)
|
||||||
|
|
||||||
|
# Check for function pointer: RETURN_TYPE (*NAME)(PARAMS)
|
||||||
|
fnptr_m = re.search(r'\(\*\s*(\w+)\s*\)', line)
|
||||||
|
if fnptr_m:
|
||||||
|
field_name = fnptr_m.group(1)
|
||||||
|
node_id = ids.alloc()
|
||||||
|
return {
|
||||||
|
'id': str(node_id),
|
||||||
|
'kind': 'FuncPtr64',
|
||||||
|
'name': field_name,
|
||||||
|
'offset': offset,
|
||||||
|
'parentId': str(parent_id),
|
||||||
|
}
|
||||||
|
|
||||||
# Check for struct/union keyword prefix
|
# Check for struct/union keyword prefix
|
||||||
keyword = None
|
keyword = None
|
||||||
m = re.match(r'^(struct|union|enum)\s+(.+)', line)
|
m = re.match(r'^(struct|union|enum)\s+(.+)', line)
|
||||||
|
|||||||
Reference in New Issue
Block a user