/***************************************************************************
* 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 *
#include <QApplication>
#include <QDrag>
#include <QEvent>
+#include <QGraphicsScene>
#include <QGraphicsSceneEvent>
+#include <QGraphicsView>
#include <QMimeData>
#include <QTimer>
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),
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)));
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);
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.
}
}
+ 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);
+ if (selectSingleItem) {
+ const int current = m_selectionManager->currentItem();
+ m_selectionManager->setSelected(current);
+ return true;
+ }
+
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;
- }
- 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(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 {
+ 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;
}
case Qt::Key_Space:
- if (controlPressed) {
- m_selectionManager->endAnchoredSelection();
- m_selectionManager->setSelected(index, 1, KItemListSelectionManager::Toggle);
- m_selectionManager->beginAnchoredSelection(index);
- break;
+ 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;
+
+ case Qt::Key_Menu: {
+ // Emit the signal itemContextMenuRequested() in case if at least one
+ // item is selected. Otherwise the signal viewContextMenuRequested() will be emitted.
+ const QSet<int> selectedItems = m_selectionManager->selectedItems();
+ int index = -1;
+ if (selectedItems.count() >= 2) {
+ const int currentItemIndex = m_selectionManager->currentItem();
+ index = selectedItems.contains(currentItemIndex)
+ ? currentItemIndex : selectedItems.toList().first();
+ } else if (selectedItems.count() == 1) {
+ index = selectedItems.toList().first();
+ }
+
+ if (index >= 0) {
+ const QRectF contextRect = m_view->itemContextRect(index);
+ const QPointF pos(m_view->scene()->views().first()->mapToGlobal(contextRect.bottomRight().toPoint()));
+ emit itemContextMenuRequested(index, pos);
+ } else {
+ emit viewContextMenuRequested(QCursor::pos());
+ }
+ break;
+ }
default:
m_keyboardManager->addKeys(event->text());
}
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;
+
+ 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);
+ if (!shiftPressed) {
+ m_selectionManager->beginAnchoredSelection(index);
+ }
+ break;
}
m_view->scrollToItem(index);
int index;
if (searchFromNextItem) {
index = m_model->indexForKeyboardSearch(text, (currentIndex + 1) % m_model->count());
- }
- else {
+ } else {
index = m_model->indexForKeyboardSearch(text, currentIndex);
}
if (index >= 0) {
m_selectionManager->clearSelection();
m_selectionManager->setSelected(index, 1);
m_selectionManager->beginAnchoredSelection(index);
+ m_view->scrollToItem(index);
}
}
m_pressedMousePos = transform.map(event->pos());
m_pressedIndex = m_view->itemAt(m_pressedMousePos);
+ if (m_pressedIndex >= 0) {
+ emit itemPressed(m_pressedIndex, event->button());
+ }
if (m_view->isAboveExpansionToggle(m_pressedIndex, m_pressedMousePos)) {
+ m_selectionManager->endAnchoredSelection();
m_selectionManager->setCurrentItem(m_pressedIndex);
+ m_selectionManager->beginAnchoredSelection(m_pressedIndex);
return true;
}
m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
if (m_selectionTogglePressed) {
m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
+ // The previous anchored selection has been finished already in
+ // KItemListSelectionManager::setSelected(). We can safely change
+ // the current item and start a new anchored selection now.
m_selectionManager->setCurrentItem(m_pressedIndex);
+ m_selectionManager->beginAnchoredSelection(m_pressedIndex);
return true;
}
(!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) {
// 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();
}
}
return false;
}
+ if (m_pressedIndex >= 0) {
+ emit itemReleased(m_pressedIndex, event->button());
+ }
+
const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
if (isAboveSelectionToggle) {
m_selectionTogglePressed = false;
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)) {
} 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) {
m_pressedMousePos = QPointF();
m_pressedIndex = -1;
+ m_clearSelectionIfItemsAreNotDragged = false;
return false;
}
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) {
{
Q_UNUSED(event);
Q_UNUSED(transform);
+
+ KItemListWidget* widget = hoveredWidget();
+ if (widget) {
+ widget->setHovered(false);
+ emit itemUnhovered(widget->index());
+ }
return false;
}
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
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"