mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
fix: vergilius fnptr import, remove tab pin, flatten workspace tree, middle-click close
- Fix vergilius_to_rcx.py to detect function pointer syntax (*Name)(params) and emit FuncPtr64 - Re-fetch 85 structs to recover proper field names (697/716 fixed) - Remove pin button from dock tabs and all pin-related context menu items - Fix newClass() creating duplicate tabs - Set workspace tree font to match tab bar (size 10) - Flatten workspace tree: remove redundant Project group node (VS Code Explorer style) - Add middle-click to close dock widget tabs - Allow type chooser to show cross-doc types for root nodes
This commit is contained in:
@@ -2821,7 +2821,7 @@ void RcxController::showTypePopup(RcxEditor* editor, TypePopupMode mode,
|
||||
}
|
||||
|
||||
// Add types from other open documents
|
||||
if (mode != TypePopupMode::Root && m_projectDocs) {
|
||||
if (m_projectDocs) {
|
||||
QSet<QString> localNames;
|
||||
for (const auto& e : entries)
|
||||
if (e.entryKind == TypeEntry::Composite)
|
||||
|
||||
@@ -5,28 +5,18 @@
|
||||
#include <QHBoxLayout>
|
||||
#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.
|
||||
class DockTabButtons : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
QToolButton* pinBtn;
|
||||
QToolButton* closeBtn;
|
||||
bool pinned = false;
|
||||
|
||||
explicit DockTabButtons(QWidget* parent = nullptr) : QWidget(parent) {
|
||||
auto* hl = new QHBoxLayout(this);
|
||||
hl->setContentsMargins(0, 0, 0, 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->setAutoRaise(true);
|
||||
closeBtn->setCursor(Qt::PointingHandCursor);
|
||||
@@ -35,31 +25,12 @@ public:
|
||||
closeBtn->setIcon(QIcon(":/vsicons/close.svg"));
|
||||
closeBtn->setIconSize(QSize(12, 12));
|
||||
hl->addWidget(closeBtn);
|
||||
|
||||
connect(pinBtn, &QToolButton::clicked, this, [this]() {
|
||||
pinned = !pinned;
|
||||
updatePinIcon();
|
||||
emit pinToggled(pinned);
|
||||
});
|
||||
}
|
||||
|
||||
void applyTheme(const QColor& hover) {
|
||||
QString style = QStringLiteral(
|
||||
"QToolButton { border: none; padding: 1px; border-radius: 0px; }"
|
||||
"QToolButton:hover { background: %1; }").arg(hover.name());
|
||||
pinBtn->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
92
src/main.cpp
92
src/main.cpp
@@ -1774,8 +1774,9 @@ void MainWindow::setupDockTabBars() {
|
||||
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;
|
||||
tabBar->installEventFilter(this);
|
||||
tabBar->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(tabBar, &QTabBar::customContextMenuRequested,
|
||||
this, [this, tabBar](const QPoint& pos) {
|
||||
@@ -1790,9 +1791,6 @@ void MainWindow::setupDockTabBars() {
|
||||
if (!target) return;
|
||||
|
||||
auto tabIt = m_tabs.find(target);
|
||||
auto* btns = qobject_cast<DockTabButtons*>(
|
||||
tabBar->tabButton(idx, QTabBar::RightSide));
|
||||
bool isPinned = btns && btns->pinned;
|
||||
|
||||
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();
|
||||
|
||||
// Copy Full Path / Open Containing Folder (only if saved)
|
||||
@@ -1859,14 +1835,6 @@ void MainWindow::setupDockTabBars() {
|
||||
|
||||
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();
|
||||
|
||||
// New Document Groups (only if >1 tab)
|
||||
@@ -1918,6 +1886,25 @@ 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
|
||||
static void buildEmptyStruct(NodeTree& tree, const QString& classKeyword = QString()) {
|
||||
// ── Enum: bare node with empty enumMembers, no hex children ──
|
||||
@@ -2008,10 +1995,7 @@ MainWindow::~MainWindow() {
|
||||
}
|
||||
|
||||
void MainWindow::newClass() {
|
||||
auto* first = project_new(QStringLiteral("class"));
|
||||
project_new(QStringLiteral("class"));
|
||||
// Select the first tab
|
||||
if (first) first->raise();
|
||||
}
|
||||
|
||||
void MainWindow::newStruct() {
|
||||
@@ -2606,9 +2590,12 @@ void MainWindow::setEditorFont(const QString& fontName) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sync workspace tree font
|
||||
if (m_workspaceTree)
|
||||
m_workspaceTree->setFont(f);
|
||||
// Sync workspace tree font (match tab bar size)
|
||||
if (m_workspaceTree) {
|
||||
QFont wf(fontName, 10);
|
||||
wf.setFixedPitch(true);
|
||||
m_workspaceTree->setFont(wf);
|
||||
}
|
||||
// Sync dock titlebar font
|
||||
if (m_dockTitleLabel)
|
||||
m_dockTitleLabel->setFont(f);
|
||||
@@ -3378,13 +3365,19 @@ void MainWindow::createWorkspaceDock() {
|
||||
m_workspaceTree->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
m_workspaceTree->setExpandsOnDoubleClick(false);
|
||||
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) {
|
||||
m_workspaceProxy->setFilterFixedString(text);
|
||||
if (!text.isEmpty())
|
||||
m_workspaceTree->expandAll();
|
||||
else
|
||||
m_workspaceTree->expandToDepth(0);
|
||||
m_workspaceTree->collapseAll();
|
||||
});
|
||||
|
||||
// Override palette: selection + hover use theme colors (not default blue)
|
||||
@@ -3402,13 +3395,9 @@ void MainWindow::createWorkspaceDock() {
|
||||
m_workspaceTree->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_workspaceTree, &QWidget::customContextMenuRequested, this, [this](const QPoint& pos) {
|
||||
QModelIndex index = m_workspaceTree->indexAt(pos);
|
||||
if (!index.isValid()) return;
|
||||
|
||||
auto structIdVar = index.data(Qt::UserRole + 1);
|
||||
uint64_t structId = structIdVar.isValid() ? structIdVar.toULongLong() : 0;
|
||||
|
||||
// Right-click on "Project" group → New Class / New Struct / New Enum
|
||||
if (structId == rcx::kGroupSentinel) {
|
||||
// Right-click on empty area → New Class / New Struct / New Enum
|
||||
if (!index.isValid()) {
|
||||
QMenu menu;
|
||||
auto* actClass = menu.addAction("New Class");
|
||||
auto* actStruct = menu.addAction("New Struct");
|
||||
@@ -3420,6 +3409,8 @@ void MainWindow::createWorkspaceDock() {
|
||||
return;
|
||||
}
|
||||
|
||||
auto structIdVar = index.data(Qt::UserRole + 1);
|
||||
uint64_t structId = structIdVar.isValid() ? structIdVar.toULongLong() : 0;
|
||||
if (structId == 0) return;
|
||||
|
||||
auto subVar = index.data(Qt::UserRole);
|
||||
@@ -3524,12 +3515,6 @@ void MainWindow::createWorkspaceDock() {
|
||||
auto structIdVar = index.data(Qt::UserRole + 1);
|
||||
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);
|
||||
if (!subVar.isValid()) return;
|
||||
auto* ownerDock = static_cast<QDockWidget*>(subVar.value<void*>());
|
||||
@@ -3739,7 +3724,6 @@ void MainWindow::rebuildWorkspaceModel() {
|
||||
tabs.append({ &tab.doc->tree, name, static_cast<void*>(it.key()) });
|
||||
}
|
||||
rcx::buildProjectExplorer(m_workspaceModel, tabs);
|
||||
m_workspaceTree->expandToDepth(0);
|
||||
}
|
||||
|
||||
void MainWindow::addRecentFile(const QString& path) {
|
||||
|
||||
@@ -178,6 +178,7 @@ private:
|
||||
protected:
|
||||
void changeEvent(QEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
};
|
||||
|
||||
} // namespace rcx
|
||||
|
||||
@@ -21,13 +21,6 @@ inline void buildProjectExplorer(QStandardItemModel* model,
|
||||
model->clear();
|
||||
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
|
||||
struct Entry { const Node* node; void* subPtr; const NodeTree* tree; };
|
||||
QVector<Entry> types, enums;
|
||||
@@ -99,7 +92,7 @@ inline void buildProjectExplorer(QStandardItemModel* model,
|
||||
item->appendRow(childItem);
|
||||
}
|
||||
|
||||
projectItem->appendRow(item);
|
||||
model->appendRow(item);
|
||||
}
|
||||
|
||||
for (const auto& e : enums) {
|
||||
@@ -111,10 +104,8 @@ inline void buildProjectExplorer(QStandardItemModel* model,
|
||||
QIcon(":/vsicons/symbol-enum.svg"), display);
|
||||
item->setData(QVariant::fromValue(e.subPtr), Qt::UserRole);
|
||||
item->setData(QVariant::fromValue(e.node->id), Qt::UserRole + 1);
|
||||
projectItem->appendRow(item);
|
||||
model->appendRow(item);
|
||||
}
|
||||
|
||||
model->appendRow(projectItem);
|
||||
}
|
||||
|
||||
} // 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'\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
|
||||
keyword = None
|
||||
m = re.match(r'^(struct|union|enum)\s+(.+)', line)
|
||||
|
||||
Reference in New Issue
Block a user