diff --git a/CMakeLists.txt b/CMakeLists.txt index 5443f7d..8eacf2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,7 @@ add_executable(Reclass src/mainwindow.h src/mcp/mcp_bridge.h src/mcp/mcp_bridge.cpp + $<$:src/app.rc> ) target_include_directories(Reclass PRIVATE src) @@ -79,8 +80,8 @@ if(WIN32) target_link_libraries(Reclass PRIVATE dbghelp dwmapi psapi) endif() -add_executable(rcx-mcp-stdio tools/rcx-mcp-stdio.cpp) -target_link_libraries(rcx-mcp-stdio PRIVATE ${QT}::Core ${QT}::Network) +add_executable(ReclassMcpBridge tools/rcx-mcp-stdio.cpp) +target_link_libraries(ReclassMcpBridge PRIVATE ${QT}::Core ${QT}::Network) include(deploy) diff --git a/src/app.rc b/src/app.rc new file mode 100644 index 0000000..a6ed31f --- /dev/null +++ b/src/app.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON "icons/class.ico" diff --git a/src/icons/class.ico b/src/icons/class.ico new file mode 100644 index 0000000..dc2e218 Binary files /dev/null and b/src/icons/class.ico differ diff --git a/src/icons/class.png b/src/icons/class.png new file mode 100644 index 0000000..df06749 Binary files /dev/null and b/src/icons/class.png differ diff --git a/src/main.cpp b/src/main.cpp index 46247bd..1e3bc70 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -253,6 +253,14 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) { .arg(t.background.name(), t.textMuted.name(), t.text.name(), t.backgroundAlt.name(), t.hover.name())); } + { + QSettings settings("Reclass", "Reclass"); + QString fontName = settings.value("font", "JetBrains Mono").toString(); + QFont f(fontName, 12); + f.setFixedPitch(true); + if (auto* tb = m_mdiArea->findChild()) + tb->setFont(f); + } setCentralWidget(m_mdiArea); createWorkspaceDock(); @@ -488,13 +496,32 @@ RcxEditor* MainWindow::activePaneEditor() { return pane ? pane->editor : nullptr; } +static QString rootName(const NodeTree& tree, uint64_t viewRootId = 0) { + if (viewRootId != 0) { + int idx = tree.indexOfId(viewRootId); + if (idx >= 0) { + const auto& n = tree.nodes[idx]; + if (!n.structTypeName.isEmpty()) return n.structTypeName; + if (!n.name.isEmpty()) return n.name; + } + } + for (const auto& n : tree.nodes) { + if (n.parentId == 0 && n.kind == NodeKind::Struct) { + if (!n.structTypeName.isEmpty()) return n.structTypeName; + if (!n.name.isEmpty()) return n.name; + } + } + return QStringLiteral("Untitled"); +} + QMdiSubWindow* MainWindow::createTab(RcxDocument* doc) { auto* splitter = new QSplitter(Qt::Horizontal); auto* ctrl = new RcxController(doc, splitter); auto* sub = m_mdiArea->addSubWindow(splitter); + sub->setWindowIcon(QIcon()); // suppress app icon in MDI tabs sub->setWindowTitle(doc->filePath.isEmpty() - ? "Untitled" : QFileInfo(doc->filePath).fileName()); + ? rootName(doc->tree) : QFileInfo(doc->filePath).fileName()); sub->setAttribute(Qt::WA_DeleteOnClose); sub->showMaximized(); @@ -553,8 +580,13 @@ QMdiSubWindow* MainWindow::createTab(RcxDocument* doc) { if (it != m_tabs.end()) QTimer::singleShot(0, this, [this, sub]() { auto it2 = m_tabs.find(sub); - if (it2 != m_tabs.end()) updateAllRenderedPanes(*it2); + if (it2 != m_tabs.end()) { + updateAllRenderedPanes(*it2); + if (it2->doc->filePath.isEmpty()) + sub->setWindowTitle(rootName(it2->doc->tree, it2->ctrl->viewRootId())); + } rebuildWorkspaceModel(); + updateWindowTitle(); }); }); connect(&doc->undoStack, &QUndoStack::indexChanged, @@ -563,7 +595,12 @@ QMdiSubWindow* MainWindow::createTab(RcxDocument* doc) { if (it != m_tabs.end()) QTimer::singleShot(0, this, [this, sub]() { auto it2 = m_tabs.find(sub); - if (it2 != m_tabs.end()) updateAllRenderedPanes(*it2); + if (it2 != m_tabs.end()) { + updateAllRenderedPanes(*it2); + if (it2->doc->filePath.isEmpty()) + sub->setWindowTitle(rootName(it2->doc->tree, it2->ctrl->viewRootId())); + } + updateWindowTitle(); }); }); @@ -677,7 +714,7 @@ void MainWindow::newDocument() { emit doc->documentChanged(); auto* sub = m_mdiArea->activeSubWindow(); - if (sub) sub->setWindowTitle("Untitled"); + if (sub) sub->setWindowTitle(rootName(doc->tree, ctrl->viewRootId())); updateWindowTitle(); rebuildWorkspaceModel(); } @@ -835,7 +872,7 @@ void MainWindow::toggleMcp() { } else { m_mcp->start(); m_mcpAction->setText("Stop &MCP Server"); - m_statusLabel->setText("MCP server listening on pipe: rcx-mcp"); + m_statusLabel->setText("MCP server listening on pipe: ReclassMcpBridge"); } } @@ -916,6 +953,9 @@ void MainWindow::setEditorFont(const QString& fontName) { m_workspaceTree->setFont(f); // Sync status bar font statusBar()->setFont(f); + // Sync MDI tab bar font + if (auto* tb = m_mdiArea->findChild()) + tb->setFont(f); // Sync menu bar / menu font via global stylesheet applyGlobalTheme(ThemeManager::instance().current()); } @@ -947,7 +987,8 @@ void MainWindow::updateWindowTitle() { auto* sub = m_mdiArea->activeSubWindow(); if (sub && m_tabs.contains(sub)) { auto& tab = m_tabs[sub]; - QString name = tab.doc->filePath.isEmpty() ? "Untitled" + QString name = tab.doc->filePath.isEmpty() + ? rootName(tab.doc->tree, tab.ctrl->viewRootId()) : QFileInfo(tab.doc->filePath).fileName(); if (tab.doc->modified) name += " *"; setWindowTitle(name + " - Reclass"); @@ -1325,7 +1366,8 @@ void MainWindow::rebuildWorkspaceModel() { TabState& tab = m_tabs[sub]; QString tabName = tab.doc->filePath.isEmpty() - ? "Untitled" : QFileInfo(tab.doc->filePath).fileName(); + ? rootName(tab.doc->tree, tab.ctrl->viewRootId()) + : QFileInfo(tab.doc->filePath).fileName(); buildWorkspaceModel(m_workspaceModel, tab.doc->tree, tabName, static_cast(sub)); @@ -1461,6 +1503,7 @@ int main(int argc, char* argv[]) { applyGlobalTheme(rcx::ThemeManager::instance().current()); rcx::MainWindow window; + window.setWindowIcon(QIcon(":/icons/class.png")); bool screenshotMode = app.arguments().contains("--screenshot"); if (screenshotMode) diff --git a/src/mcp/mcp_bridge.cpp b/src/mcp/mcp_bridge.cpp index a13c7ad..97b6fb1 100644 --- a/src/mcp/mcp_bridge.cpp +++ b/src/mcp/mcp_bridge.cpp @@ -28,9 +28,9 @@ void McpBridge::start() { m_server->setSocketOptions(QLocalServer::WorldAccessOption); // Remove stale socket (Linux/Mac leave files behind) - QLocalServer::removeServer("rcx-mcp"); + QLocalServer::removeServer("ReclassMcpBridge"); - if (!m_server->listen("rcx-mcp")) { + if (!m_server->listen("ReclassMcpBridge")) { qWarning() << "[MCP] Failed to start server:" << m_server->errorString(); delete m_server; m_server = nullptr; @@ -39,7 +39,7 @@ void McpBridge::start() { connect(m_server, &QLocalServer::newConnection, this, &McpBridge::onNewConnection); - qDebug() << "[MCP] Server listening on pipe: rcx-mcp"; + qDebug() << "[MCP] Server listening on pipe: ReclassMcpBridge"; } void McpBridge::stop() { diff --git a/src/resources.qrc b/src/resources.qrc index cfa974c..550feb7 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -2,6 +2,7 @@ icons/chevron-right.png icons/chevron-down.png + icons/class.png fonts/JetBrainsMono.ttf diff --git a/tools/rcx-mcp-stdio.cpp b/tools/rcx-mcp-stdio.cpp index 26d1c52..508f254 100644 --- a/tools/rcx-mcp-stdio.cpp +++ b/tools/rcx-mcp-stdio.cpp @@ -1,5 +1,5 @@ -// rcx-mcp-stdio: Bridges stdin/stdout to QLocalSocket for MCP transport. -// Claude Desktop spawns this process; it connects to the rcx-mcp named pipe +// ReclassMcpBridge: Bridges stdin/stdout to QLocalSocket for MCP transport. +// Claude Desktop spawns this process; it connects to the ReclassMcpBridge named pipe // inside the running Reclass application. // // stdin (from Claude) → QLocalSocket → McpBridge (in Reclass) @@ -43,7 +43,7 @@ int main(int argc, char* argv[]) { }); QObject::connect(socket, &QLocalSocket::disconnected, [&]() { - fprintf(stderr, "[rcx-mcp-stdio] Disconnected from server\n"); + fprintf(stderr, "[ReclassMcpBridge] Disconnected from server\n"); app.quit(); }); @@ -52,19 +52,19 @@ int main(int argc, char* argv[]) { #else QObject::connect(socket, QOverload::of(&QLocalSocket::error), [&](QLocalSocket::LocalSocketError err) { #endif - fprintf(stderr, "[rcx-mcp-stdio] Socket error %d: %s\n", + fprintf(stderr, "[ReclassMcpBridge] Socket error %d: %s\n", (int)err, socket->errorString().toUtf8().constData()); app.quit(); }); // Connect to the named pipe - socket->connectToServer("rcx-mcp"); + socket->connectToServer("ReclassMcpBridge"); if (!socket->waitForConnected(5000)) { - fprintf(stderr, "[rcx-mcp-stdio] Failed to connect to rcx-mcp pipe: %s\n", + fprintf(stderr, "[ReclassMcpBridge] Failed to connect to ReclassMcpBridge pipe: %s\n", socket->errorString().toUtf8().constData()); return 1; } - fprintf(stderr, "[rcx-mcp-stdio] Connected to rcx-mcp\n"); + fprintf(stderr, "[ReclassMcpBridge] Connected to ReclassMcpBridge\n"); // Stdin → socket: poll stdin with a timer (stdin isn't a socket on Windows) QByteArray stdinBuf;