From 403a56931584df13b768bd524c0784545f1d2a3f Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Wed, 6 Oct 2021 23:37:20 -0500 Subject: [PATCH] [Spreadsheet] Refactor keyboard handling LineEdit no longer actually handles motion, it simply indicates which action was taken to cause it to lose focus (e.g. which key was pressed). It's up to the client code to determine what this means. This allows significant consolidation of keyboard-handling logic, and the implementation of more extensive keyboard navigation features. New keyboard shortcuts include a tab counter to implement auto-return, plus Ctrl->Arrow, End, Home, Ctrl-End, and Ctrl-Home, matching the behavior of OpenOffice, LibreOffice, etc. Block selection via keyboard has also been added by holding down the shift key during navigation with the arrow keys (this also works in combination with the Ctrl modifier for region navigation). --- src/Mod/Spreadsheet/App/PropertySheet.cpp | 8 + src/Mod/Spreadsheet/App/PropertySheet.h | 2 + src/Mod/Spreadsheet/App/Sheet.cpp | 5 + src/Mod/Spreadsheet/App/Sheet.h | 2 + src/Mod/Spreadsheet/Gui/LineEdit.cpp | 86 ++--- src/Mod/Spreadsheet/Gui/LineEdit.h | 14 +- src/Mod/Spreadsheet/Gui/SheetTableView.cpp | 326 +++++++++++++++--- src/Mod/Spreadsheet/Gui/SheetTableView.h | 5 + .../Spreadsheet/Gui/SpreadsheetDelegate.cpp | 24 +- src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.h | 4 +- src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp | 95 +++-- src/Mod/Spreadsheet/Gui/SpreadsheetView.h | 5 +- 12 files changed, 394 insertions(+), 182 deletions(-) diff --git a/src/Mod/Spreadsheet/App/PropertySheet.cpp b/src/Mod/Spreadsheet/App/PropertySheet.cpp index 8a6fa6542d..286b72890f 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.cpp +++ b/src/Mod/Spreadsheet/App/PropertySheet.cpp @@ -982,6 +982,14 @@ void PropertySheet::getSpans(CellAddress address, int & rows, int & cols) const } } +App::CellAddress Spreadsheet::PropertySheet::getAnchor(App::CellAddress address) const +{ + if (auto anchor = mergedCells.find(address); anchor != mergedCells.end()) + return anchor->second; + else + return address; +} + bool PropertySheet::isMergedCell(CellAddress address) const { return mergedCells.find(address) != mergedCells.end(); diff --git a/src/Mod/Spreadsheet/App/PropertySheet.h b/src/Mod/Spreadsheet/App/PropertySheet.h index 7e20cf6a7d..60694b85b5 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.h +++ b/src/Mod/Spreadsheet/App/PropertySheet.h @@ -148,6 +148,8 @@ public: void getSpans(App::CellAddress address, int &rows, int &cols) const; + App::CellAddress getAnchor(App::CellAddress address) const; + bool isMergedCell(App::CellAddress address) const; bool isHidden(App::CellAddress address) const; diff --git a/src/Mod/Spreadsheet/App/Sheet.cpp b/src/Mod/Spreadsheet/App/Sheet.cpp index 3442e5a9b7..561e88c303 100644 --- a/src/Mod/Spreadsheet/App/Sheet.cpp +++ b/src/Mod/Spreadsheet/App/Sheet.cpp @@ -1063,6 +1063,11 @@ bool Sheet::isMergedCell(CellAddress address) const return cells.isMergedCell(address); } +App::CellAddress Spreadsheet::Sheet::getAnchor(App::CellAddress address) const +{ + return cells.getAnchor(address); +} + /** * @brief Set column with of column \a col to \a width- * @param col Index of column. diff --git a/src/Mod/Spreadsheet/App/Sheet.h b/src/Mod/Spreadsheet/App/Sheet.h index 7b60256885..18e898b849 100644 --- a/src/Mod/Spreadsheet/App/Sheet.h +++ b/src/Mod/Spreadsheet/App/Sheet.h @@ -110,6 +110,8 @@ public: bool isMergedCell(App::CellAddress address) const; + App::CellAddress getAnchor(App::CellAddress address) const; + void setColumnWidth(int col, int width); int getColumnWidth(int col) const; diff --git a/src/Mod/Spreadsheet/Gui/LineEdit.cpp b/src/Mod/Spreadsheet/Gui/LineEdit.cpp index 2e6a00a2c6..3116dcc6f7 100644 --- a/src/Mod/Spreadsheet/Gui/LineEdit.cpp +++ b/src/Mod/Spreadsheet/Gui/LineEdit.cpp @@ -26,68 +26,58 @@ # include #endif +#include + #include "LineEdit.h" using namespace SpreadsheetGui; LineEdit::LineEdit(QWidget *parent) : Gui::ExpressionLineEdit(parent, false, true) - , current() - , deltaCol(0) - , deltaRow(0) + , lastKeyPressed(0) + , lastModifiers(0) { + setFocusPolicy(Qt::FocusPolicy::ClickFocus); +} + +bool LineEdit::eventFilter(QObject* object, QEvent* event) +{ + if (event && event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Tab) { + // Special tab handling -- must be done via a QApplication event filter, otherwise the widget + // system will always grab the tab events + if (completerActive()) { + hideCompleter(); + event->accept(); + return true; // To make sure this tab press doesn't do anything else + } + else { + lastKeyPressed = keyEvent->key(); + lastModifiers = keyEvent->modifiers(); + } + } + } + return false; // We don't usually actually "handle" the tab event, we just keep track of it } bool LineEdit::event(QEvent *event) { - if (event && event->type() == QEvent::KeyPress) { + if (event && event->type() == QEvent::FocusIn) { + qApp->installEventFilter(this); + } + else if (event && event->type() == QEvent::FocusOut) { + qApp->removeEventFilter(this); + if (lastKeyPressed) + Q_EMIT finishedWithKey(lastKeyPressed, lastModifiers); + lastKeyPressed = 0; + } + else if (event && event->type() == QEvent::KeyPress && !completerActive()) { QKeyEvent * kevent = static_cast(event); - - if (kevent->key() == Qt::Key_Tab) { - if (kevent->modifiers() == 0) { - deltaCol = 1; - deltaRow = 0; - Q_EMIT returnPressed(); - return true; - } - } - else if (kevent->key() == Qt::Key_Backtab) { - if (kevent->modifiers() == Qt::ShiftModifier) { - deltaCol = -1; - deltaRow = 0; - Q_EMIT returnPressed(); - return true; - } - } - else if (kevent->key() == Qt::Key_Enter || kevent->key() == Qt::Key_Return) { - if (kevent->modifiers() == 0) { - deltaCol = 0; - deltaRow = 1; - Q_EMIT returnPressed(); - return true; - } - else if (kevent->modifiers() == Qt::ShiftModifier) { - deltaCol = 0; - deltaRow = -1; - Q_EMIT returnPressed(); - return true; - } - } + lastKeyPressed = kevent->key(); + lastModifiers = kevent->modifiers(); } return Gui::ExpressionLineEdit::event(event); } -void LineEdit::setIndex(QModelIndex _current) -{ - current = _current; -} - -QModelIndex LineEdit::next() const -{ - const QAbstractItemModel * m = current.model(); - - return m->index(qMin(qMax(0, current.row() + deltaRow), m->rowCount() - 1 ), - qMin(qMax(0, current.column() + deltaCol), m->columnCount() - 1 ) ); -} - #include "moc_LineEdit.cpp" diff --git a/src/Mod/Spreadsheet/Gui/LineEdit.h b/src/Mod/Spreadsheet/Gui/LineEdit.h index fa4e85185f..467e81cb7b 100644 --- a/src/Mod/Spreadsheet/Gui/LineEdit.h +++ b/src/Mod/Spreadsheet/Gui/LineEdit.h @@ -36,13 +36,17 @@ public: explicit LineEdit(QWidget *parent = 0); bool event(QEvent *event); - void setIndex(QModelIndex _current); - QModelIndex next() const; + +Q_SIGNALS: + void finishedWithKey(int key, Qt::KeyboardModifiers modifiers); private: - QModelIndex current; - int deltaCol; - int deltaRow; + bool eventFilter(QObject* object, QEvent* event); + + +private: + int lastKeyPressed; + Qt::KeyboardModifiers lastModifiers; }; } diff --git a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp index 11da87b0b5..86b532213e 100644 --- a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp +++ b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp @@ -101,6 +101,7 @@ static std::pair selectedMinMaxColumns(QModelIndexList list) SheetTableView::SheetTableView(QWidget *parent) : QTableView(parent) , sheet(0) + , tabCounter(0) { setHorizontalHeader(new SheetViewHeader(this,Qt::Horizontal)); setVerticalHeader(new SheetViewHeader(this,Qt::Vertical)); @@ -319,7 +320,7 @@ void SheetTableView::insertColumnsAfter() { assert(sheet != 0); const auto columns = selectionModel()->selectedColumns(); - const auto & [min, max] = selectedMinMaxColumns(columns); + const auto& [min, max] = selectedMinMaxColumns(columns); assert(max - min == columns.size() - 1); Q_UNUSED(min) @@ -345,7 +346,7 @@ void SheetTableView::removeColumns() Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Remove rows")); for (std::vector::const_iterator it = sortedColumns.begin(); it != sortedColumns.end(); ++it) Gui::cmdAppObjectArgs(sheet, "removeColumns('%s', %d)", - columnName(*it).c_str(), 1); + columnName(*it).c_str(), 1); Gui::Command::commitCommand(); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.recompute()"); } @@ -365,7 +366,7 @@ void SheetTableView::updateCellSpan(CellAddress address) setSpan(address.row(), address.col(), rows, cols); } -void SheetTableView::setSheet(Sheet * _sheet) +void SheetTableView::setSheet(Sheet* _sheet) { sheet = _sheet; cellSpanChangedConnection = sheet->cellSpanChanged.connect(bind(&SheetTableView::updateCellSpan, this, bp::_1)); @@ -399,57 +400,45 @@ void SheetTableView::setSheet(Sheet * _sheet) } -void SheetTableView::commitData ( QWidget * editor ) +void SheetTableView::commitData(QWidget* editor) { QTableView::commitData(editor); } -bool SheetTableView::edit ( const QModelIndex & index, EditTrigger trigger, QEvent * event ) +bool SheetTableView::edit(const QModelIndex& index, EditTrigger trigger, QEvent* event) { - if (trigger & (QAbstractItemView::DoubleClicked | QAbstractItemView::AnyKeyPressed | QAbstractItemView::EditKeyPressed) ) + if (trigger & (QAbstractItemView::DoubleClicked | QAbstractItemView::AnyKeyPressed | QAbstractItemView::EditKeyPressed)) currentEditIndex = index; return QTableView::edit(index, trigger, event); } -bool SheetTableView::event(QEvent *event) +bool SheetTableView::event(QEvent* event) { - /* Catch key presses for navigating the table; Enter/Return (+Shift), and Tab (+Shift) */ - if (event && event->type() == QEvent::KeyPress) { - QKeyEvent * kevent = static_cast(event); - - if (kevent->key() == Qt::Key_Tab) { - QModelIndex c = currentIndex(); - - if (kevent->modifiers() == 0) { - setCurrentIndex(model()->index(c.row(), qMin(c.column() + 1, model()->columnCount() -1))); - return true; - } - } - else if (kevent->key() == Qt::Key_Backtab) { - QModelIndex c = currentIndex(); - - if (kevent->modifiers() == Qt::ShiftModifier) { - setCurrentIndex(model()->index(c.row(), qMax(c.column() - 1, 0))); - return true; - } - } - else if (kevent->key() == Qt::Key_Enter || kevent->key() == Qt::Key_Return) { - QModelIndex c = currentIndex(); - - if (kevent->modifiers() == 0) { - setCurrentIndex(model()->index(qMin(c.row() + 1, model()->rowCount() - 1), c.column())); - return true; - } - else if (kevent->modifiers() == Qt::ShiftModifier) { - setCurrentIndex(model()->index(qMax(c.row() - 1, 0), c.column())); - return true; - } - } - else if (kevent->key() == Qt::Key_Delete) { + if (event && event->type() == QEvent::KeyPress && this->hasFocus()) { + // If this widget has focus, look for keyboard events that represent movement shortcuts + // and handle them. + QKeyEvent* kevent = static_cast(event); + switch (kevent->key()) { + case Qt::Key_Return: [[fallthrough]]; + case Qt::Key_Enter: [[fallthrough]]; + case Qt::Key_Home: [[fallthrough]]; + case Qt::Key_End: [[fallthrough]]; + case Qt::Key_Left: [[fallthrough]]; + case Qt::Key_Right: [[fallthrough]]; + case Qt::Key_Up: [[fallthrough]]; + case Qt::Key_Down: [[fallthrough]]; + case Qt::Key_Tab: [[fallthrough]]; + case Qt::Key_Backtab: + finishEditWithMove(kevent->key(), kevent->modifiers(), true); + return true; + // Also handle the delete key here: + case Qt::Key_Delete: deleteSelection(); return true; + default: + break; } - else if (kevent->matches(QKeySequence::Cut)) { + if (kevent->matches(QKeySequence::Cut)) { cutSelection(); return true; } @@ -468,18 +457,19 @@ bool SheetTableView::event(QEvent *event) kevent->modifiers() == Qt::ShiftModifier || kevent->modifiers() == Qt::KeypadModifier) { switch (kevent->key()) { - case Qt::Key_Return: - case Qt::Key_Enter: - case Qt::Key_Delete: - case Qt::Key_Home: - case Qt::Key_End: - case Qt::Key_Backspace: - case Qt::Key_Left: - case Qt::Key_Right: - case Qt::Key_Up: - case Qt::Key_Down: + case Qt::Key_Return: [[fallthrough]]; + case Qt::Key_Enter: [[fallthrough]]; + case Qt::Key_Delete: [[fallthrough]]; + case Qt::Key_Home: [[fallthrough]]; + case Qt::Key_End: [[fallthrough]]; + case Qt::Key_Backspace: [[fallthrough]]; + case Qt::Key_Left: [[fallthrough]]; + case Qt::Key_Right: [[fallthrough]]; + case Qt::Key_Up: [[fallthrough]]; + case Qt::Key_Down: [[fallthrough]]; case Qt::Key_Tab: kevent->accept(); + break; default: break; } @@ -614,13 +604,239 @@ void SheetTableView::pasteClipboard() } } -void SheetTableView::closeEditor(QWidget * editor, QAbstractItemDelegate::EndEditHint hint) +void SheetTableView::finishEditWithMove(int keyPressed, Qt::KeyboardModifiers modifiers, bool handleTabMotion) { - SpreadsheetGui::LineEdit * le = qobject_cast(editor); + // A utility lambda for finding the beginning and ending of data regions + auto scanForRegionBoundary = [this](int& r, int& c, int dr, int dc) { + auto startAddress = CellAddress(r, c); + auto startCell = sheet->getCell(startAddress); + bool startedAtEmptyCell = startCell ? !startCell->isUsed() : true; + const int maxRow = this->model()->rowCount() - 1; + const int maxCol = this->model()->columnCount() - 1; + while (c + dc >= 0 && r + dr >= 0 && c + dc <= maxCol && r + dr <= maxRow) { + r += dr; + c += dc; + auto cell = sheet->getCell(CellAddress(r, c)); + auto cellIsEmpty = cell ? !cell->isUsed() : true; + if (cellIsEmpty && !startedAtEmptyCell) { + // Don't stop at the empty cell, stop at the last non-empty cell + r -= dr; + c -= dc; + break; + } + else if (!cellIsEmpty && startedAtEmptyCell) { + break; + } + } + if (r == startAddress.row() && c == startAddress.col()) { + // Always move at least one cell: + r += dr; + c += dc; + } + r = std::max(0, std::min(r, maxRow)); + c = std::max(0, std::min(c, maxCol)); + }; - currentEditIndex = QModelIndex(); + int targetRow = currentIndex().row(); + int targetColumn = currentIndex().column(); + int colSpan; + int rowSpan; + sheet->getSpans(CellAddress(targetRow, targetColumn), rowSpan, colSpan); + switch (keyPressed) { + case Qt::Key_Return: + case Qt::Key_Enter: + if (modifiers == Qt::NoModifier) { + targetRow += rowSpan; + targetColumn -= tabCounter; + } + else if (modifiers == Qt::ShiftModifier) { + targetRow -= 1; + targetColumn -= tabCounter; + } + else { + // For an unrecognized modifier, just go down + targetRow += rowSpan; + } + tabCounter = 0; + break; + + case Qt::Key_Home: + // Home: row 1, same column + // Ctrl-Home: row 1, column 1 + targetRow = 0; + if (modifiers == Qt::ControlModifier) + targetColumn = 0; + tabCounter = 0; + break; + + case Qt::Key_End: + { + // End should take you to the last occupied cell in the current column + // Ctrl-End takes you to the last cell in the sheet + auto usedCells = sheet->getCells()->getUsedCells(); + for (const auto& cell : usedCells) { + if (modifiers == Qt::NoModifier) { + if (cell.col() == targetColumn) + targetRow = std::max(targetRow, cell.row()); + } + else if (modifiers == Qt::ControlModifier) { + targetRow = std::max(targetRow, cell.row()); + targetColumn = std::max(targetColumn, cell.col()); + } + } + tabCounter = 0; + break; + } + + case Qt::Key_Left: + if (targetColumn == 0) + break; // Nothing to do, we're already in the first column + if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) + targetColumn--; + else if (modifiers == Qt::ControlModifier || + modifiers == (Qt::ControlModifier | Qt::ShiftModifier)) + scanForRegionBoundary(targetRow, targetColumn, 0, -1); + else + targetColumn--; //Unrecognized modifier combination: default to just moving one cell + tabCounter = 0; + break; + case Qt::Key_Right: + if (targetColumn >= this->model()->columnCount() - 1) + break; // Nothing to do, we're already in the last column + if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) + targetColumn += colSpan; + else if (modifiers == Qt::ControlModifier || + modifiers == (Qt::ControlModifier | Qt::ShiftModifier)) + scanForRegionBoundary(targetRow, targetColumn, 0, 1); + else + targetColumn += colSpan; //Unrecognized modifier combination: default to just moving one cell + tabCounter = 0; + break; + case Qt::Key_Up: + if (targetRow == 0) + break; // Nothing to do, we're already in the first column + if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) + targetRow--; + else if (modifiers == Qt::ControlModifier || + modifiers == (Qt::ControlModifier | Qt::ShiftModifier)) + scanForRegionBoundary(targetRow, targetColumn, -1, 0); + else + targetRow--; //Unrecognized modifier combination: default to just moving one cell + tabCounter = 0; + break; + case Qt::Key_Down: + if (targetRow >= this->model()->rowCount() - 1) + break; // Nothing to do, we're already in the last row + if (modifiers == Qt::NoModifier || modifiers == Qt::ShiftModifier) + targetRow += rowSpan; + else if (modifiers == Qt::ControlModifier || + modifiers == (Qt::ControlModifier | Qt::ShiftModifier)) + scanForRegionBoundary(targetRow, targetColumn, 1, 0); + else + targetRow += rowSpan; //Unrecognized modifier combination: default to just moving one cell + tabCounter = 0; + break; + case Qt::Key_Tab: + if (modifiers == Qt::NoModifier) { + tabCounter++; + if (handleTabMotion) + targetColumn += colSpan; + } + else if (modifiers == Qt::ShiftModifier) { + tabCounter = 0; + if (handleTabMotion) + targetColumn--; + } + break; + case Qt::Key_Backtab: + if (modifiers == Qt::NoModifier) { + targetColumn--; + } + tabCounter = 0; + break; + default: + break; + } + + if (this->sheet->isMergedCell(CellAddress(targetRow, targetColumn))) { + auto anchor = this->sheet->getAnchor(CellAddress(targetRow, targetColumn)); + targetRow = anchor.row(); + targetColumn = anchor.col(); + } + + // Overflow/underflow protection: + const int maxRow = this->model()->rowCount() - 1; + const int maxCol = this->model()->columnCount() - 1; + targetRow = std::max(0, std::min(targetRow, maxRow)); + targetColumn = std::max(0, std::min(targetColumn, maxCol)); + + if (!(modifiers & Qt::ShiftModifier) || keyPressed == Qt::Key_Tab || keyPressed == Qt::Key_Enter || keyPressed == Qt::Key_Return) { + // We have to use this method so that Ctrl-modifier combinations don't result in multiple selection + this->selectionModel()->setCurrentIndex(model()->index(targetRow, targetColumn), + QItemSelectionModel::ClearAndSelect); + } + else if (modifiers & Qt::ShiftModifier) { + // With shift down, this motion becomes a block selection command, rather than just simple motion: + ModifyBlockSelection(targetRow, targetColumn); + } + +} + +void SheetTableView::ModifyBlockSelection(int targetRow, int targetCol) +{ + int startingRow = currentIndex().row(); + int startingCol = currentIndex().column(); + + // Get the current block selection size: + auto selection = this->selectionModel()->selection(); + for (const auto& range : selection) { + if (range.contains(currentIndex())) { + // This range contains the current cell, so it's the one we're going to modify (assuming we're at one of the corners) + int rangeMinRow = range.top(); + int rangeMaxRow = range.bottom(); + int rangeMinCol = range.left(); + int rangeMaxCol = range.right(); + if ((startingRow == rangeMinRow || startingRow == rangeMaxRow) && + (startingCol == rangeMinCol || startingCol == rangeMaxCol)) { + if (range.contains(model()->index(targetRow, targetCol))) { + // If the range already contains the target cell, then we're making the range smaller + if (startingRow == rangeMinRow) + rangeMinRow = targetRow; + if (startingRow == rangeMaxRow) + rangeMaxRow = targetRow; + if (startingCol == rangeMinCol) + rangeMinCol = targetCol; + if (startingCol == rangeMaxCol) + rangeMaxCol = targetCol; + } + else { + // We're making the range bigger + rangeMinRow = std::min(rangeMinRow, targetRow); + rangeMaxRow = std::max(rangeMaxRow, targetRow); + rangeMinCol = std::min(rangeMinCol, targetCol); + rangeMaxCol = std::max(rangeMaxCol, targetCol); + } + QItemSelection oldRange(range.topLeft(), range.bottomRight()); + this->selectionModel()->select(oldRange, QItemSelectionModel::Deselect); + QItemSelection newRange(model()->index(rangeMinRow, rangeMinCol), model()->index(rangeMaxRow, rangeMaxCol)); + this->selectionModel()->select(newRange, QItemSelectionModel::Select); + } + break; + } + } + + this->selectionModel()->setCurrentIndex(model()->index(targetRow, targetCol), QItemSelectionModel::Current); +} + +void SheetTableView::closeEditor(QWidget * editor, QAbstractItemDelegate::EndEditHint hint) +{ QTableView::closeEditor(editor, hint); - setCurrentIndex(le->next()); +} + +void SheetTableView::mousePressEvent(QMouseEvent* event) +{ + tabCounter = 0; + QTableView::mousePressEvent(event); } void SheetTableView::edit ( const QModelIndex & index ) diff --git a/src/Mod/Spreadsheet/Gui/SheetTableView.h b/src/Mod/Spreadsheet/Gui/SheetTableView.h index 5dbb7b3b05..fb0f260a23 100644 --- a/src/Mod/Spreadsheet/Gui/SheetTableView.h +++ b/src/Mod/Spreadsheet/Gui/SheetTableView.h @@ -62,6 +62,9 @@ public: void copySelection(); void cutSelection(); void pasteClipboard(); + void finishEditWithMove(int keyPressed, Qt::KeyboardModifiers modifiers, bool handleTabMotion = false); + + void ModifyBlockSelection(int targetRow, int targetColumn); protected Q_SLOTS: void commitData(QWidget *editor); @@ -77,8 +80,10 @@ protected: bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event); bool event(QEvent *event); void closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint); + void mousePressEvent(QMouseEvent* event); QModelIndex currentEditIndex; + int tabCounter; Spreadsheet::Sheet * sheet; boost::signals2::scoped_connection cellSpanChangedConnection; diff --git a/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp b/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp index 835a0ec6af..682e22b0c4 100644 --- a/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp +++ b/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.cpp @@ -47,28 +47,11 @@ QWidget *SpreadsheetDelegate::createEditor(QWidget *parent, const QModelIndex &index) const { SpreadsheetGui::LineEdit *editor = new SpreadsheetGui::LineEdit(parent); - editor->setIndex(index); - editor->setDocumentObject(sheet); - connect(editor, SIGNAL(returnPressed()), this, SLOT(commitAndCloseEditor())); + connect(editor, &SpreadsheetGui::LineEdit::finishedWithKey, this, &SpreadsheetDelegate::on_editorFinishedWithKey); return editor; } -void SpreadsheetDelegate::commitAndCloseEditor() -{ - Gui::ExpressionLineEdit *editor = qobject_cast(sender()); - if (editor->completerActive()) { - editor->hideCompleter(); - return; - } - - // See https://forum.freecadweb.org/viewtopic.php?f=3&t=41694 - // It looks like the slot commitAndCloseEditor() is not needed any more and even - // causes a crash when doing so because the LineEdit is still accessed after its destruction. - //Q_EMIT commitData(editor); - //Q_EMIT closeEditor(editor); -} - void SpreadsheetDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { @@ -89,6 +72,11 @@ void SpreadsheetDelegate::setModelData(QWidget *editor, } } +void SpreadsheetDelegate::on_editorFinishedWithKey(int key, Qt::KeyboardModifiers modifiers) +{ + Q_EMIT finishedWithKey(key, modifiers); +} + QSize SpreadsheetDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const { Q_UNUSED(option); diff --git a/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.h b/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.h index 03080af514..fe4efc7855 100644 --- a/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.h +++ b/src/Mod/Spreadsheet/Gui/SpreadsheetDelegate.h @@ -45,8 +45,10 @@ public: const QModelIndex &index) const; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; +Q_SIGNALS: + void finishedWithKey(int key, Qt::KeyboardModifiers modifiers); private Q_SLOTS: - void commitAndCloseEditor(); + void on_editorFinishedWithKey(int key, Qt::KeyboardModifiers modifiers); private: Spreadsheet::Sheet * sheet; }; diff --git a/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp b/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp index 1bd97d7af6..5addb23b96 100644 --- a/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp +++ b/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp @@ -98,9 +98,12 @@ SheetView::SheetView(Gui::Document *pcDocument, App::DocumentObject *docObj, QWi connect(ui->cells->verticalHeader(), SIGNAL(sectionResized ( int, int, int ) ), this, SLOT(rowResized(int, int, int))); - connect(ui->cellContent, SIGNAL(returnPressed()), this, SLOT( editingFinished() )); - connect(ui->cellAlias, SIGNAL(returnPressed()), this, SLOT( editingFinished() )); - connect(ui->cellAlias, SIGNAL(textEdited(QString)), this, SLOT(aliasChanged(QString))); + connect(delegate, &SpreadsheetDelegate::finishedWithKey, this, &SheetView::editingFinishedWithKey); + connect(ui->cellContent, &LineEdit::finishedWithKey, this, [this](int, Qt::KeyboardModifiers) {confirmContentChanged(ui->cellContent->text()); }); + connect(ui->cellContent, &LineEdit::returnPressed, this, [this]() {confirmContentChanged(ui->cellContent->text()); }); + connect(ui->cellAlias, &LineEdit::finishedWithKey, this, [this](int, Qt::KeyboardModifiers) {confirmAliasChanged(ui->cellAlias->text()); }); + connect(ui->cellAlias, &LineEdit::returnPressed, this, [this]() {confirmAliasChanged(ui->cellAlias->text()); }); + connect(ui->cellAlias, &LineEdit::textEdited, this, &SheetView::aliasChanged); columnWidthChangedConnection = sheet->columnWidthChanged.connect(bind(&SheetView::resizeColumn, this, bp::_1, bp::_2)); rowHeightChangedConnection = sheet->rowHeightChanged.connect(bind(&SheetView::resizeRow, this, bp::_1, bp::_2)); @@ -215,20 +218,6 @@ void SheetView::setCurrentCell(QString str) updateAliasLine(); } -void SheetView::keyPressEvent(QKeyEvent *event) -{ - if (event->key() == Qt::Key_Delete) { - if (event->modifiers() == 0) { - //model()->setData(currentIndex(), QVariant(), Qt::EditRole); - } - else if (event->modifiers() == Qt::ControlModifier) { - //model()->setData(currentIndex(), QVariant(), Qt::EditRole); - } - } - else - Gui::MDIView::keyPressEvent(event); -} - void SheetView::updateContentLine() { QModelIndex i = ui->cells->currentIndex(); @@ -238,7 +227,6 @@ void SheetView::updateContentLine() if (const auto * cell = sheet->getCell(CellAddress(i.row(), i.column()))) (void)cell->getStringContent(str); ui->cellContent->setText(QString::fromUtf8(str.c_str())); - ui->cellContent->setIndex(i); ui->cellContent->setEnabled(true); // Update completer model; for the time being, we do this by setting the document object of the input line. @@ -255,7 +243,6 @@ void SheetView::updateAliasLine() if (const auto * cell = sheet->getCell(CellAddress(i.row(), i.column()))) (void)cell->getAlias(str); ui->cellAlias->setText(QString::fromUtf8(str.c_str())); - ui->cellAlias->setIndex(i); ui->cellAlias->setEnabled(true); // Update completer model; for the time being, we do this by setting the document object of the input line. @@ -322,51 +309,53 @@ void SheetView::resizeRow(int col, int newSize) ui->cells->setRowHeight(col, newSize); } -void SheetView::editingFinished() +void SheetView::editingFinishedWithKey(int key, Qt::KeyboardModifiers modifiers) { - if (ui->cellContent->completerActive()) { - ui->cellContent->hideCompleter(); - return; - } - - if (ui->cellAlias->completerActive()) { - ui->cellAlias->hideCompleter(); - return; - } - QModelIndex i = ui->cells->currentIndex(); if (i.isValid()) { - QString str = ui->cellAlias->text(); - bool aliasOkay = true; - - if (str.length()!= 0 && !sheet->isValidAlias(Base::Tools::toStdString(str))){ - aliasOkay = false; - } - - ui->cellAlias->setDocumentObject(sheet); ui->cells->model()->setData(i, QVariant(ui->cellContent->text()), Qt::EditRole); + ui->cells->finishEditWithMove(key, modifiers); + } +} - if (const auto * cell = sheet->getCell(CellAddress(i.row(), i.column()))){ - if (!aliasOkay){ - //do not show error message if failure to set new alias is because it is already the same string - std::string current_alias; - (void)cell->getAlias(current_alias); - if (str != QString::fromUtf8(current_alias.c_str())){ - Base::Console().Error("Unable to set alias: %s\n", Base::Tools::toStdString(str).c_str()); - } - } else { - std::string address = CellAddress(i.row(), i.column()).toString(); - Gui::cmdAppObjectArgs(sheet, "setAlias('%s', '%s')", - address, str.toStdString()); - Gui::cmdAppDocument(sheet->getDocument(), "recompute()"); +void SheetView::confirmAliasChanged(const QString& text) +{ + bool aliasOkay = true; + + ui->cellAlias->setDocumentObject(sheet); + if (text.length() != 0 && !sheet->isValidAlias(Base::Tools::toStdString(text))) { + aliasOkay = false; + } + + QModelIndex i = ui->cells->currentIndex(); + if (const auto* cell = sheet->getCell(CellAddress(i.row(), i.column()))) { + if (!aliasOkay) { + //do not show error message if failure to set new alias is because it is already the same string + std::string current_alias; + (void)cell->getAlias(current_alias); + if (text != QString::fromUtf8(current_alias.c_str())) { + Base::Console().Error("Unable to set alias: %s\n", Base::Tools::toStdString(text).c_str()); } } - ui->cells->setCurrentIndex(ui->cellContent->next()); - ui->cells->setFocus(); + else { + std::string address = CellAddress(i.row(), i.column()).toString(); + Gui::cmdAppObjectArgs(sheet, "setAlias('%s', '%s')", + address, text.toStdString()); + Gui::cmdAppDocument(sheet->getDocument(), "recompute()"); + ui->cells->setFocus(); + } } } + +void SheetView::confirmContentChanged(const QString& text) +{ + QModelIndex i = ui->cells->currentIndex(); + ui->cells->model()->setData(i, QVariant(ui->cellContent->text()), Qt::EditRole); + ui->cells->setFocus(); +} + void SheetView::aliasChanged(const QString& text) { // check live the input and highlight if the user input invalid characters diff --git a/src/Mod/Spreadsheet/Gui/SpreadsheetView.h b/src/Mod/Spreadsheet/Gui/SpreadsheetView.h index 8e6aced3ef..141cdfe59b 100644 --- a/src/Mod/Spreadsheet/Gui/SpreadsheetView.h +++ b/src/Mod/Spreadsheet/Gui/SpreadsheetView.h @@ -82,8 +82,10 @@ public: virtual void deleteSelf(); protected Q_SLOTS: - void editingFinished(); + void editingFinishedWithKey(int key, Qt::KeyboardModifiers modifiers); + void confirmAliasChanged(const QString& text); void aliasChanged(const QString& text); + void confirmContentChanged(const QString& text); void currentChanged( const QModelIndex & current, const QModelIndex & previous ); void columnResized(int col, int oldSize, int newSize); void rowResized(int row, int oldSize, int newSize); @@ -94,7 +96,6 @@ protected: void updateContentLine(); void updateAliasLine(); void setCurrentCell(QString str); - void keyPressEvent(QKeyEvent *event); void resizeColumn(int col, int newSize); void resizeRow(int col, int newSize);