From ef73895823046f82bf18eca76a8316aaadb840e2 Mon Sep 17 00:00:00 2001
From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com>
Date: Wed, 19 Nov 2025 01:15:43 +0100
Subject: [PATCH] feat: Add open in editor and remove from list (#267)
* refactor: Rename and move chat items
* feat: Add hotkeys for open in editor and remove file from list
* feat: Add opening by system
* feat: Add context action menu
---
ChatView/CMakeLists.txt | 28 +++----
ChatView/FileItem.cpp | 76 +++++++++++++++++++
ChatView/FileItem.hpp | 48 ++++++++++++
ChatView/qml/RootItem.qml | 9 ++-
ChatView/qml/{ => chatparts}/ChatItem.qml | 2 -
.../qml/{dialog => chatparts}/CodeBlock.qml | 0
.../FileEditBlock.qml} | 0
.../qml/{dialog => chatparts}/TextBlock.qml | 0
.../ThinkingBlock.qml} | 0
.../ToolBlock.qml} | 0
.../AttachedFilesPlace.qml | 61 +++++++++++++--
.../qml/{parts => controls}/BottomBar.qml | 0
.../FileEditsActionBar.qml | 0
.../qml/{parts => controls}/RulesViewer.qml | 0
ChatView/qml/{parts => controls}/Toast.qml | 0
ChatView/qml/{parts => controls}/TopBar.qml | 0
16 files changed, 199 insertions(+), 25 deletions(-)
create mode 100644 ChatView/FileItem.cpp
create mode 100644 ChatView/FileItem.hpp
rename ChatView/qml/{ => chatparts}/ChatItem.qml (99%)
rename ChatView/qml/{dialog => chatparts}/CodeBlock.qml (100%)
rename ChatView/qml/{FileEditItem.qml => chatparts/FileEditBlock.qml} (100%)
rename ChatView/qml/{dialog => chatparts}/TextBlock.qml (100%)
rename ChatView/qml/{ThinkingStatusItem.qml => chatparts/ThinkingBlock.qml} (100%)
rename ChatView/qml/{ToolStatusItem.qml => chatparts/ToolBlock.qml} (100%)
rename ChatView/qml/{parts => controls}/AttachedFilesPlace.qml (62%)
rename ChatView/qml/{parts => controls}/BottomBar.qml (100%)
rename ChatView/qml/{parts => controls}/FileEditsActionBar.qml (100%)
rename ChatView/qml/{parts => controls}/RulesViewer.qml (100%)
rename ChatView/qml/{parts => controls}/Toast.qml (100%)
rename ChatView/qml/{parts => controls}/TopBar.qml (100%)
diff --git a/ChatView/CMakeLists.txt b/ChatView/CMakeLists.txt
index d5d790a..8acc334 100644
--- a/ChatView/CMakeLists.txt
+++ b/ChatView/CMakeLists.txt
@@ -10,18 +10,20 @@ qt_add_qml_module(QodeAssistChatView
QtQuick
QML_FILES
qml/RootItem.qml
- qml/ChatItem.qml
- qml/dialog/CodeBlock.qml
- qml/dialog/TextBlock.qml
- qml/parts/TopBar.qml
- qml/parts/BottomBar.qml
- qml/parts/AttachedFilesPlace.qml
- qml/parts/Toast.qml
- qml/ToolStatusItem.qml
- qml/ThinkingStatusItem.qml
- qml/FileEditItem.qml
- qml/parts/RulesViewer.qml
- qml/parts/FileEditsActionBar.qml
+
+ qml/chatparts/CodeBlock.qml
+ qml/chatparts/FileEditBlock.qml
+ qml/chatparts/TextBlock.qml
+ qml/chatparts/ThinkingBlock.qml
+ qml/chatparts/ToolBlock.qml
+ qml/chatparts/ChatItem.qml
+
+ qml/controls/AttachedFilesPlace.qml
+ qml/controls/BottomBar.qml
+ qml/controls/FileEditsActionBar.qml
+ qml/controls/RulesViewer.qml
+ qml/controls/Toast.qml
+ qml/controls/TopBar.qml
RESOURCES
icons/attach-file-light.svg
@@ -56,7 +58,7 @@ qt_add_qml_module(QodeAssistChatView
ChatSerializer.hpp ChatSerializer.cpp
ChatView.hpp ChatView.cpp
ChatData.hpp
-
+ FileItem.hpp FileItem.cpp
)
target_link_libraries(QodeAssistChatView
diff --git a/ChatView/FileItem.cpp b/ChatView/FileItem.cpp
new file mode 100644
index 0000000..ac9c16d
--- /dev/null
+++ b/ChatView/FileItem.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2025 Petr Mironychev
+ *
+ * This file is part of QodeAssist.
+ *
+ * QodeAssist is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * QodeAssist is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with QodeAssist. If not, see .
+ */
+
+#include "FileItem.hpp"
+
+#include
+#include
+
+#include
+#include
+#include
+
+namespace QodeAssist::Chat {
+
+FileItem::FileItem(QQuickItem *parent)
+ : QQuickItem(parent)
+{}
+
+void FileItem::openFileInEditor()
+{
+ if (m_filePath.isEmpty()) {
+ return;
+ }
+
+ Utils::FilePath filePathObj = Utils::FilePath::fromString(m_filePath);
+ Core::IEditor *editor = Core::EditorManager::openEditor(filePathObj);
+
+ if (!editor) {
+ LOG_MESSAGE(QString("Failed to open file in editor: %1").arg(m_filePath));
+ }
+}
+
+void FileItem::openFileInExternalEditor()
+{
+ if (m_filePath.isEmpty()) {
+ return;
+ }
+
+ bool success = QDesktopServices::openUrl(QUrl::fromLocalFile(m_filePath));
+ if (success) {
+ LOG_MESSAGE(QString("Opened file in external application: %1").arg(m_filePath));
+ } else {
+ LOG_MESSAGE(QString("Failed to open file externally: %1").arg(m_filePath));
+ }
+}
+
+QString FileItem::filePath() const
+{
+ return m_filePath;
+}
+
+void FileItem::setFilePath(const QString &newFilePath)
+{
+ if (m_filePath == newFilePath)
+ return;
+ m_filePath = newFilePath;
+ emit filePathChanged();
+}
+
+} // namespace QodeAssist::Chat
diff --git a/ChatView/FileItem.hpp b/ChatView/FileItem.hpp
new file mode 100644
index 0000000..3f43278
--- /dev/null
+++ b/ChatView/FileItem.hpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2025 Petr Mironychev
+ *
+ * This file is part of QodeAssist.
+ *
+ * QodeAssist is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * QodeAssist is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with QodeAssist. If not, see .
+ */
+
+#pragma once
+
+#include
+
+namespace QodeAssist::Chat {
+
+class FileItem: public QQuickItem
+{
+ Q_OBJECT
+ QML_NAMED_ELEMENT(FileItem)
+
+ Q_PROPERTY(QString filePath READ filePath WRITE setFilePath NOTIFY filePathChanged)
+
+public:
+ FileItem(QQuickItem *parent = nullptr);
+
+ Q_INVOKABLE void openFileInEditor();
+ Q_INVOKABLE void openFileInExternalEditor();
+
+ QString filePath() const;
+ void setFilePath(const QString &newFilePath);
+
+signals:
+ void filePathChanged();
+
+private:
+ QString m_filePath;
+};
+}
diff --git a/ChatView/qml/RootItem.qml b/ChatView/qml/RootItem.qml
index a5be9d8..421ea12 100644
--- a/ChatView/qml/RootItem.qml
+++ b/ChatView/qml/RootItem.qml
@@ -24,7 +24,8 @@ import QtQuick.Layouts
import ChatView
import UIControls
import Qt.labs.platform as Platform
-import "./parts"
+
+import "./chatparts"
ChatRootView {
id: root
@@ -188,7 +189,7 @@ ChatRootView {
Component {
id: toolMessageComponent
- ToolStatusItem {
+ ToolBlock {
id: toolsItem
width: parent.width
@@ -208,7 +209,7 @@ ChatRootView {
Component {
id: fileEditMessageComponent
- FileEditItem {
+ FileEditBlock {
width: parent.width
editContent: model.content
@@ -233,7 +234,7 @@ ChatRootView {
Component {
id: thinkingMessageComponent
- ThinkingStatusItem {
+ ThinkingBlock {
id: thinking
width: parent.width
diff --git a/ChatView/qml/ChatItem.qml b/ChatView/qml/chatparts/ChatItem.qml
similarity index 99%
rename from ChatView/qml/ChatItem.qml
rename to ChatView/qml/chatparts/ChatItem.qml
index e4a3416..389e2ef 100644
--- a/ChatView/qml/ChatItem.qml
+++ b/ChatView/qml/chatparts/ChatItem.qml
@@ -23,8 +23,6 @@ import QtQuick.Controls
import QtQuick.Layouts
import UIControls
-import "./dialog"
-
Rectangle {
id: root
diff --git a/ChatView/qml/dialog/CodeBlock.qml b/ChatView/qml/chatparts/CodeBlock.qml
similarity index 100%
rename from ChatView/qml/dialog/CodeBlock.qml
rename to ChatView/qml/chatparts/CodeBlock.qml
diff --git a/ChatView/qml/FileEditItem.qml b/ChatView/qml/chatparts/FileEditBlock.qml
similarity index 100%
rename from ChatView/qml/FileEditItem.qml
rename to ChatView/qml/chatparts/FileEditBlock.qml
diff --git a/ChatView/qml/dialog/TextBlock.qml b/ChatView/qml/chatparts/TextBlock.qml
similarity index 100%
rename from ChatView/qml/dialog/TextBlock.qml
rename to ChatView/qml/chatparts/TextBlock.qml
diff --git a/ChatView/qml/ThinkingStatusItem.qml b/ChatView/qml/chatparts/ThinkingBlock.qml
similarity index 100%
rename from ChatView/qml/ThinkingStatusItem.qml
rename to ChatView/qml/chatparts/ThinkingBlock.qml
diff --git a/ChatView/qml/ToolStatusItem.qml b/ChatView/qml/chatparts/ToolBlock.qml
similarity index 100%
rename from ChatView/qml/ToolStatusItem.qml
rename to ChatView/qml/chatparts/ToolBlock.qml
diff --git a/ChatView/qml/parts/AttachedFilesPlace.qml b/ChatView/qml/controls/AttachedFilesPlace.qml
similarity index 62%
rename from ChatView/qml/parts/AttachedFilesPlace.qml
rename to ChatView/qml/controls/AttachedFilesPlace.qml
index 8ae6396..739d3f5 100644
--- a/ChatView/qml/parts/AttachedFilesPlace.qml
+++ b/ChatView/qml/controls/AttachedFilesPlace.qml
@@ -40,19 +40,68 @@ Flow {
Repeater {
id: attachRepeater
- delegate: Rectangle {
+ delegate: FileItem {
+ id: fileItem
+
required property int index
required property string modelData
+
+ filePath: modelData
height: 30
width: contentRow.width + 10
- radius: 4
- color: palette.button
- border.width: 1
- border.color: mouse.hovered ? palette.highlight : root.accentColor
- HoverHandler {
+ Rectangle {
+ anchors.fill: parent
+ radius: 4
+ color: palette.button
+ border.width: 1
+ border.color: mouse.containsMouse ? palette.highlight : root.accentColor
+ }
+
+ MouseArea {
id: mouse
+
+ anchors.fill: parent
+ hoverEnabled: true
+ acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
+ onClicked: (mouse) => {
+ if (mouse.button === Qt.RightButton) {
+ contextMenu.popup()
+ } else if (mouse.button === Qt.MiddleButton ||
+ (mouse.button === Qt.LeftButton && (mouse.modifiers & Qt.ControlModifier))) {
+ root.removeFileFromListByIndex(fileItem.index)
+ } else if (mouse.modifiers & Qt.ShiftModifier) {
+ fileItem.openFileInExternalEditor()
+ } else {
+ fileItem.openFileInEditor()
+ }
+ }
+
+ ToolTip.visible: containsMouse
+ ToolTip.delay: 500
+ ToolTip.text: "Click: Open in Qt Creator\nShift+Click: Open in external editor\nCtrl+Click / Middle Click: Remove"
+ }
+
+ Menu {
+ id: contextMenu
+
+ MenuItem {
+ text: "Open in Qt Creator"
+ onTriggered: fileItem.openFileInEditor()
+ }
+
+ MenuItem {
+ text: "Open in External Editor"
+ onTriggered: fileItem.openFileInExternalEditor()
+ }
+
+ MenuSeparator {}
+
+ MenuItem {
+ text: "Remove"
+ onTriggered: root.removeFileFromListByIndex(fileItem.index)
+ }
}
Row {
diff --git a/ChatView/qml/parts/BottomBar.qml b/ChatView/qml/controls/BottomBar.qml
similarity index 100%
rename from ChatView/qml/parts/BottomBar.qml
rename to ChatView/qml/controls/BottomBar.qml
diff --git a/ChatView/qml/parts/FileEditsActionBar.qml b/ChatView/qml/controls/FileEditsActionBar.qml
similarity index 100%
rename from ChatView/qml/parts/FileEditsActionBar.qml
rename to ChatView/qml/controls/FileEditsActionBar.qml
diff --git a/ChatView/qml/parts/RulesViewer.qml b/ChatView/qml/controls/RulesViewer.qml
similarity index 100%
rename from ChatView/qml/parts/RulesViewer.qml
rename to ChatView/qml/controls/RulesViewer.qml
diff --git a/ChatView/qml/parts/Toast.qml b/ChatView/qml/controls/Toast.qml
similarity index 100%
rename from ChatView/qml/parts/Toast.qml
rename to ChatView/qml/controls/Toast.qml
diff --git a/ChatView/qml/parts/TopBar.qml b/ChatView/qml/controls/TopBar.qml
similarity index 100%
rename from ChatView/qml/parts/TopBar.qml
rename to ChatView/qml/controls/TopBar.qml