From c16eefd0f2886fce0a68e68a3ac58946195bfd4d Mon Sep 17 00:00:00 2001 From: Skylar Date: Mon, 5 Feb 2024 16:57:54 +0100 Subject: [PATCH] fix:#50 feat:#46 console improovements: - added multiline console selection - ctrl-c now dependent on selection text status - fixed autoscroll bug --- src/Plugins/System/Terminal/Terminal.cpp | 17 +++ src/Plugins/System/Terminal/Terminal.h | 10 ++ src/Plugins/System/Terminal/qml/Terminal.qml | 144 +++++++++++++++++- .../System/Terminal/qml/TerminalExec.qml | 62 +++++--- .../System/Terminal/qml/TerminalLine.qml | 33 +++- .../qml/DatalinkInspectorView.qml | 8 + src/lib/ApxCore/App/AppNotify.h | 1 + src/lib/ApxCore/App/AppNotifyListModel.cpp | 18 +++ src/lib/ApxCore/App/AppNotifyListModel.h | 5 +- src/main/qml/Apx/Common/LogListView.qml | 14 +- 10 files changed, 277 insertions(+), 35 deletions(-) diff --git a/src/Plugins/System/Terminal/Terminal.cpp b/src/Plugins/System/Terminal/Terminal.cpp index bdafce1c..d7968cbd 100644 --- a/src/Plugins/System/Terminal/Terminal.cpp +++ b/src/Plugins/System/Terminal/Terminal.cpp @@ -46,6 +46,8 @@ Terminal::Terminal(Fact *parent) qmlRegisterUncreatableType("APX.Terminal", 1, 0, "Terminal", "Reference only"); loadQml("qrc:/" PLUGIN_NAME "/TerminalPlugin.qml"); + + connect(clipboard, &QClipboard::dataChanged, this, &Terminal::clipboardContentChangedSignal); } void Terminal::exec(QString cmd) @@ -240,6 +242,21 @@ QString Terminal::autocomplete(QString cmd) return c; } +bool Terminal::isClipboardEmpty() const +{ + return clipboard->text().isEmpty(); +} + +void Terminal::copyConsoleHistoryToClipboard() const +{ + AppNotify::instance()->copyTextToClipboardSignal(); +} + +void Terminal::copyTextToClipboard(const QString &text) const +{ + clipboard->setText(text); +} + QMap Terminal::_get_js_properties(QString scope, QString flt) { QMap map; diff --git a/src/Plugins/System/Terminal/Terminal.h b/src/Plugins/System/Terminal/Terminal.h index 6769e144..cebe3ad0 100644 --- a/src/Plugins/System/Terminal/Terminal.h +++ b/src/Plugins/System/Terminal/Terminal.h @@ -22,6 +22,8 @@ #pragma once #include +#include +#include #include #include @@ -40,11 +42,18 @@ class Terminal : public Fact Q_INVOKABLE QString autocomplete(QString cmd); + Q_INVOKABLE bool isClipboardEmpty() const; + + Q_INVOKABLE void copyConsoleHistoryToClipboard() const; + + Q_INVOKABLE void copyTextToClipboard(const QString &text) const; + private: int _enterIndex; QStringList _history; int _historyIndex; QString _replacedHistory; + QClipboard *clipboard{QGuiApplication::clipboard()}; QMap _get_js_properties(QString scope, QString flt); @@ -54,4 +63,5 @@ public slots: signals: void newMessage(QtMsgType type, QString category, QString text); + void clipboardContentChangedSignal(); }; diff --git a/src/Plugins/System/Terminal/qml/Terminal.qml b/src/Plugins/System/Terminal/qml/Terminal.qml index 2fcb0298..62f4f3be 100755 --- a/src/Plugins/System/Terminal/qml/Terminal.qml +++ b/src/Plugins/System/Terminal/qml/Terminal.qml @@ -50,9 +50,63 @@ Rectangle { Layout.alignment: Qt.AlignBottom Layout.preferredWidth: 300 Layout.preferredHeight: 400 + currentIndex: -1 + + property list selectedItems spacing: lineSpace + Menu { + id: contextMenu + MenuItem { + text: "Cut" + enabled: listView.footerItem.isSomeSelection() + onTriggered: { + listView.footerItem.cut() + } + } + MenuItem { + text: "Copy" + enabled: listView.footerItem.isSomeSelection() || (listView.currentIndex !== -1) + onTriggered: { + if (listView.currentIndex !== -1) + listView.copySelectedLines() + else + listView.footerItem.copy() + } + } + MenuItem { + text: "Paste" + enabled: !listView.footerItem.isClipboardEmpty + onTriggered: { + listView.footerItem.paste() + listView.footerItem.setFocus() + } + } + MenuItem { + text: "Copy all" + onTriggered: { + listView.footerItem.copyAll() + } + } + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: contextMenu.popup() + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.MiddleButton + onWheel: { + if (wheel.angleDelta.y > 0) + listView.focus=false + wheel.accepted=false + } + } + model: application.notifyModel delegate: TerminalLine { width: listView.width @@ -75,13 +129,97 @@ Rectangle { easing.type: Easing.OutCubic } } - + + onCountChanged: scrollTimer.start() + Timer { + id: scrollTimer + interval: 1 + onTriggered: listView.scrollToEnd() + } + + Keys.onPressed: { + if (event.key === Qt.Key_C && (event.modifiers & (Qt.ControlModifier | Qt.MetaModifier))) { + copySelectedLines() + } + if ((event.text.length == 1 && event.modifiers == Qt.NoModifier) || event.key == Qt.Key_Backspace) { + event.accepted=true + footerItem.setFocus(); + if (event.key != Qt.Key_Backspace) + footerItem.appendCmd(event.text) + else + footerItem.doBackSpace() + } + } + Keys.onTabPressed: { + event.accepted=true + footerItem.hints() + footerItem.setFocus() + } + Keys.onEnterPressed: { + event.accepted = true + footerItem.exec() + } + Keys.onReturnPressed: { + event.accepted = true + footerItem.exec() + } + Keys.onUpPressed: { + footerItem.setFocus() + footerItem.upPressed(event) + } + Keys.onDownPressed: { + footerItem.setFocus() + footerItem.downPressed(event) + } + footer: TerminalExec { width: parent.width - onFocused: listView.scrollToEnd() + onFocused: { + listView.scrollToEnd() + listView.currentIndex = -1 + + for (var i = 0; i < listView.selectedItems.length; i++) { + listView.selectedItems[i].selected=false + } + listView.selectedItems.length=0 + } + } + + function copySelectedLines() + { + var selectedText = "" + for (var i = 0; i < selectedItems.length; i++) { + selectedText += selectedItems[i].text + '\n' + } + footerItem.copyText(selectedText) } + } + } + + Item { + anchors { + top: parent.top + right: parent.right + rightMargin: 35 + topMargin: 15 + } - onClicked: listView.footerItem.focusRequested() + Rectangle { + width: 25 + height: 20 + color: "#80808080" + + Text { + anchors.centerIn: parent + text: "☰" + color: "white" + font.pixelSize: 12 + } + + MouseArea { + anchors.fill: parent + onClicked: contextMenu.popup() + } } } } diff --git a/src/Plugins/System/Terminal/qml/TerminalExec.qml b/src/Plugins/System/Terminal/qml/TerminalExec.qml index 44209219..c98fd18b 100755 --- a/src/Plugins/System/Terminal/qml/TerminalExec.qml +++ b/src/Plugins/System/Terminal/qml/TerminalExec.qml @@ -19,7 +19,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ -import QtQuick 2.7 +import QtQuick 2.15 import QtQuick.Layouts 1.3 import QtQuick.Controls 2.2 @@ -34,16 +34,17 @@ Rectangle{ readonly property string user: apx.vehicles.current.title readonly property string pdel: "> " readonly property string prefix: user+pdel + property bool isClipboardEmpty: terminal.isClipboardEmpty() property var terminal: apx.tools.terminal signal focused() - - signal focusRequested() //fwd to text field - + signal upPressed(var event) + signal downPressed(var event) + onUpPressed: (event) => cmdText.Keys.upPressed(event) + onDownPressed: (event) => cmdText.Keys.downPressed(event) onPrefixChanged: setCmd(getCmd()) - onFocusRequested: cmdText.forceActiveFocus() TextInput { id: cmdText @@ -56,6 +57,7 @@ Rectangle{ readonly property int pos0: text.indexOf(pdel)+pdel.length readonly property int pos: cursorPosition-pos0 text: prefix //+"" + persistentSelection: true onActiveFocusChanged: if(activeFocus)focused() @@ -79,12 +81,12 @@ Rectangle{ onTriggered: if(cmdText.selectionStart") readonly property bool bold: { if(source==AppNotify.FromInput) return true @@ -63,11 +66,37 @@ RowLayout { Label { Layout.fillWidth: true focus: false - color: control.color + color: selected ? "#0977e3" : control.color font: apx.font(lineSize,control.bold) wrapMode: Text.WrapAnywhere text: control.text textFormat: html?Text.RichText:Text.AutoText + + Keys.onUpPressed: listView.Keys.upPressed(event) + Keys.onDownPressed: listView.Keys.downPressed(event) + + MouseArea { + acceptedButtons: Qt.LeftButton + anchors.fill: parent + onClicked: { + if (mouse.modifiers & (Qt.ControlModifier | Qt.MetaModifier)) { + listView.currentIndex = index + listView.currentItem.id = index + listView.selectedItems.push(listView.currentItem) + for (var i = listView.selectedItems.length - 1; i >= 1 && + listView.selectedItems[i].id < listView.selectedItems[i-1].id; i--) { + var tmp = listView.selectedItems[i] + listView.selectedItems[i] = listView.selectedItems[i-1] + listView.selectedItems[i-1]=tmp + } + selected=true + forceActiveFocus() + } + if (mouse.modifiers == Qt.NoModifier) { + listView.footerItem.setFocus() + } + } + } } Label { Layout.alignment: Qt.AlignRight|Qt.AlignVCenter diff --git a/src/Plugins/Tools/DatalinkInspector/qml/DatalinkInspectorView.qml b/src/Plugins/Tools/DatalinkInspector/qml/DatalinkInspectorView.qml index 78aa5ba3..96ebbc32 100644 --- a/src/Plugins/Tools/DatalinkInspector/qml/DatalinkInspectorView.qml +++ b/src/Plugins/Tools/DatalinkInspector/qml/DatalinkInspectorView.qml @@ -33,4 +33,12 @@ LogListView { width: listView.width packet: model.blocks } + + MouseArea { + anchors.fill: parent + onClicked: { + listView.scrollToEnd() + listView.clicked() + } + } } diff --git a/src/lib/ApxCore/App/AppNotify.h b/src/lib/ApxCore/App/AppNotify.h index 0bd7d967..33f254d4 100755 --- a/src/lib/ApxCore/App/AppNotify.h +++ b/src/lib/ApxCore/App/AppNotify.h @@ -68,5 +68,6 @@ private slots: signals: void notification(QString msg, QString subsystem, AppNotify::NotifyFlags flags, Fact *fact); + void copyTextToClipboardSignal(); }; Q_DECLARE_OPERATORS_FOR_FLAGS(AppNotify::NotifyFlags) diff --git a/src/lib/ApxCore/App/AppNotifyListModel.cpp b/src/lib/ApxCore/App/AppNotifyListModel.cpp index 22938325..382bcde5 100644 --- a/src/lib/ApxCore/App/AppNotifyListModel.cpp +++ b/src/lib/ApxCore/App/AppNotifyListModel.cpp @@ -21,6 +21,9 @@ */ #include "AppNotifyListModel.h" #include +#include +#include +#include AppNotifyListModel::AppNotifyListModel(AppNotify *appNotify) : QAbstractListModel(appNotify) @@ -28,6 +31,10 @@ AppNotifyListModel::AppNotifyListModel(AppNotify *appNotify) qRegisterMetaType("QtMsgType"); connect(appNotify, &AppNotify::notification, this, &AppNotifyListModel::notification); + connect(appNotify, + &AppNotify::copyTextToClipboardSignal, + this, + &AppNotifyListModel::copyTextToClipboardSlot); } AppNotifyListModel::~AppNotifyListModel() { @@ -77,6 +84,17 @@ QVariant AppNotifyListModel::data(const QModelIndex &index, int role) const return QVariant(); } +void AppNotifyListModel::copyTextToClipboardSlot() const +{ + QString log = std::accumulate(std::cbegin(m_items), + std::cend(m_items), + QString(), + [](auto a, auto b) { + return std::move(a).append('\n' + b->text); + }); + QGuiApplication::clipboard()->setText(std::move(log)); +} + void AppNotifyListModel::notification(QString msg, QString subsystem, AppNotify::NotifyFlags flags, diff --git a/src/lib/ApxCore/App/AppNotifyListModel.h b/src/lib/ApxCore/App/AppNotifyListModel.h index b85f4937..7e796908 100755 --- a/src/lib/ApxCore/App/AppNotifyListModel.h +++ b/src/lib/ApxCore/App/AppNotifyListModel.h @@ -66,7 +66,10 @@ class AppNotifyListModel : public QAbstractListModel }; QList m_items; -protected: +private slots: + void notification(QString msg, QString subsystem, AppNotify::NotifyFlags flags, Fact *fact); + void copyTextToClipboardSlot() const; + signals: void countChanged(); diff --git a/src/main/qml/Apx/Common/LogListView.qml b/src/main/qml/Apx/Common/LogListView.qml index 93776e91..24d17742 100644 --- a/src/main/qml/Apx/Common/LogListView.qml +++ b/src/main/qml/Apx/Common/LogListView.qml @@ -36,8 +36,13 @@ ListView { keyNavigationEnabled: false ScrollBar.vertical: ScrollBar { + id: scrollBar width: Style.buttonSize/4 active: !listView.atYEnd + onPressedChanged: { + if (pressed) + listView.focus=false + } } boundsBehavior: Flickable.StopAtBounds @@ -49,6 +54,7 @@ ListView { Component.onCompleted: scrollToEnd() function scrollToEnd() { + positionViewAtEnd() positionViewAtEnd() stickEnd=true } @@ -67,12 +73,4 @@ ListView { } } } - - MouseArea { - anchors.fill: parent - onClicked: { - listView.scrollToEnd() - listView.clicked() - } - } }