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