diff --git a/assets/qml/Windows/Console.qml b/assets/qml/Windows/Console.qml index f4ad7970..b3ab722a 100644 --- a/assets/qml/Windows/Console.qml +++ b/assets/qml/Windows/Console.qml @@ -41,6 +41,13 @@ Control { // property alias text: textArea.text readonly property color consoleColor: "#8ecd9d" + + // + // Hacks to allow context menu to work + // + property int curPos + property int selectEnd + property int selectStart // // Function to send through serial port data @@ -62,6 +69,42 @@ Control { property alias displayMode: displayModeCombo.currentIndex } + // + // Shortcut to copy selection of console + // + Shortcut { + sequence: StandardKey.Copy + onActivated: textArea.copy() + } + + // + // Right-click context menu + // + Menu { + id: contextMenu + + MenuItem { + text: qsTr("Copy") + opacity: enabled ? 1 : 0.5 + onTriggered: textArea.copy() + enabled: textArea.selectedText + } + + MenuItem { + text: qsTr("Clear") + opacity: enabled ? 1 : 0.5 + enabled: textArea.length > 0 + onTriggered: Cpp_IO_Console.clear() + } + + MenuItem { + opacity: enabled ? 1 : 0.5 + text: qsTr("Save as") + "..." + onTriggered: Cpp_IO_Console.save() + enabled: Cpp_IO_Console.saveAvailable + } + } + // // Controls // @@ -111,18 +154,54 @@ Control { TextEdit { id: textArea + focus: true readOnly: true font.pixelSize: 12 width: flick.width height: flick.height + selectByMouse: true + selectByKeyboard: true color: root.consoleColor + persistentSelection: true wrapMode: TextEdit.NoWrap font.family: app.monoFont + selectionColor: palette.highlight + selectedTextColor: palette.highlightedText onCursorRectangleChanged: flick.ensureVisible(cursorRectangle) onTextChanged: { if (Cpp_IO_Console.autoscroll) textArea.cursorPosition = textArea.length } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.RightButton + + onClicked: { + root.selectStart = textArea.selectionStart; + root.selectEnd = textArea.selectionEnd; + root.curPos = textArea.cursorPosition; + contextMenu.x = mouse.x; + contextMenu.y = mouse.y; + contextMenu.open(); + textArea.cursorPosition = root.curPos; + textArea.select(root.selectStart, root.selectEnd); + } + + onPressAndHold: { + if (mouse.source === Qt.MouseEventNotSynthesized) { + root.selectStart = textArea.selectionStart; + root.selectEnd = textArea.selectionEnd; + root.curPos = textArea.cursorPosition; + contextMenu.x = mouse.x; + contextMenu.y = mouse.y; + contextMenu.open(); + textArea.cursorPosition = root.curPos; + textArea.select(root.selectStart, root.selectEnd); + } + } + } } } } @@ -246,6 +325,18 @@ Control { Button { height: 24 + Layout.maximumWidth: 32 + icon.color: palette.text + opacity: enabled ? 1 : 0.5 + onClicked: Cpp_IO_Console.save() + icon.source: "qrc:/icons/save.svg" + enabled: Cpp_IO_Console.saveAvailable + Behavior on opacity {NumberAnimation{}} + } + + Button { + height: 24 + Layout.maximumWidth: 32 icon.color: palette.text opacity: enabled ? 1 : 0.5 enabled: textArea.length > 0 diff --git a/assets/qml/Windows/Setup.qml b/assets/qml/Windows/Setup.qml index 33f911f6..215ee03d 100644 --- a/assets/qml/Windows/Setup.qml +++ b/assets/qml/Windows/Setup.qml @@ -186,7 +186,7 @@ Control { // Label { opacity: enabled ? 1 : 0.5 - enabled: !Cpp_IO_Serial.connected + enabled: !Cpp_IO_Manager.connected Behavior on opacity {NumberAnimation{}} text: qsTr("COM Port") + ":" } ComboBox { @@ -200,7 +200,7 @@ Control { } opacity: enabled ? 1 : 0.5 - enabled: !Cpp_IO_Serial.connected + enabled: !Cpp_IO_Manager.connected Behavior on opacity {NumberAnimation{}} } @@ -209,7 +209,6 @@ Control { // Label { opacity: enabled ? 1 : 0.5 - enabled: !Cpp_IO_Serial.connected Behavior on opacity {NumberAnimation{}} text: qsTr("Baud Rate") + ":" } ComboBox { diff --git a/assets/translations/de.qm b/assets/translations/de.qm index e8c0a270..54bd6dfe 100644 Binary files a/assets/translations/de.qm and b/assets/translations/de.qm differ diff --git a/assets/translations/de.ts b/assets/translations/de.ts index 18aff3e0..10c2c8a1 100644 --- a/assets/translations/de.ts +++ b/assets/translations/de.ts @@ -155,6 +155,18 @@ Show timestamp Zeitstempel anzeigen + + Copy + Kopieren + + + Clear + Löschen + + + Save as + Speichern als + CsvPlayer @@ -459,6 +471,18 @@ Hexadecimal Hexadezimal + + Export console data + Konsolendaten exportieren + + + Text files + Textdateien + + + File save error + Fehler beim Speichern der Datei + IO::DataSources::Serial diff --git a/assets/translations/en.qm b/assets/translations/en.qm index 8f6f9ea0..555f764d 100644 Binary files a/assets/translations/en.qm and b/assets/translations/en.qm differ diff --git a/assets/translations/en.ts b/assets/translations/en.ts index 33eee682..40a8fa40 100644 --- a/assets/translations/en.ts +++ b/assets/translations/en.ts @@ -151,6 +151,18 @@ Show timestamp + + Copy + + + + Clear + + + + Save as + + CsvPlayer @@ -396,6 +408,18 @@ Hexadecimal + + Export console data + + + + Text files + + + + File save error + + IO::DataSources::Serial diff --git a/assets/translations/es.qm b/assets/translations/es.qm index d1077f49..8049ddd1 100644 Binary files a/assets/translations/es.qm and b/assets/translations/es.qm differ diff --git a/assets/translations/es.ts b/assets/translations/es.ts index 763bf6c2..fec08ed3 100644 --- a/assets/translations/es.ts +++ b/assets/translations/es.ts @@ -159,6 +159,18 @@ Show timestamp Marca de tiempo + + Copy + Copiar + + + Clear + Limpiar + + + Save as + Guardar como + CsvPlayer @@ -491,6 +503,18 @@ Hexadecimal Hexadecimal + + Export console data + Exportar datos de la consola + + + Text files + Archivos de texto + + + File save error + Error al intentar guardar el archivo + IO::DataSources::Serial diff --git a/assets/translations/zh.qm b/assets/translations/zh.qm index 519c3b89..819d1378 100644 Binary files a/assets/translations/zh.qm and b/assets/translations/zh.qm differ diff --git a/assets/translations/zh.ts b/assets/translations/zh.ts index a8f0bc76..0e5bbbb4 100644 --- a/assets/translations/zh.ts +++ b/assets/translations/zh.ts @@ -155,6 +155,18 @@ Show timestamp 显示时间戳 + + Copy + 复制 + + + Clear + 删除 + + + Save as + 另存为 + CsvPlayer @@ -487,6 +499,18 @@ Hexadecimal 十六进制 + + Export console data + 导出控制台数据 + + + Text files + 文字档 + + + File save error + 文件保存错误 + IO::DataSources::Serial diff --git a/src/IO/Console.cpp b/src/IO/Console.cpp index 1b8e4c4e..c1a6477c 100644 --- a/src/IO/Console.cpp +++ b/src/IO/Console.cpp @@ -23,13 +23,21 @@ #include "Console.h" #include "Manager.h" +#include #include #include +#include +#include #include using namespace IO; static Console *INSTANCE = nullptr; +/** + * Set buffer size to 15 MB + */ +static const int MAX_BUFFER_SIZE = 1024 * 1024 * 15; + /** * Constructor function */ @@ -45,6 +53,7 @@ Console::Console() , m_cursor(nullptr) , m_document(nullptr) { + clear(); auto m = Manager::getInstance(); connect(m, &Manager::dataReceived, this, &Console::onDataReceived); LOG_INFO() << "Class initialized"; @@ -79,6 +88,14 @@ bool Console::autoscroll() const return m_autoscroll; } +/** + * Returns @c true if data buffer contains information that the user can export. + */ +bool Console::saveAvailable() const +{ + return m_dataBuffer.size() > 0; +} + /** * Returns @c true if a timestamp should be shown before each displayed data block. */ @@ -190,15 +207,49 @@ QStringList Console::displayModes() const void Console::copy() { } -void Console::save() { } +/** + * Allows the user to export the information displayed on the console + */ +void Console::save() +{ + // No data buffer received, abort + if (!saveAvailable()) + return; + + // Get file name + auto path + = QFileDialog::getSaveFileName(Q_NULLPTR, tr("Export console data"), + QDir::homePath(), tr("Text files") + " (*.txt)"); + + // Create file + if (!path.isEmpty()) + { + QFile file(path); + if (file.open(QFile::WriteOnly)) + { + file.write(m_dataBuffer); + file.close(); + + Misc::Utilities::revealFile(path); + } + + else + Misc::Utilities::showMessageBox(tr("File save error"), file.errorString()); + } +} /** * Deletes all the text displayed by the current QML text document */ void Console::clear() { + // Clear console display if (document()) document()->clear(); + + // Reserve 15 MB for data buffer + m_dataBuffer.clear(); + m_dataBuffer.reserve(MAX_BUFFER_SIZE); } /** @@ -383,10 +434,25 @@ void Console::setTextDocument(QQuickTextDocument *document) * Displays the given @a data in the console. @c QByteArray to ~@c QString conversion is * done by the @c dataToString() function, which displays incoming data either in UTF-8 * or in hexadecimal mode. + * + * The incoming data is also added to a buffer so that the user can save the file if + * needed. */ void Console::onDataReceived(const QByteArray &data) { + // Display data append(dataToString(data)); + + // Append data to buffer + m_dataBuffer.append(data); + if (m_dataBuffer.size() > MAX_BUFFER_SIZE) + { + m_dataBuffer.clear(); + m_dataBuffer.reserve(MAX_BUFFER_SIZE); + } + + // Notify UI + emit dataReceived(); } /** diff --git a/src/IO/Console.h b/src/IO/Console.h index 8e690ef6..7802d8f4 100644 --- a/src/IO/Console.h +++ b/src/IO/Console.h @@ -48,6 +48,9 @@ class Console : public QObject READ showTimestamp WRITE setShowTimestamp NOTIFY showTimestampChanged) + Q_PROPERTY(bool saveAvailable + READ saveAvailable + NOTIFY dataReceived) Q_PROPERTY(IO::Console::DataMode dataMode READ dataMode WRITE setDataMode @@ -67,6 +70,7 @@ class Console : public QObject signals: void echoChanged(); + void dataReceived(); void dataModeChanged(); void autoscrollChanged(); void lineEndingChanged(); @@ -103,6 +107,7 @@ public: bool echo() const; bool autoscroll() const; + bool saveAvailable() const; bool showTimestamp() const; DataMode dataMode() const; @@ -145,6 +150,7 @@ private: private: DataMode m_dataMode; + QByteArray m_dataBuffer; LineEnding m_lineEnding; DisplayMode m_displayMode; diff --git a/src/IO/Manager.cpp b/src/IO/Manager.cpp index ee483b19..958bd809 100644 --- a/src/IO/Manager.cpp +++ b/src/IO/Manager.cpp @@ -46,6 +46,7 @@ Manager::Manager() , m_finishSequence("*/") { setWatchdogInterval(15); + setMaxBufferSize(1024 * 1024); LOG_INFO() << "Class initialized"; } @@ -310,6 +311,7 @@ void Manager::disconnectDevice() m_device = nullptr; m_receivedBytes = 0; m_dataBuffer.clear(); + m_dataBuffer.reserve(maxBufferSize()); // Update UI emit deviceChanged(); @@ -351,6 +353,8 @@ void Manager::setMaxBufferSize(const int maxBufferSize) { m_maxBuzzerSize = maxBufferSize; emit maxBufferSizeChanged(); + + m_dataBuffer.reserve(maxBufferSize); } /**