]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/kitemlistcontroller.cpp
Whitespace cleanups and documentation fixes
[dolphin.git] / src / kitemviews / kitemlistcontroller.cpp
index 8577bf8630ae0eb033be5ca748c457f6c4e3a14c..239cf2e6c4b3c2d33bb46921e487e8b95d7155ac 100644 (file)
@@ -1,5 +1,6 @@
 /***************************************************************************
  *   Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com>             *
+ *   Copyright (C) 2012 by Frank Reininghaus <frank78ac@googlemail.com>    *
  *                                                                         *
  *   Based on the Itemviews NG project from Trolltech Labs:                *
  *   http://qt.gitorious.org/qt-labs/itemviews-ng                          *
@@ -41,7 +42,9 @@
 
 KItemListController::KItemListController(QObject* parent) :
     QObject(parent),
+    m_singleClickActivation(KGlobalSettings::singleClick()),
     m_selectionTogglePressed(false),
+    m_clearSelectionIfItemsAreNotDragged(false),
     m_selectionBehavior(NoSelection),
     m_model(0),
     m_view(0),
@@ -50,7 +53,9 @@ KItemListController::KItemListController(QObject* parent) :
     m_pressedIndex(-1),
     m_pressedMousePos(),
     m_autoActivationTimer(0),
-    m_oldSelection()
+    m_oldSelection(),
+    m_keyboardAnchorIndex(-1),
+    m_keyboardAnchorPos(0)
 {
     connect(m_keyboardManager, SIGNAL(changeCurrentItem(QString,bool)),
             this, SLOT(slotChangeCurrentItem(QString,bool)));
@@ -140,6 +145,16 @@ int KItemListController::autoActivationDelay() const
     return m_autoActivationTimer->interval();
 }
 
+void KItemListController::setSingleClickActivation(bool singleClick)
+{
+    m_singleClickActivation = singleClick;
+}
+
+bool KItemListController::singleClickActivation() const
+{
+    return m_singleClickActivation;
+}
+
 bool KItemListController::showEvent(QShowEvent* event)
 {
     Q_UNUSED(event);
@@ -175,7 +190,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.
@@ -188,8 +202,9 @@ bool KItemListController::keyPressEvent(QKeyEvent* event)
         default:            break;
         }
     }
-    
-    const bool selectSingleItem = itemCount == 1 &&
+
+    const bool selectSingleItem = m_selectionBehavior != NoSelection &&
+                                  itemCount == 1 &&
                                   (key == Qt::Key_Home || key == Qt::Key_End  ||
                                    key == Qt::Key_Up   || key == Qt::Key_Down ||
                                    key == Qt::Key_Left || key == Qt::Key_Right);
@@ -202,44 +217,95 @@ bool KItemListController::keyPressEvent(QKeyEvent* event)
     switch (key) {
     case Qt::Key_Home:
         index = 0;
+        m_keyboardAnchorIndex = index;
+        m_keyboardAnchorPos = keyboardAnchorPos(index);
         break;
 
     case Qt::Key_End:
         index = itemCount - 1;
+        m_keyboardAnchorIndex = index;
+        m_keyboardAnchorPos = keyboardAnchorPos(index);
         break;
 
     case Qt::Key_Left:
         if (index > 0) {
-            index--;
+            --index;
+            m_keyboardAnchorIndex = index;
+            m_keyboardAnchorPos = keyboardAnchorPos(index);
         }
         break;
 
     case Qt::Key_Right:
         if (index < itemCount - 1) {
-            index++;
+            ++index;
+            m_keyboardAnchorIndex = index;
+            m_keyboardAnchorPos = keyboardAnchorPos(index);
         }
         break;
 
     case Qt::Key_Up:
-        if (index >= itemsPerRow) {
-            index -= itemsPerRow;
-        }
+        updateKeyboardAnchor();
+        index = previousRowIndex(index);
         break;
 
     case Qt::Key_Down:
-        if (index + itemsPerRow < itemCount) {
-            // We are not in the last row yet.
-            index += itemsPerRow;
+        updateKeyboardAnchor();
+        index = nextRowIndex(index);
+        break;
+
+    case Qt::Key_PageUp:
+        if (m_view->scrollOrientation() == Qt::Horizontal) {
+            // The new current index should correspond to the first item in the current column.
+            int newIndex = qMax(index - 1, 0);
+            while (newIndex != index && m_view->itemRect(newIndex).topLeft().y() < m_view->itemRect(index).topLeft().y()) {
+                index = newIndex;
+                newIndex = qMax(index - 1, 0);
+            }
+            m_keyboardAnchorIndex = index;
+            m_keyboardAnchorPos = keyboardAnchorPos(index);
         } 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;
+            const qreal currentItemBottom = m_view->itemRect(index).bottomLeft().y();
+            const qreal height = m_view->geometry().height();
+
+            // The new current item should be the first item in the current
+            // column whose itemRect's top coordinate is larger than targetY.
+            const qreal targetY = currentItemBottom - height;
+
+            updateKeyboardAnchor();
+            int newIndex = previousRowIndex(index);
+            do {
+                index = newIndex;
+                updateKeyboardAnchor();
+                newIndex = previousRowIndex(index);
+            } while (m_view->itemRect(newIndex).topLeft().y() > targetY && newIndex != index);
+        }
+        break;
+
+    case Qt::Key_PageDown:
+        if (m_view->scrollOrientation() == Qt::Horizontal) {
+            // The new current index should correspond to the last item in the current column.
+            int newIndex = qMin(index + 1, m_model->count() - 1);
+            while (newIndex != index && m_view->itemRect(newIndex).topLeft().y() > m_view->itemRect(index).topLeft().y()) {
+                index = newIndex;
+                newIndex = qMin(index + 1, m_model->count() - 1);
             }
+            m_keyboardAnchorIndex = index;
+            m_keyboardAnchorPos = keyboardAnchorPos(index);
+        } else {
+            const qreal currentItemTop = m_view->itemRect(index).topLeft().y();
+            const qreal height = m_view->geometry().height();
+
+            // The new current item should be the last item in the current
+            // column whose itemRect's bottom coordinate is smaller than targetY.
+            const qreal targetY = currentItemTop + height;
+
+            updateKeyboardAnchor();
+            int newIndex = nextRowIndex(index);
+            do {
+                index = newIndex;
+                updateKeyboardAnchor();
+                newIndex = nextRowIndex(index);
+            } while (m_view->itemRect(newIndex).bottomLeft().y() < targetY && newIndex != index);
         }
         break;
 
@@ -257,13 +323,15 @@ bool KItemListController::keyPressEvent(QKeyEvent* event)
     }
 
     case Qt::Key_Space:
-        if (controlPressed) {
-            m_selectionManager->endAnchoredSelection();
-            m_selectionManager->setSelected(index, 1, KItemListSelectionManager::Toggle);
-            m_selectionManager->beginAnchoredSelection(index);
-        } else {
-            const int current = m_selectionManager->currentItem();
-            m_selectionManager->setSelected(current);
+        if (m_selectionBehavior == MultiSelection) {
+            if (controlPressed) {
+                m_selectionManager->endAnchoredSelection();
+                m_selectionManager->setSelected(index, 1, KItemListSelectionManager::Toggle);
+                m_selectionManager->beginAnchoredSelection(index);
+            } else {
+                const int current = m_selectionManager->currentItem();
+                m_selectionManager->setSelected(current);
+            }
         }
         break;
 
@@ -296,19 +364,33 @@ bool KItemListController::keyPressEvent(QKeyEvent* event)
     }
 
     if (m_selectionManager->currentItem() != index) {
-        if (controlPressed) {
-            m_selectionManager->endAnchoredSelection();
-        }
-
-        m_selectionManager->setCurrentItem(index);
+        switch (m_selectionBehavior) {
+        case NoSelection:
+            m_selectionManager->setCurrentItem(index);
+            break;
 
-        if (!shiftOrControlPressed || m_selectionBehavior == SingleSelection) {
+        case SingleSelection:
+            m_selectionManager->setCurrentItem(index);
             m_selectionManager->clearSelection();
             m_selectionManager->setSelected(index, 1);
-        }
+            break;
 
-        if (!shiftPressed) {
-            m_selectionManager->beginAnchoredSelection(index);
+        case MultiSelection:
+            if (controlPressed) {
+                m_selectionManager->endAnchoredSelection();
+            }
+
+            m_selectionManager->setCurrentItem(index);
+
+            if (!shiftOrControlPressed) {
+                m_selectionManager->clearSelection();
+                m_selectionManager->setSelected(index, 1);
+            }
+
+            if (!shiftPressed) {
+                m_selectionManager->beginAnchoredSelection(index);
+            }
+            break;
         }
 
         m_view->scrollToItem(index);
@@ -377,7 +459,9 @@ bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent* event, const
     }
 
     if (m_view->isAboveExpansionToggle(m_pressedIndex, m_pressedMousePos)) {
+        m_selectionManager->endAnchoredSelection();
         m_selectionManager->setCurrentItem(m_pressedIndex);
+        m_selectionManager->beginAnchoredSelection(m_pressedIndex);
         return true;
     }
 
@@ -408,6 +492,13 @@ bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent* event, const
                                 (!shiftOrControlPressed && !pressedItemAlreadySelected);
     if (clearSelection) {
         m_selectionManager->clearSelection();
+    } else if (pressedItemAlreadySelected && !shiftOrControlPressed && (event->buttons() & Qt::LeftButton)) {
+        // The user might want to start dragging multiple items, but if he clicks the item
+        // in order to trigger it instead, the other selected items must be deselected.
+        // However, we do not know yet what the user is going to do.
+        // -> remember that the user pressed an item which had been selected already and
+        //    clear the selection in mouseReleaseEvent(), unless the items are dragged.
+        m_clearSelectionIfItemsAreNotDragged = true;
     }
 
     if (!shiftPressed) {
@@ -500,7 +591,12 @@ bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const
                     // done on the mouse-press event, but when using the selection-toggle on a
                     // selected item the dragged item is not selected yet.
                     m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
+                } else {
+                    // A selected item has been clicked to drag all selected items
+                    // -> the selection should not be cleared when the mouse button is released.
+                    m_clearSelectionIfItemsAreNotDragged = false;
                 }
+
                 startDragging();
             }
         }
@@ -574,6 +670,14 @@ bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, con
     if (index >= 0 && index == m_pressedIndex) {
         // The release event is done above the same item as the press event
 
+        if (m_clearSelectionIfItemsAreNotDragged) {
+            // A selected item has been clicked, but no drag operation has been started
+            // -> clear the rest of the selection.
+            m_selectionManager->clearSelection();
+            m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
+            m_selectionManager->beginAnchoredSelection(m_pressedIndex);
+        }
+
         if (event->button() & Qt::LeftButton) {
             bool emitItemActivated = true;
             if (m_view->isAboveExpansionToggle(index, pos)) {
@@ -585,7 +689,7 @@ bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, con
             } else if (shiftOrControlPressed) {
                 // The mouse click should only update the selection, not trigger the item
                 emitItemActivated = false;
-            } else if (!KGlobalSettings::singleClick()) {
+            } else if (!m_singleClickActivation) {
                 emitItemActivated = false;
             }
             if (emitItemActivated) {
@@ -598,6 +702,7 @@ bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, con
 
     m_pressedMousePos = QPointF();
     m_pressedIndex = -1;
+    m_clearSelectionIfItemsAreNotDragged = false;
     return false;
 }
 
@@ -606,7 +711,7 @@ bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event,
     const QPointF pos = transform.map(event->pos());
     const int index = m_view->itemAt(pos);
 
-    bool emitItemActivated = !KGlobalSettings::singleClick() &&
+    bool emitItemActivated = !m_singleClickActivation &&
                              (event->button() & Qt::LeftButton) &&
                              index >= 0 && index < m_model->count();
     if (emitItemActivated) {
@@ -626,6 +731,12 @@ bool KItemListController::dragLeaveEvent(QGraphicsSceneDragDropEvent* event, con
 {
     Q_UNUSED(event);
     Q_UNUSED(transform);
+
+    KItemListWidget* widget = hoveredWidget();
+    if (widget) {
+        widget->setHovered(false);
+        emit itemUnhovered(widget->index());
+    }
     return false;
 }
 
@@ -922,7 +1033,7 @@ void KItemListController::startDragging()
     const QPixmap pixmap = m_view->createDragPixmap(selectedItems);
     drag->setPixmap(pixmap);
 
-    drag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::IgnoreAction);
+    drag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::CopyAction);
 }
 
 KItemListWidget* KItemListController::hoveredWidget() const
@@ -955,4 +1066,95 @@ 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_keyboardAnchorPos;
+    if (!validAnchor) {
+        const int index = m_selectionManager->currentItem();
+        m_keyboardAnchorIndex = index;
+        m_keyboardAnchorPos = keyboardAnchorPos(index);
+    }
+}
+
+int KItemListController::nextRowIndex(int index) const
+{
+    if (m_keyboardAnchorIndex < 0) {
+        return index;
+    }
+
+    const int maxIndex = m_model->count() - 1;
+    if (index == maxIndex) {
+        return index;
+    }
+
+    // Calculate the index of the last column inside the row of the current index
+    int lastColumnIndex = index;
+    while (keyboardAnchorPos(lastColumnIndex + 1) > keyboardAnchorPos(lastColumnIndex)) {
+        ++lastColumnIndex;
+        if (lastColumnIndex >= maxIndex) {
+            return index;
+        }
+    }
+
+    // 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_keyboardAnchorPos - keyboardAnchorPos(nextRowIndex));
+    while (searchIndex < maxIndex && keyboardAnchorPos(searchIndex + 1) > keyboardAnchorPos(searchIndex)) {
+        ++searchIndex;
+        const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
+        if (searchDiff < minDiff) {
+            minDiff = searchDiff;
+            nextRowIndex = searchIndex;
+        }
+    }
+
+    return nextRowIndex;
+}
+
+int KItemListController::previousRowIndex(int index) const
+{
+    if (m_keyboardAnchorIndex < 0 || index == 0) {
+        return index;
+    }
+
+    // Calculate the index of the first column inside the row of the current index
+    int firstColumnIndex = index;
+    while (keyboardAnchorPos(firstColumnIndex - 1) < keyboardAnchorPos(firstColumnIndex)) {
+        --firstColumnIndex;
+        if (firstColumnIndex <= 0) {
+            return index;
+        }
+    }
+
+    // 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_keyboardAnchorPos - keyboardAnchorPos(previousRowIndex));
+    while (searchIndex > 0 && keyboardAnchorPos(searchIndex - 1) < keyboardAnchorPos(searchIndex)) {
+        --searchIndex;
+        const qreal searchDiff = qAbs(m_keyboardAnchorPos - 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"