diff --git a/gui/main.qml b/gui/main.qml index de0a730..e85aab9 100644 --- a/gui/main.qml +++ b/gui/main.qml @@ -146,6 +146,36 @@ ApplicationWindow { ollie.switchAgent(parentId, agentId) } + // Rename tracking + property int renamingIndex: -1 + property string renamingText: "" + + function startRename(index) { + var item = sessionTreeModel.get(index) + if (item.type === "session") { + renamingIndex = index + renamingText = item.displayName + } + } + + function commitRename() { + if (renamingIndex < 0) return + var item = sessionTreeModel.get(renamingIndex) + var newName = renamingText.trim() + if (newName !== "" && newName !== item.id) { + if (ollie.renameSession(item.id, newName)) { + sessionTreeModel.set(renamingIndex, { id: newName, displayName: newName }) + } + } + renamingIndex = -1 + renamingText = "" + } + + function cancelRename() { + renamingIndex = -1 + renamingText = "" + } + Connections { target: ollie function onSessionsChanged() { @@ -500,27 +530,95 @@ ApplicationWindow { } visible: height > 0 + // Click disambiguation: timer-based single vs double click + property int _clickCount: 0 + Timer { + id: clickTimer + interval: 300 + repeat: false + onTriggered: { + parent._clickCount = 0 + if (type === "session" && index !== renamingIndex) { + toggleExpand(index) + } else if (type === "agent") { + activateAgent(parentId, agentId) + } + } + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton + hoverEnabled: false + onClicked: function() { + if (renamingIndex >= 0) return + parent._clickCount++ + if (parent._clickCount === 1) { + clickTimer.start() + } else if (parent._clickCount === 2) { + clickTimer.stop() + parent._clickCount = 0 + if (type === "session") { + startRename(index) + } + } + } + } + contentItem: RowLayout { anchors.fill: parent spacing: 0 // Indent + expand/collapse indicator - Label { + Item { Layout.preferredWidth: (level || 0) * 16 + 16 - text: type === "session" ? (expanded ? "▼" : "▶") : "" - font.pixelSize: theme.fontSize > 0 ? theme.fontSize * 0.7 : 10 - color: theme.textColor - opacity: 0.6 - verticalAlignment: Text.AlignVCenter + Layout.preferredHeight: 28 + + Label { + anchors.centerIn: parent + text: type === "session" ? (expanded ? "▼" : "▶") : "" + font.pixelSize: theme.fontSize > 0 ? theme.fontSize * 0.7 : 10 + color: theme.textColor + opacity: 0.6 + } } - // Name - Label { + // Name area + Item { Layout.fillWidth: true - text: displayName || "" - color: theme.textColor - font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize - verticalAlignment: Text.AlignVCenter + Layout.preferredHeight: 28 + + TextField { + anchors.fill: parent + text: (index === renamingIndex) ? renamingText : "" + visible: index === renamingIndex + verticalAlignment: Text.AlignVCenter + font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize + color: theme.textColor + background: Rectangle { + color: theme.baseColor + border.color: theme.highlightColor + radius: 2 + } + Keys.onReturnPressed: commitRename() + Keys.onEscapePressed: cancelRename() + onAccepted: commitRename() + onVisibleChanged: { + if (visible) { + forceActiveFocus() + selectAll() + } + } + } + + Label { + anchors.fill: parent + text: displayName || "" + color: theme.textColor + font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize + verticalAlignment: Text.AlignVCenter + visible: index !== renamingIndex + } } // State dot @@ -538,14 +636,6 @@ ApplicationWindow { } } - onClicked: { - if (type === "session") { - toggleExpand(index) - } else if (type === "agent") { - activateAgent(parentId, agentId) - } - } - highlighted: (type === "agent" && agentId === ollie.activeAgentId && parentId === ollie.activeSessionId) function parentSessionExpanded(pid) { diff --git a/gui/ollie9pclient.cpp b/gui/ollie9pclient.cpp index 0fce3db..110ba78 100644 --- a/gui/ollie9pclient.cpp +++ b/gui/ollie9pclient.cpp @@ -271,6 +271,42 @@ bool Ollie9pClient::createSession(const QString &cwd, const QString &name, const return true; } +bool Ollie9pClient::renameSession(const QString &sessionId, const QString &newName) +{ + if (sessionId.isEmpty() || newName.isEmpty()) return false; + + // Use Twstat to rename the session directory. + // We need to open the directory and write its stat. + // 9p doesn't have a direct "mv" command, but we can use stat/rdwr + // to read the current stat, modify the name, and write it back. + // However, plan9port's 9p doesn't support wstat directly. + // + // Alternative: use our own ollie-9p which has a mv command. + QString bin = QStandardPaths::findExecutable("ollie-9p"); + if (bin.isEmpty()) { + qDebug() << "renameSession: ollie-9p not found, cannot rename"; + return false; + } + + QProcess proc; + proc.start(bin, {"-a", serverAddr(), "mv", "session/" + sessionId, "session/" + newName}); + proc.waitForFinished(5000); + + if (proc.exitCode() != 0) { + qDebug() << "renameSession failed:" << QString::fromUtf8(proc.readAllStandardError()).trimmed(); + return false; + } + + // Update active session ID if we renamed the active session + if (m_activeSessionId == sessionId) { + m_activeSessionId = newName; + emit activeSessionIdChanged(); + } + + refreshSessions(); + return true; +} + // --- Root data loading (lazy) --- void Ollie9pClient::ensureRootDataLoaded() diff --git a/gui/ollie9pclient.h b/gui/ollie9pclient.h index 29d9132..c7c2d3e 100644 --- a/gui/ollie9pclient.h +++ b/gui/ollie9pclient.h @@ -65,6 +65,7 @@ public: Q_INVOKABLE void loadRootAgents(); Q_INVOKABLE QStringList getAvailableModels(const QString &backend) const; Q_INVOKABLE bool createSession(const QString &cwd, const QString &name, const QString &backend, const QString &model, const QString &agent, const QString &remote); + Q_INVOKABLE bool renameSession(const QString &sessionId, const QString &newName); signals: void sessionsChanged();