Tab titles show root struct name instead of Untitled, sync MDI tab font with editor, rename MCP pipe/exe to ReclassMcpBridge

This commit is contained in:
iamacontributor
2026-02-13 06:51:09 -07:00
committed by sysadmin
parent b153665059
commit c86a6dbc73
8 changed files with 65 additions and 19 deletions

View File

@@ -62,6 +62,7 @@ add_executable(Reclass
src/mainwindow.h src/mainwindow.h
src/mcp/mcp_bridge.h src/mcp/mcp_bridge.h
src/mcp/mcp_bridge.cpp src/mcp/mcp_bridge.cpp
$<$<PLATFORM_ID:Windows>:src/app.rc>
) )
target_include_directories(Reclass PRIVATE src) target_include_directories(Reclass PRIVATE src)
@@ -79,8 +80,8 @@ if(WIN32)
target_link_libraries(Reclass PRIVATE dbghelp dwmapi psapi) target_link_libraries(Reclass PRIVATE dbghelp dwmapi psapi)
endif() endif()
add_executable(rcx-mcp-stdio tools/rcx-mcp-stdio.cpp) add_executable(ReclassMcpBridge tools/rcx-mcp-stdio.cpp)
target_link_libraries(rcx-mcp-stdio PRIVATE ${QT}::Core ${QT}::Network) target_link_libraries(ReclassMcpBridge PRIVATE ${QT}::Core ${QT}::Network)
include(deploy) include(deploy)

1
src/app.rc Normal file
View File

@@ -0,0 +1 @@
IDI_ICON1 ICON "icons/class.ico"

BIN
src/icons/class.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 970 B

BIN
src/icons/class.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 918 B

View File

@@ -253,6 +253,14 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
.arg(t.background.name(), t.textMuted.name(), t.text.name(), .arg(t.background.name(), t.textMuted.name(), t.text.name(),
t.backgroundAlt.name(), t.hover.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<QTabBar*>())
tb->setFont(f);
}
setCentralWidget(m_mdiArea); setCentralWidget(m_mdiArea);
createWorkspaceDock(); createWorkspaceDock();
@@ -488,13 +496,32 @@ RcxEditor* MainWindow::activePaneEditor() {
return pane ? pane->editor : nullptr; 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) { QMdiSubWindow* MainWindow::createTab(RcxDocument* doc) {
auto* splitter = new QSplitter(Qt::Horizontal); auto* splitter = new QSplitter(Qt::Horizontal);
auto* ctrl = new RcxController(doc, splitter); auto* ctrl = new RcxController(doc, splitter);
auto* sub = m_mdiArea->addSubWindow(splitter); auto* sub = m_mdiArea->addSubWindow(splitter);
sub->setWindowIcon(QIcon()); // suppress app icon in MDI tabs
sub->setWindowTitle(doc->filePath.isEmpty() sub->setWindowTitle(doc->filePath.isEmpty()
? "Untitled" : QFileInfo(doc->filePath).fileName()); ? rootName(doc->tree) : QFileInfo(doc->filePath).fileName());
sub->setAttribute(Qt::WA_DeleteOnClose); sub->setAttribute(Qt::WA_DeleteOnClose);
sub->showMaximized(); sub->showMaximized();
@@ -553,8 +580,13 @@ QMdiSubWindow* MainWindow::createTab(RcxDocument* doc) {
if (it != m_tabs.end()) if (it != m_tabs.end())
QTimer::singleShot(0, this, [this, sub]() { QTimer::singleShot(0, this, [this, sub]() {
auto it2 = m_tabs.find(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(); rebuildWorkspaceModel();
updateWindowTitle();
}); });
}); });
connect(&doc->undoStack, &QUndoStack::indexChanged, connect(&doc->undoStack, &QUndoStack::indexChanged,
@@ -563,7 +595,12 @@ QMdiSubWindow* MainWindow::createTab(RcxDocument* doc) {
if (it != m_tabs.end()) if (it != m_tabs.end())
QTimer::singleShot(0, this, [this, sub]() { QTimer::singleShot(0, this, [this, sub]() {
auto it2 = m_tabs.find(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(); emit doc->documentChanged();
auto* sub = m_mdiArea->activeSubWindow(); auto* sub = m_mdiArea->activeSubWindow();
if (sub) sub->setWindowTitle("Untitled"); if (sub) sub->setWindowTitle(rootName(doc->tree, ctrl->viewRootId()));
updateWindowTitle(); updateWindowTitle();
rebuildWorkspaceModel(); rebuildWorkspaceModel();
} }
@@ -835,7 +872,7 @@ void MainWindow::toggleMcp() {
} else { } else {
m_mcp->start(); m_mcp->start();
m_mcpAction->setText("Stop &MCP Server"); 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); m_workspaceTree->setFont(f);
// Sync status bar font // Sync status bar font
statusBar()->setFont(f); statusBar()->setFont(f);
// Sync MDI tab bar font
if (auto* tb = m_mdiArea->findChild<QTabBar*>())
tb->setFont(f);
// Sync menu bar / menu font via global stylesheet // Sync menu bar / menu font via global stylesheet
applyGlobalTheme(ThemeManager::instance().current()); applyGlobalTheme(ThemeManager::instance().current());
} }
@@ -947,7 +987,8 @@ void MainWindow::updateWindowTitle() {
auto* sub = m_mdiArea->activeSubWindow(); auto* sub = m_mdiArea->activeSubWindow();
if (sub && m_tabs.contains(sub)) { if (sub && m_tabs.contains(sub)) {
auto& tab = m_tabs[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(); : QFileInfo(tab.doc->filePath).fileName();
if (tab.doc->modified) name += " *"; if (tab.doc->modified) name += " *";
setWindowTitle(name + " - Reclass"); setWindowTitle(name + " - Reclass");
@@ -1325,7 +1366,8 @@ void MainWindow::rebuildWorkspaceModel() {
TabState& tab = m_tabs[sub]; TabState& tab = m_tabs[sub];
QString tabName = tab.doc->filePath.isEmpty() 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, buildWorkspaceModel(m_workspaceModel, tab.doc->tree, tabName,
static_cast<void*>(sub)); static_cast<void*>(sub));
@@ -1461,6 +1503,7 @@ int main(int argc, char* argv[]) {
applyGlobalTheme(rcx::ThemeManager::instance().current()); applyGlobalTheme(rcx::ThemeManager::instance().current());
rcx::MainWindow window; rcx::MainWindow window;
window.setWindowIcon(QIcon(":/icons/class.png"));
bool screenshotMode = app.arguments().contains("--screenshot"); bool screenshotMode = app.arguments().contains("--screenshot");
if (screenshotMode) if (screenshotMode)

View File

@@ -28,9 +28,9 @@ void McpBridge::start() {
m_server->setSocketOptions(QLocalServer::WorldAccessOption); m_server->setSocketOptions(QLocalServer::WorldAccessOption);
// Remove stale socket (Linux/Mac leave files behind) // 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(); qWarning() << "[MCP] Failed to start server:" << m_server->errorString();
delete m_server; delete m_server;
m_server = nullptr; m_server = nullptr;
@@ -39,7 +39,7 @@ void McpBridge::start() {
connect(m_server, &QLocalServer::newConnection, connect(m_server, &QLocalServer::newConnection,
this, &McpBridge::onNewConnection); this, &McpBridge::onNewConnection);
qDebug() << "[MCP] Server listening on pipe: rcx-mcp"; qDebug() << "[MCP] Server listening on pipe: ReclassMcpBridge";
} }
void McpBridge::stop() { void McpBridge::stop() {

View File

@@ -2,6 +2,7 @@
<qresource prefix="/icons"> <qresource prefix="/icons">
<file alias="chevron-right.png">icons/chevron-right.png</file> <file alias="chevron-right.png">icons/chevron-right.png</file>
<file alias="chevron-down.png">icons/chevron-down.png</file> <file alias="chevron-down.png">icons/chevron-down.png</file>
<file alias="class.png">icons/class.png</file>
</qresource> </qresource>
<qresource prefix="/fonts"> <qresource prefix="/fonts">
<file alias="JetBrainsMono.ttf">fonts/JetBrainsMono.ttf</file> <file alias="JetBrainsMono.ttf">fonts/JetBrainsMono.ttf</file>

View File

@@ -1,5 +1,5 @@
// rcx-mcp-stdio: Bridges stdin/stdout to QLocalSocket for MCP transport. // ReclassMcpBridge: Bridges stdin/stdout to QLocalSocket for MCP transport.
// Claude Desktop spawns this process; it connects to the rcx-mcp named pipe // Claude Desktop spawns this process; it connects to the ReclassMcpBridge named pipe
// inside the running Reclass application. // inside the running Reclass application.
// //
// stdin (from Claude) → QLocalSocket → McpBridge (in Reclass) // stdin (from Claude) → QLocalSocket → McpBridge (in Reclass)
@@ -43,7 +43,7 @@ int main(int argc, char* argv[]) {
}); });
QObject::connect(socket, &QLocalSocket::disconnected, [&]() { QObject::connect(socket, &QLocalSocket::disconnected, [&]() {
fprintf(stderr, "[rcx-mcp-stdio] Disconnected from server\n"); fprintf(stderr, "[ReclassMcpBridge] Disconnected from server\n");
app.quit(); app.quit();
}); });
@@ -52,19 +52,19 @@ int main(int argc, char* argv[]) {
#else #else
QObject::connect(socket, QOverload<QLocalSocket::LocalSocketError>::of(&QLocalSocket::error), [&](QLocalSocket::LocalSocketError err) { QObject::connect(socket, QOverload<QLocalSocket::LocalSocketError>::of(&QLocalSocket::error), [&](QLocalSocket::LocalSocketError err) {
#endif #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()); (int)err, socket->errorString().toUtf8().constData());
app.quit(); app.quit();
}); });
// Connect to the named pipe // Connect to the named pipe
socket->connectToServer("rcx-mcp"); socket->connectToServer("ReclassMcpBridge");
if (!socket->waitForConnected(5000)) { 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()); socket->errorString().toUtf8().constData());
return 1; 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) // Stdin → socket: poll stdin with a timer (stdin isn't a socket on Windows)
QByteArray stdinBuf; QByteArray stdinBuf;