diff --git a/src/ArdorQuery.pro b/src/ArdorQuery.pro index 3b30be3..3b57dbd 100644 --- a/src/ArdorQuery.pro +++ b/src/ArdorQuery.pro @@ -36,6 +36,7 @@ SOURCES += \ Models/shortcutsection.cpp \ QuickControls/backendimage.cpp \ ViewModels/backendviewmodel.cpp \ + ViewModels/globalmouseviewmodel.cpp \ ViewModels/httpperformerviewmodel.cpp \ ViewModels/httprequestresultviewmodel.cpp \ ViewModels/httprequestviewmodel.cpp \ @@ -88,6 +89,7 @@ HEADERS += \ Models/shortcutsection.h \ QuickControls/backendimage.h \ ViewModels/backendviewmodel.h \ + ViewModels/globalmouseviewmodel.h \ ViewModels/httpperformerviewmodel.h \ ViewModels/httprequestresultviewmodel.h \ ViewModels/httprequestviewmodel.h \ diff --git a/src/ListModels/responsebodylistmodel.cpp b/src/ListModels/responsebodylistmodel.cpp index 0745034..5ea96b1 100644 --- a/src/ListModels/responsebodylistmodel.cpp +++ b/src/ListModels/responsebodylistmodel.cpp @@ -20,7 +20,6 @@ ResponseBodyListModel::ResponseBodyListModel(QObject *parent) : QAbstractListModel{parent} { - } int ResponseBodyListModel::rowCount(const QModelIndex &parent) const @@ -39,6 +38,18 @@ QVariant ResponseBodyListModel::data(const QModelIndex &index, int role) const switch (role) { case CurrentLineRole: { + if (currentIndex == m_startSelectLine && currentIndex != m_endSelectLine) { + //TODO: this line is start for more lines + auto highlightedLine = selectAsStartLine(line); + return QVariant(highlightedLine); + } + if (currentIndex != m_startSelectLine && currentIndex == m_endSelectLine) { + //TODO: this line is end for more lines + return QVariant(selectAsEndLine(line)); + } + if (currentIndex == m_startSelectLine && currentIndex == m_endSelectLine) { + return QVariant(selectAsOneLine(line)); + } return QVariant(line); } case IndexRole: { @@ -206,6 +217,25 @@ void ResponseBodyListModel::clear() noexcept m_originalBody.clear(); } +QString ResponseBodyListModel::selectAsStartLine(const QString &line) const +{ + auto start = m_startSelectLine > m_endSelectLine ? 0 : m_startSelectPosition; + auto end = m_startSelectLine > m_endSelectLine ? m_startSelectPosition : 0; + auto formatted = QString(line); + + return formatted; +} + +QString ResponseBodyListModel::selectAsEndLine(const QString &line) const +{ + +} + +QString ResponseBodyListModel::selectAsOneLine(const QString &line) const +{ + +} + void ResponseBodyListModel::searchText(const QString &filter) noexcept { if (m_previousFilter == filter) return; @@ -251,6 +281,56 @@ void ResponseBodyListModel::searchText(const QString &filter) noexcept emit countFindedLinesTextChanged(); } +void ResponseBodyListModel::selectLine(int elementIndex, int positionX) noexcept +{ + if (m_startSelectLine == -1 && m_endSelectLine == -1) { + m_startSelectLine = elementIndex; + m_endSelectLine = elementIndex; + m_startSelectPosition = positionX; + m_endSelectPosition = positionX; + emit startSelectLineChanged(); + emit startSelectPositionChanged(); + emit endSelectLineChanged(); + emit endSelectPositionChanged(); + return; + } + + + m_endSelectLine = elementIndex; + m_endSelectPosition = positionX; + emit endSelectLineChanged(); + emit endSelectPositionChanged(); + + if (m_startSelectLine != m_endSelectLine || m_startSelectPosition != m_endSelectPosition) { + auto startLine = m_startSelectLine; + auto endLine = m_endSelectLine; + if (m_startSelectLine > m_endSelectLine) { + startLine = m_endSelectLine; + endLine = m_startSelectLine; + } + emit dataChanged(index(startLine,0), index(endLine,0)); + } + + qDebug() << m_startSelectLine << " " << m_endSelectLine << " " << m_startSelectPosition << " " << m_endSelectPosition; +} + +void ResponseBodyListModel::resetSelected() noexcept +{ + beginResetModel(); + + m_startSelectLine = -1; + m_endSelectLine = -1; + m_startSelectPosition = -1; + m_endSelectPosition = -1; + + emit startSelectLineChanged(); + emit endSelectLineChanged(); + emit startSelectPositionChanged(); + emit endSelectPositionChanged(); + + endResetModel(); +} + QString & ResponseBodyListModel::cleanLineFromTags(QString &line) noexcept { return line.replace("", "").replace("<", "<").replace(">", "<").replace(m_fontTagStartRegExp, ""); diff --git a/src/ListModels/responsebodylistmodel.h b/src/ListModels/responsebodylistmodel.h index 31615ea..fe68b50 100644 --- a/src/ListModels/responsebodylistmodel.h +++ b/src/ListModels/responsebodylistmodel.h @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include "../Formatters/formatterfactory.h" #include "../globalconstants.h" @@ -33,6 +35,10 @@ class ResponseBodyListModel : public QAbstractListModel Q_PROPERTY(int bodyImageHeight READ bodyImageHeight NOTIFY bodyImageHeightChanged) Q_PROPERTY(int countFindedLines READ countFindedLines NOTIFY countFindedLinesChanged) Q_PROPERTY(QString countFindedLinesText READ countFindedLinesText NOTIFY countFindedLinesTextChanged) + Q_PROPERTY(int startSelectLine READ startSelectLine NOTIFY startSelectLineChanged) + Q_PROPERTY(int endSelectLine READ endSelectLine NOTIFY endSelectLineChanged) + Q_PROPERTY(int startSelectPosition READ startSelectPosition NOTIFY startSelectPositionChanged) + Q_PROPERTY(int endSelectPosition READ endSelectPosition NOTIFY endSelectPositionChanged) private: QStringList m_lines { QStringList() }; @@ -46,6 +52,12 @@ class ResponseBodyListModel : public QAbstractListModel QRegularExpression m_fontTagStartRegExp { R"a()a" }; bool m_notFounded { false }; QString m_previousFilter { "" }; + int m_startSelectLine { -1 }; + int m_endSelectLine { -1 }; + int m_startSelectPosition { -1 }; + int m_endSelectPosition { -1 }; + QFont m_font { QFont() }; + QFontMetrics m_fontMetrics { QFontMetrics(m_font) }; enum ResponseBodyRoles { CurrentLineRole = Qt::UserRole + 1, @@ -76,7 +88,18 @@ class ResponseBodyListModel : public QAbstractListModel int getCurrentFindedLine() noexcept; void clear() noexcept; + int startSelectLine() const noexcept { return m_startSelectLine; } + int endSelectLine() const noexcept { return m_endSelectLine; } + int startSelectPosition() const noexcept { return m_startSelectPosition; } + int endSelectPosition() const noexcept { return m_endSelectPosition; } + + QString selectAsStartLine(const QString& line) const; + QString selectAsEndLine(const QString& line) const; + QString selectAsOneLine(const QString& line) const; + Q_INVOKABLE void searchText(const QString& filter) noexcept; + Q_INVOKABLE void selectLine(int elementIndex, int positionX) noexcept; + Q_INVOKABLE void resetSelected() noexcept; private: QString& cleanLineFromTags(QString& line) noexcept; @@ -88,6 +111,10 @@ class ResponseBodyListModel : public QAbstractListModel void bodyImageHeightChanged(); void countFindedLinesChanged(); void countFindedLinesTextChanged(); + void startSelectLineChanged(); + void endSelectLineChanged(); + void startSelectPositionChanged(); + void endSelectPositionChanged(); }; diff --git a/src/ViewModels/backendviewmodel.cpp b/src/ViewModels/backendviewmodel.cpp index 8710e5a..9fbb815 100644 --- a/src/ViewModels/backendviewmodel.cpp +++ b/src/ViewModels/backendviewmodel.cpp @@ -409,6 +409,67 @@ void BackendViewModel::importFromOpenApi(int index) noexcept m_requests->selectItem(createdIndex); } +void BackendViewModel::setFontFamily(const QString &family) noexcept +{ + m_fontFamily = family; + m_font = QFont(family, m_fontPointSize); + m_fontMetrics = QFontMetrics(m_font); + m_fontHeight = m_fontMetrics.boundingRect(QString('A')).height(); + + auto characters = QString("AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789 !@#$%^&*()_+-={}[];:\\|/?><'\""); + + foreach (auto character, characters) { + m_characterWidths[character] = m_fontMetrics.boundingRect(QString(character == ' ' ? '<' : character)).width(); + } + + emit fontFamilyChanged(); +} + +int BackendViewModel::getPositionInText(const QString &line, int positionX, int positionY, int width, bool formatted) noexcept +{ + if (formatted) { + auto replacedLine = QString(line).replace(" ", " ").replace("<", "[").replace(""", "\"").replace("&qt;", "]"); + auto tagStarted = false; + auto characterWidth = 4; + auto characterPosition = 0; + auto characterLine = 0; + auto lineInside = positionY > m_fontHeight ? positionY / m_fontHeight : 0; + foreach (auto character, replacedLine) { + if (characterWidth >= width) characterLine += 1; + if (character == '<' && !tagStarted) { + tagStarted = true; + continue; + } + + if (tagStarted) { + if (character == '>') tagStarted = false; + continue; + } + + if (lineInside <= characterLine) { + if (!m_characterWidths.contains(character)) m_characterWidths[character] = m_fontMetrics.boundingRect(QString(character)).width(); + auto oldCharacterWidth = characterWidth; + characterWidth += m_characterWidths[character]; + + if (oldCharacterWidth < positionX && positionX <= characterWidth) return characterPosition; + } + if (characterLine > lineInside) break; + + characterPosition++; + } + } else { + auto characterWidth = 0; + auto characterPosition = 0; + foreach (auto character, line) { + characterWidth += m_characterWidths[character]; + if (characterWidth <= positionX) return characterPosition; + characterPosition++; + } + } + + return 0; +} + void BackendViewModel::deleteCurrentRequest() noexcept { if (m_requests->singleRequest()) addNewRequest(); diff --git a/src/ViewModels/backendviewmodel.h b/src/ViewModels/backendviewmodel.h index f23a89d..9f3331b 100644 --- a/src/ViewModels/backendviewmodel.h +++ b/src/ViewModels/backendviewmodel.h @@ -17,6 +17,8 @@ #define BACKENDVIEWMODEL_H #include +#include +#include #include "../ViewModels/httpperformerviewmodel.h" #include "../ViewModels/textadvisorviewmodel.h" #include "../ListModels/httprequestslistmodel.h" @@ -43,6 +45,8 @@ class BackendViewModel : public QObject Q_PROPERTY(bool openedCommandPalette READ openedCommandPalette NOTIFY openedCommandPaletteChanged) Q_PROPERTY(OpenApiExporterViewModel* openApiExporter READ openApiExporter NOTIFY openApiExporterChanged) Q_PROPERTY(GlobalVariablesListModel* globalVariables READ globalVariables NOTIFY globalVariablesChanged) + Q_PROPERTY(QString fontFamily READ fontFamily NOTIFY fontFamilyChanged) + Q_PROPERTY(int fontPointSize READ fontPointSize NOTIFY fontPointSizeChanged) private: HttpPerformerViewModel* m_requestPerformer { new HttpPerformerViewModel(this) }; @@ -58,6 +62,12 @@ class BackendViewModel : public QObject OpenApiExporterViewModel* m_openApiExporter { new OpenApiExporterViewModel(this) }; GlobalVariablesListModel* m_globalVariables { new GlobalVariablesListModel(this) }; bool m_openApiHelpVisible { false }; + QString m_fontFamily { "" }; + int m_fontPointSize { 9 }; + QFont m_font; + QFontMetrics m_fontMetrics { QFontMetrics(QFont()) }; + int m_fontHeight { 0 }; + QMap m_characterWidths { QMap() }; public: explicit BackendViewModel(QObject *parent = nullptr); @@ -83,6 +93,8 @@ class BackendViewModel : public QObject Q_INVOKABLE void generateImage(const QString& filePath) noexcept; Q_INVOKABLE void generateImageToClipboard() noexcept; Q_INVOKABLE void importFromOpenApi(int index) noexcept; + Q_INVOKABLE void setFontFamily(const QString& family) noexcept; + Q_INVOKABLE int getPositionInText(const QString& line, int positionX, int positionY, int width, bool formatted) noexcept; void deleteCurrentRequest() noexcept; @@ -91,6 +103,9 @@ class BackendViewModel : public QObject bool openApiHelpVisible() const noexcept { return m_openApiHelpVisible; } + QString fontFamily() const noexcept { return m_fontFamily; } + int fontPointSize() const noexcept { return m_fontPointSize; } + private: QString removeProtocol(const QString& filePath) noexcept; void fillAuthorizationSecurity(const QString& key, HttpRequestViewModel* request, const OpenApiRoutesOptions& options); @@ -115,6 +130,8 @@ class BackendViewModel : public QObject void openApiHelpVisibleChanged(); void globalVariablesChanged(); void needGlobalVariablesWindow(); + void fontFamilyChanged(); + void fontPointSizeChanged(); private slots: void errorNotification(const QString& message, const QString& title); diff --git a/src/ViewModels/globalmouseviewmodel.cpp b/src/ViewModels/globalmouseviewmodel.cpp new file mode 100644 index 0000000..04bfc3a --- /dev/null +++ b/src/ViewModels/globalmouseviewmodel.cpp @@ -0,0 +1,49 @@ +#include +#include +#include "globalmouseviewmodel.h" + +GlobalMouseViewModel::GlobalMouseViewModel(QObject *parent) + : QObject{parent} +{ + QGuiApplication::instance()->installEventFilter(this); +} + +bool GlobalMouseViewModel::eventFilter(QObject* watched, QEvent* event) +{ + QEvent::Type t = event->type(); + if (t == QEvent::MouseMove && m_moveTracking && event->spontaneous()) { + auto mouseEvent = static_cast(event); + auto position = mouseEvent->position(); + m_xCoordinate = position.x(); + m_yCoordinate = position.y(); + if (m_xCoordinate >= m_leftEdge && m_yCoordinate >= m_topEdge) { + emit mouseMoved(m_xCoordinate, m_yCoordinate); + } + } + + return QObject::eventFilter(watched, event); +} + +void GlobalMouseViewModel::setLeftEdge(int leftEdge) noexcept +{ + if (m_leftEdge == leftEdge) return; + + m_leftEdge = leftEdge; + emit leftEdgeChanged(); +} + +void GlobalMouseViewModel::setTopEdge(int topEdge) noexcept +{ + if (m_topEdge == topEdge) return; + + m_topEdge = topEdge; + emit topEdgeChanged(); +} + +void GlobalMouseViewModel::setMoveTracking(bool moveTracking) noexcept +{ + if (m_moveTracking == moveTracking) return; + + m_moveTracking = moveTracking; + emit moveTrackingChanged(); +} diff --git a/src/ViewModels/globalmouseviewmodel.h b/src/ViewModels/globalmouseviewmodel.h new file mode 100644 index 0000000..4e9f742 --- /dev/null +++ b/src/ViewModels/globalmouseviewmodel.h @@ -0,0 +1,49 @@ +#ifndef GLOBALMOUSEVIEWMODEL_H +#define GLOBALMOUSEVIEWMODEL_H + +#include + +class GlobalMouseViewModel : public QObject +{ + Q_OBJECT + Q_PROPERTY(int leftEdge READ leftEdge WRITE setLeftEdge NOTIFY leftEdgeChanged) + Q_PROPERTY(int topEdge READ topEdge WRITE setTopEdge NOTIFY topEdgeChanged) + Q_PROPERTY(bool moveTracking READ moveTracking WRITE setMoveTracking NOTIFY moveTrackingChanged) + Q_PROPERTY(int xCoordinate READ xCoordinate NOTIFY xCoordinateChanged) + Q_PROPERTY(int yCoordinate READ yCoordinate NOTIFY yCoordinateChanged) + +private: + int m_leftEdge { 0 }; + int m_topEdge { 0 }; + int m_xCoordinate { 0 }; + int m_yCoordinate { 0 }; + bool m_moveTracking { false }; + +public: + explicit GlobalMouseViewModel(QObject *parent = nullptr); + + bool eventFilter(QObject* watched, QEvent* event); + + int leftEdge() const noexcept { return m_leftEdge; } + void setLeftEdge(int leftEdge) noexcept; + + int topEdge() const noexcept { return m_topEdge; } + void setTopEdge(int topEdge) noexcept; + + bool moveTracking() const noexcept { return m_moveTracking; } + void setMoveTracking(bool moveTracking) noexcept; + + int xCoordinate() const noexcept { return m_xCoordinate; } + int yCoordinate() const noexcept { return m_yCoordinate; } + +signals: + void leftEdgeChanged(); + void topEdgeChanged(); + void mouseMoved(int x, int y); + void moveTrackingChanged(); + void xCoordinateChanged(); + void yCoordinateChanged(); + +}; + +#endif // GLOBALMOUSEVIEWMODEL_H diff --git a/src/Views/HttpResultViewer.qml b/src/Views/HttpResultViewer.qml index 7a762e3..79f2f2c 100644 --- a/src/Views/HttpResultViewer.qml +++ b/src/Views/HttpResultViewer.qml @@ -323,49 +323,97 @@ Item { Component { id: listComponent - ListView { - id: listStrings - clip: true + Item { anchors.fill: parent - flickDeceleration: 5000 - flickableDirection: Flickable.HorizontalAndVerticalFlick - boundsBehavior: ListView.StopAtBounds - model: viewModel.bodyModel - delegate: bodyLineComponent - ScrollBar.vertical: ScrollBar { - active: true - } - - Component { - id: bodyLineComponent - Item { - width: listStrings.width - height: line.height + ListView { + id: listStrings + clip: true + width: parent.width - vbar.width + height: parent.height + flickDeceleration: 5000 + flickableDirection: Flickable.HorizontalAndVerticalFlick + boundsBehavior: ListView.StopAtBounds + model: viewModel.bodyModel + delegate: bodyLineComponent + ScrollBar.vertical: vbar + + Component { + id: bodyLineComponent Rectangle { - color: "black" - opacity: .05 - anchors.fill: parent - visible: isFindIndex - } - Text { - id: line - leftPadding: 4 - rightPadding: 10 - textFormat: viewModel.isFormatting ? Text.RichText : Text.PlainText - text: currentLine - width: bodyContainer.width - wrapMode: Text.Wrap - font.pointSize: 9 + id: lineContainer + width: listStrings.width + height: line.height + + signal selectLine(int width, int height, int positionX, int positionY) + onSelectLine: function (width, height, positionX, positionY){ + const positionInLine = backend.getPositionInText(currentLine, positionX, positionY, width, viewModel.isFormatting); + viewModel.bodyModel.selectLine(currentIndex, positionInLine); + } + + Rectangle { + color: "black" + opacity: .05 + anchors.fill: parent + visible: isFindIndex + } + Text { + id: line + leftPadding: 4 + rightPadding: 10 + textFormat: viewModel.isFormatting ? Text.RichText : Text.PlainText + text: currentLine + width: listStrings.width + wrapMode: Text.WrapAnywhere + font.pointSize: backend.fontPointSize + font.family: backend.fontFamily + } } } } + ScrollBar { + id: vbar + hoverEnabled: true + active: true + orientation: Qt.Vertical + size: listStrings.height / listStrings.contentHeight + anchors.top: parent.top + anchors.right: parent.right + anchors.bottom: parent.bottom + } + MouseArea { - anchors.fill: parent - onPressed: { - console.log(mouseX, mouseY, listStrings.contentX, listStrings.contentY); + anchors.bottom: parent.bottom + anchors.left: parent.left + height: parent.height + width: parent.width - 14 + propagateComposedEvents: false + scrollGestureEnabled: false + onPressed: function (mouse) { + const point = root.mapFromItem(listStrings, 0, 0); + globalMouseViewModel.moveTracking = true; + globalMouseViewModel.leftEdge = listStrings.x; + globalMouseViewModel.topEdge = point.y; + viewModel.bodyModel.resetSelected(); + mouse.accepted = true; + } + onReleased: { + globalMouseViewModel.moveTracking = false; + } + } + + Connections { + id: connection + target: globalMouseViewModel + function onMouseMoved(x, y) { + const point = listStrings.mapFromItem(root, x, y); + const child = listStrings.contentItem.childAt(point.x, point.y - 34 + listStrings.contentY); + if (!child) return; + + const positionY = (point.y + listStrings.contentY) - 34 - child.y; + child.selectLine(child.width, child.height, point.x, positionY); } } } diff --git a/src/main.cpp b/src/main.cpp index 26baf1a..a18ba93 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,6 +30,7 @@ #include "ListModels/addressespalettelistmodel.h" #include "ListModels/globalvariableslistmodel.h" #include "ListModels/responsebodylistmodel.h" +#include "ViewModels/globalmouseviewmodel.h" #ifdef QT_DEBUG #include #include "Tests/jsonformatterunittests.h" @@ -102,6 +103,7 @@ void registerQmlTypes() { qmlRegisterType("ArdorQuery.Backend", 1, 0, "OpenApiRoutesListModel"); qmlRegisterType("ArdorQuery.Backend", 1, 0, "GlobalVariablesListModel"); qmlRegisterType("ArdorQuery.Backend", 1, 0, "ResponseBodyListModel"); + qmlRegisterType("ArdorQuery.Backend", 1, 0, "GlobalMouseViewModel"); } void adjustmentLocalStorage() { diff --git a/src/main.qml b/src/main.qml index a17e79f..568af0f 100644 --- a/src/main.qml +++ b/src/main.qml @@ -24,6 +24,13 @@ ApplicationWindow { footer: ApplicationFooter { } + Text { + id: emptyText + Component.onCompleted: { + backend.setFontFamily(emptyText.font.family); + } + } + Item { id: keysItem Keys.onPressed: (event) => { @@ -152,6 +159,10 @@ ApplicationWindow { } Item { + GlobalMouseViewModel { + id: globalMouseViewModel + } + BackendViewModel { id: backend requestExternal.httpRequest: backend.requests.selectedItem.requestModel