diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bba3617..76dba6c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -96,6 +96,7 @@ set(HEADER_FILES QClearLineEdit.h QIndexTreeWidget.h QStringPair.h + QHistoryLineEdit.h ) set(SOURCE_FILES @@ -174,6 +175,7 @@ set(SOURCE_FILES QZoomChart.cpp QClearLineEdit.cpp QIndexTreeWidget.cpp + QHistoryLineEdit.cpp ) qt5_add_resources(SOURCE_FILES resource.qrc) diff --git a/src/QHistoryLineEdit.cpp b/src/QHistoryLineEdit.cpp new file mode 100644 index 0000000..ebfabaf --- /dev/null +++ b/src/QHistoryLineEdit.cpp @@ -0,0 +1,279 @@ +/** + * \file + * + * \author Mattia Basaglia + * + * \copyright Copyright (C) 2012-2020 Mattia Basaglia + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + */ +#include "QHistoryLineEdit.h" +#include +#include +#include +#include +#include +#include + +QHistoryLineEdit::QHistoryLineEdit(QWidget *parent) : QLineEdit(parent), _currentLine(0), _completer(0), _completionMinchars(1), _completionMax(0) { + + QObject::connect(this, &QLineEdit::returnPressed, this, &QHistoryLineEdit::execute); +} + +/** + * \brief Number of available lines + */ +int QHistoryLineEdit::lineCount() const { + + return _lines.size(); +} + +/** + * \brief Overwrite the line history + */ +void QHistoryLineEdit::setHistory(const QStringList& history) { + + _lines = history; + _currentLine = _lines.size(); +} + +/** + * \brief Stored history + */ +QStringList QHistoryLineEdit::history() const { + + return _lines; +} + +/** + * \brief Sets the completer used on a per-word completion + * + * Unlike setCompleter(), this suggests completion at every entered word + * + * If \c completer is null it will remove the current completer + */ +void QHistoryLineEdit::setWordCompleter(QCompleter* comp) { + + if ( _completer ) { + + QObject::disconnect(_completer, 0, this, 0); + + _completer->setWidget(0); + } + + _completer = comp; + + if ( comp ) { + + /// \todo should set these only when on focus + QObject::connect(_completer, QOverload::of(&QCompleter::activated), this, &QHistoryLineEdit::autoComplete); + QObject::connect(_completer, QOverload::of(&QCompleter::highlighted), this, &QHistoryLineEdit::autoComplete); + + _completer->setWidget(this); + } +} + +/** + * \brief Sets a prefix that is ignored by the word completer + */ +void QHistoryLineEdit::setWordCompleterPrefix(const QString& prefix) { + _completionPrefix = prefix; +} + +/** + * \brief Sets the minimum number of characters required to display the word completer + */ +void QHistoryLineEdit::setWordCompleterMinChars(int minChars) { + _completionMinchars = minChars; +} + +/** + * \brief Sets the maximum number of suggestions that the completer should show. + * + * If more than this many suggestions are found the completer isn't shown + */ +void QHistoryLineEdit::setWordCompleterMaxSuggestions(int max) { + _completionMax = max; +} + +/** + * \brief Executes the current line + */ +void QHistoryLineEdit::execute() { + + if ( _lines.empty() || _lines.back() != text() ) { + _lines << text(); + } + + _currentLine = _lines.size(); + + clear(); + + emit lineExecuted(_lines.back()); +} + +void QHistoryLineEdit::keyPressEvent(QKeyEvent * ev) { + + if ( ev->key() == Qt::Key_Up ) { + previousLine(); + return; + + } else if ( ev->key() == Qt::Key_Down ) { + nextLine(); + return; + + } else if (_completer && _completer->popup() && _completer->popup()->isVisible()) { + switch (ev->key()) { + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_F4: + case Qt::Key_Select: + _completer->popup()->hide(); + return; + } + } + + QLineEdit::keyPressEvent(ev); + + if (_completer) { + + QString current = current_word(); + _completer->setCompletionPrefix(current); + + if ( current.size() < _completionMinchars || _completer->completionCount() == 0 || (_completionMax > 0 && _completer->completionCount() > _completionMax) ) { + + _completer->popup()->hide(); + + } else { + // Get the selection status + int sel = selectionStart(); + int sellength = selectedText().size(); + + // Get the current cursor position + int c = cursorPosition(); + + // Get the start of the current word + setCursorPosition(_wordStart()); + + // Get the cursor rectangle at the beginning of the current word + QRect rect = cursorRect(); + + // Restore cursor position (clears the selection) + setCursorPosition(c); + + // If we had a selection + if ( sel != -1 ) { + // If the selection started at the cursor, + // it needs to start at the far end and go back + // (otherwise it moves the cursor at the end) + if ( sel == c ) + setSelection(sel+sellength, -sellength); + else + setSelection(sel, sellength); + } + + // Set the rectangle to the appropriate width + rect.setWidth( _completer->popup()->sizeHintForColumn(0) + _completer->popup()->verticalScrollBar()->sizeHint().width()); + + // Display the _completer under the rectangle + _completer->complete(rect); + } + } +} + +void QHistoryLineEdit::wheelEvent(QWheelEvent *ev) { + + if ( ev->angleDelta().y() > 0 ) { + previousLine(); + }else{ + nextLine(); + } +} + +void QHistoryLineEdit::previousLine() { + + if ( _lines.empty() ) { + return; + } + + if ( !text().isEmpty() && ( _currentLine >= _lines.size() || text() != _lines[_currentLine] ) ) { + _unfinished = text(); + } + + if ( _currentLine > 0 ) { + _currentLine--; + } + + setText(_lines[_currentLine]); +} + + +void QHistoryLineEdit::nextLine() { + + if ( _lines.empty() ) { + return; + } + + _currentLine++; + + if ( _currentLine >= _lines.size() ) { + + setText(_unfinished); + _unfinished = ""; + _currentLine = _lines.size(); + + } else { + setText(_lines[_currentLine]); + } +} + +/** + * \brief Current word being edited (used to fire the completer) + */ +QString QHistoryLineEdit::current_word() const { + + int completion_index = _wordStart(); + + return text().mid(completion_index, cursorPosition() - completion_index); +} + +/** + * \brief Autocompletes the current word + */ +void QHistoryLineEdit::autoComplete(const QString& completion) { + + int completion_index = _wordStart(); + + setText(text().replace( completion_index, cursorPosition() - completion_index, completion)); + + setCursorPosition(completion_index+completion.size()); +} + +/** + * \brief Returns the index of the character starting the currently edited word + */ +int QHistoryLineEdit::_wordStart() const { + + // lastIndexOf returns the index of the last space or -1 if there are no spaces + // so that + 1 returns the index of the character starting the word or 0 + int after_space = text().leftRef(cursorPosition()).lastIndexOf(' ') + 1; + + if ( text().rightRef(text().size()-after_space).startsWith(_completionPrefix) ) { + after_space += _completionPrefix.size(); + } + + return after_space; +} + diff --git a/src/QHistoryLineEdit.h b/src/QHistoryLineEdit.h new file mode 100644 index 0000000..29bacb9 --- /dev/null +++ b/src/QHistoryLineEdit.h @@ -0,0 +1,75 @@ +/** + * \file + * + * \author Mattia Basaglia + * + * \copyright Copyright (C) 2012-2020 Mattia Basaglia + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + */ +#pragma once + +#include + +/** + * \brief Line edit providing a history of entered text + */ +class QHistoryLineEdit : public QLineEdit { + + Q_OBJECT + + public: + explicit QHistoryLineEdit(QWidget *parent = 0); + + int lineCount () const; + + void setHistory (const QStringList& history); + QStringList history() const; + + void setWordCompleter (QCompleter* completer); + void setWordCompleterPrefix (const QString& prefix); + void setWordCompleterMinChars (int minChars); + void setWordCompleterMaxSuggestions (int max); + + public slots: + void execute (); + + signals: + void lineExecuted (QString); + + protected: + void keyPressEvent (QKeyEvent *) Q_DECL_OVERRIDE; + void wheelEvent (QWheelEvent *) Q_DECL_OVERRIDE; + + void previousLine (); + void nextLine (); + + QString current_word () const; + + private slots: + void autoComplete (const QString& completion); + + private: + int _wordStart () const; + + int _currentLine; + QStringList _lines; + QString _unfinished; + QCompleter* _completer; + QString _completionPrefix; + int _completionMinchars; + int _completionMax; +}; + diff --git a/src/SeerStructVisualizerWidget.cpp b/src/SeerStructVisualizerWidget.cpp index 84fe3f3..ffdf294 100644 --- a/src/SeerStructVisualizerWidget.cpp +++ b/src/SeerStructVisualizerWidget.cpp @@ -31,7 +31,7 @@ SeerStructVisualizerWidget::SeerStructVisualizerWidget (QWidget* parent) : QWidg // Connect things. QObject::connect(refreshToolButton, &QToolButton::clicked, this, &SeerStructVisualizerWidget::handleRefreshButton); - QObject::connect(variableNameLineEdit, &QLineEdit::returnPressed, this, &SeerStructVisualizerWidget::handleVariableNameLineEdit); + QObject::connect(variableNameLineEdit, &QHistoryLineEdit::lineExecuted, this, &SeerStructVisualizerWidget::handleVariableNameLineEdit); QObject::connect(variableTreeWidget, &QTreeWidget::itemEntered, this, &SeerStructVisualizerWidget::handleItemEntered); QObject::connect(variableTreeWidget, &QTreeWidget::itemExpanded, this, &SeerStructVisualizerWidget::handleItemExpanded); QObject::connect(variableTreeWidget, &QTreeWidget::itemCollapsed, this, &SeerStructVisualizerWidget::handleItemExpanded); @@ -227,9 +227,9 @@ void SeerStructVisualizerWidget::handleRefreshButton () { emit evaluateVariableExpression(_variableId, variableNameLineEdit->text()); } -void SeerStructVisualizerWidget::handleVariableNameLineEdit () { +void SeerStructVisualizerWidget::handleVariableNameLineEdit (const QString& text) { - setVariableName (variableNameLineEdit->text()); + setVariableName (text); } void SeerStructVisualizerWidget::writeSettings() { diff --git a/src/SeerStructVisualizerWidget.h b/src/SeerStructVisualizerWidget.h index 213230b..bfcfd65 100644 --- a/src/SeerStructVisualizerWidget.h +++ b/src/SeerStructVisualizerWidget.h @@ -22,7 +22,7 @@ class SeerStructVisualizerWidget : public QWidget, protected Ui::SeerStructVisua protected slots: void handleRefreshButton (); - void handleVariableNameLineEdit (); + void handleVariableNameLineEdit (const QString& text); void handleItemEntered (QTreeWidgetItem* item, int column); void handleItemExpanded (QTreeWidgetItem* item); diff --git a/src/SeerStructVisualizerWidget.ui b/src/SeerStructVisualizerWidget.ui index 7a9e63c..8d96588 100644 --- a/src/SeerStructVisualizerWidget.ui +++ b/src/SeerStructVisualizerWidget.ui @@ -15,7 +15,7 @@ - + Enter a variable to visualize. @@ -77,6 +77,13 @@ + + + QHistoryLineEdit + QLineEdit +
QHistoryLineEdit.h
+
+