diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp index 8577bf8630..0cf62f8e09 100644 --- a/src/kitemviews/kitemlistcontroller.cpp +++ b/src/kitemviews/kitemlistcontroller.cpp @@ -50,7 +50,9 @@ KItemListController::KItemListController(QObject* parent) : m_pressedIndex(-1), m_pressedMousePos(), m_autoActivationTimer(0), - m_oldSelection() + m_oldSelection(), + m_keyboardAnchorIndex(-1), + m_keyboardAnchorXPos(0) { connect(m_keyboardManager, SIGNAL(changeCurrentItem(QString,bool)), this, SLOT(slotChangeCurrentItem(QString,bool))); @@ -175,7 +177,6 @@ bool KItemListController::keyPressEvent(QKeyEvent* event) const bool shiftOrControlPressed = shiftPressed || controlPressed; const int itemCount = m_model->count(); - const int itemsPerRow = m_view->itemsPerOffset(); // For horizontal scroll orientation, transform // the arrow keys to simplify the event handling. @@ -210,37 +211,28 @@ bool KItemListController::keyPressEvent(QKeyEvent* event) case Qt::Key_Left: if (index > 0) { - index--; + --index; + m_keyboardAnchorIndex = index; + m_keyboardAnchorXPos = keyboardAnchorPos(index); } break; case Qt::Key_Right: if (index < itemCount - 1) { - index++; + ++index; + m_keyboardAnchorIndex = index; + m_keyboardAnchorXPos = keyboardAnchorPos(index); } break; case Qt::Key_Up: - if (index >= itemsPerRow) { - index -= itemsPerRow; - } + updateKeyboardAnchor(); + index = previousRowIndex(); break; case Qt::Key_Down: - if (index + itemsPerRow < itemCount) { - // We are not in the last row yet. - index += itemsPerRow; - } else { - // We are either in the last row already, or we are in the second-last row, - // and there is no item below the current item. - // In the latter case, we jump to the very last item. - const int currentColumn = index % itemsPerRow; - const int lastItemColumn = (itemCount - 1) % itemsPerRow; - const bool inLastRow = currentColumn < lastItemColumn; - if (!inLastRow) { - index = itemCount - 1; - } - } + updateKeyboardAnchor(); + index = nextRowIndex(); break; case Qt::Key_Enter: @@ -955,4 +947,97 @@ KItemListWidget* KItemListController::widgetForPos(const QPointF& pos) const return 0; } +void KItemListController::updateKeyboardAnchor() +{ + const bool validAnchor = m_keyboardAnchorIndex >= 0 && + m_keyboardAnchorIndex < m_model->count() && + keyboardAnchorPos(m_keyboardAnchorIndex) == m_keyboardAnchorXPos; + if (!validAnchor) { + const int index = m_selectionManager->currentItem(); + m_keyboardAnchorIndex = index; + m_keyboardAnchorXPos = keyboardAnchorPos(index); + } +} + +int KItemListController::nextRowIndex() const +{ + const int currentIndex = m_selectionManager->currentItem(); + if (m_keyboardAnchorIndex < 0) { + return currentIndex; + } + + const int maxIndex = m_model->count() - 1; + if (currentIndex == maxIndex) { + return currentIndex; + } + + // Calculate the index of the last column inside the row of the current index + int lastColumnIndex = currentIndex; + while (keyboardAnchorPos(lastColumnIndex + 1) > keyboardAnchorPos(lastColumnIndex)) { + ++lastColumnIndex; + if (lastColumnIndex >= maxIndex) { + return currentIndex; + } + } + + // Based on the last column index go to the next row and calculate the nearest index + // that is below the current index + int nextRowIndex = lastColumnIndex + 1; + int searchIndex = nextRowIndex; + qreal minDiff = qAbs(m_keyboardAnchorXPos - keyboardAnchorPos(nextRowIndex)); + while (searchIndex < maxIndex && keyboardAnchorPos(searchIndex + 1) > keyboardAnchorPos(searchIndex)) { + ++searchIndex; + const qreal searchDiff = qAbs(m_keyboardAnchorXPos - keyboardAnchorPos(searchIndex)); + if (searchDiff < minDiff) { + minDiff = searchDiff; + nextRowIndex = searchIndex; + } + } + + return nextRowIndex; +} + +int KItemListController::previousRowIndex() const +{ + const int currentIndex = m_selectionManager->currentItem(); + if (m_keyboardAnchorIndex < 0 || currentIndex == 0) { + return currentIndex; + } + + // Calculate the index of the first column inside the row of the current index + int firstColumnIndex = currentIndex; + while (keyboardAnchorPos(firstColumnIndex - 1) < keyboardAnchorPos(firstColumnIndex)) { + --firstColumnIndex; + if (firstColumnIndex <= 0) { + return currentIndex; + } + } + + // Based on the first column index go to the previous row and calculate the nearest index + // that is above the current index + int previousRowIndex = firstColumnIndex - 1; + int searchIndex = previousRowIndex; + qreal minDiff = qAbs(m_keyboardAnchorXPos - keyboardAnchorPos(previousRowIndex)); + while (searchIndex > 0 && keyboardAnchorPos(searchIndex - 1) < keyboardAnchorPos(searchIndex)) { + --searchIndex; + const qreal searchDiff = qAbs(m_keyboardAnchorXPos - keyboardAnchorPos(searchIndex)); + if (searchDiff < minDiff) { + minDiff = searchDiff; + previousRowIndex = searchIndex; + } + } + + return previousRowIndex; +} + +qreal KItemListController::keyboardAnchorPos(int index) const +{ + const QRectF itemRect = m_view->itemRect(index); + if (!itemRect.isEmpty()) { + return (m_view->scrollOrientation() == Qt::Vertical) ? itemRect.x() : itemRect.y(); + } + + return 0; +} + #include "kitemlistcontroller.moc" diff --git a/src/kitemviews/kitemlistcontroller.h b/src/kitemviews/kitemlistcontroller.h index e0e8b0a9ba..b2c7d65b2e 100644 --- a/src/kitemviews/kitemlistcontroller.h +++ b/src/kitemviews/kitemlistcontroller.h @@ -220,6 +220,34 @@ private: */ KItemListWidget* widgetForPos(const QPointF& pos) const; + /** + * Updates m_keyboardAnchorIndex and m_keyboardAnchorPos. If no anchor is + * set, it will be adjusted to the current item. If it is set it will be + * checked whether it is still valid, otherwise it will be reset to the + * current item. + */ + void updateKeyboardAnchor(); + + /** + * @return Index for the next row based on the current index. + * If there is no next row the current index will be returned. + */ + int nextRowIndex() const; + + /** + * @return Index for the previous row based on the current index. + * If there is no previous row the current index will be returned. + */ + int previousRowIndex() const; + + /** + * Helper method for updateKeyboardAnchor(), previousRowIndex() and nextRowIndex(). + * @return The position of the keyboard anchor for the item with the index \a index. + * If a horizontal scrolling is used the y-position of the item will be returned, + * for the vertical scrolling the x-position will be returned. + */ + qreal keyboardAnchorPos(int index) const; + private: bool m_selectionTogglePressed; SelectionBehavior m_selectionBehavior; @@ -235,10 +263,27 @@ private: /** * When starting a rubberband selection during a Shift- or Control-key has been * pressed the current selection should never be deleted. To be able to restore - * the current selection it is remembered in m_oldSelection before + * the current selection it is remembered in m_oldSelection before the * rubberband gets activated. */ QSet m_oldSelection; + + /** + * Assuming a view is given with a vertical scroll-orientation, grouped items and + * a maximum of 4 columns: + * + * 1 2 3 4 + * 5 6 7 + * 8 9 10 11 + * 12 13 14 + * + * If the current index is on 4 and key-down is pressed, then item 7 gets the current + * item. Now when pressing key-down again item 11 should get the current item and not + * item 10. This makes it necessary to keep track of the requested column to have a + * similar behavior like in a text editor: + */ + int m_keyboardAnchorIndex; + qreal m_keyboardAnchorXPos; }; #endif diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index 42445dcfe8..be03606cea 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -485,11 +485,6 @@ void KItemListView::scrollToItem(int index) } } -int KItemListView::itemsPerOffset() const -{ - return m_layouter->itemsPerOffset(); -} - void KItemListView::beginTransaction() { ++m_activeTransactions; diff --git a/src/kitemviews/kitemlistview.h b/src/kitemviews/kitemlistview.h index 90f753087a..d44a08c027 100644 --- a/src/kitemviews/kitemlistview.h +++ b/src/kitemviews/kitemlistview.h @@ -202,15 +202,6 @@ public: */ void scrollToItem(int index); - /** - * @return The number of items that can be shown in parallel for one offset. - * Assuming the scrolldirection is vertical then a value of 4 means - * that 4 columns for items are available. Assuming the scrolldirection - * is horizontal then a value of 4 means that 4 lines for items are - * available. - */ - int itemsPerOffset() const; - void beginTransaction(); void endTransaction(); bool isTransactionActive() const; diff --git a/src/kitemviews/kitemlistviewlayouter.cpp b/src/kitemviews/kitemlistviewlayouter.cpp index 8dbbb372aa..b486a390ba 100644 --- a/src/kitemviews/kitemlistviewlayouter.cpp +++ b/src/kitemviews/kitemlistviewlayouter.cpp @@ -253,11 +253,6 @@ int KItemListViewLayouter::maximumVisibleItems() const return rows * m_columnCount; } -int KItemListViewLayouter::itemsPerOffset() const -{ - return m_columnCount; -} - bool KItemListViewLayouter::isFirstGroupItem(int itemIndex) const { const_cast(this)->doLayout(); diff --git a/src/kitemviews/kitemlistviewlayouter_p.h b/src/kitemviews/kitemlistviewlayouter_p.h index 109528c634..3d2f109063 100644 --- a/src/kitemviews/kitemlistviewlayouter_p.h +++ b/src/kitemviews/kitemlistviewlayouter_p.h @@ -107,13 +107,6 @@ public: */ int maximumVisibleItems() const; - /** - * @return Maximum number of items that can be shown in the same row - * (= vertical scrolldirection) or same column - * (= horizontal scrolldirection). - */ - int itemsPerOffset() const; - /** * @return True if the item with the index \p itemIndex * is the first item within a group.