#include "kitemlistcontroller.h"
+#include <KGlobalSettings>
+#include <KDebug>
+
#include "kitemlistview.h"
-#include "kitemlistrubberband_p.h"
#include "kitemlistselectionmanager.h"
-#include "kitemlistkeyboardsearchmanager_p.h"
+
+#include "private/kitemlistrubberband.h"
+#include "private/kitemlistkeyboardsearchmanager.h"
#include <QApplication>
#include <QDrag>
#include <QGraphicsView>
#include <QMimeData>
#include <QTimer>
+#include <QAccessible>
-#include <KGlobalSettings>
-#include <KDebug>
-
-KItemListController::KItemListController(QObject* parent) :
+KItemListController::KItemListController(KItemModelBase* model, KItemListView* view, QObject* parent) :
QObject(parent),
- m_singleClickActivation(KGlobalSettings::singleClick()),
+ m_singleClickActivationEnforced(false),
m_selectionTogglePressed(false),
m_clearSelectionIfItemsAreNotDragged(false),
m_selectionBehavior(NoSelection),
+ m_autoActivationBehavior(ActivationAndExpansion),
+ m_mouseDoubleClickAction(ActivateItemOnly),
m_model(0),
m_view(0),
m_selectionManager(new KItemListSelectionManager(this)),
m_keyboardAnchorIndex(-1),
m_keyboardAnchorPos(0)
{
- connect(m_keyboardManager, SIGNAL(changeCurrentItem(QString,bool)),
- this, SLOT(slotChangeCurrentItem(QString,bool)));
+ connect(m_keyboardManager, &KItemListKeyboardSearchManager::changeCurrentItem,
+ this, &KItemListController::slotChangeCurrentItem);
+ connect(m_selectionManager, &KItemListSelectionManager::currentChanged,
+ m_keyboardManager, &KItemListKeyboardSearchManager::slotCurrentChanged);
m_autoActivationTimer = new QTimer(this);
m_autoActivationTimer->setSingleShot(true);
m_autoActivationTimer->setInterval(-1);
- connect(m_autoActivationTimer, SIGNAL(timeout()), this, SLOT(slotAutoActivationTimeout()));
+ connect(m_autoActivationTimer, &QTimer::timeout, this, &KItemListController::slotAutoActivationTimeout);
+
+ setModel(model);
+ setView(view);
}
KItemListController::~KItemListController()
{
+ setView(0);
+ Q_ASSERT(!m_view);
+
+ setModel(0);
+ Q_ASSERT(!m_model);
}
void KItemListController::setModel(KItemModelBase* model)
}
KItemModelBase* oldModel = m_model;
+ if (oldModel) {
+ oldModel->deleteLater();
+ }
+
m_model = model;
+ if (m_model) {
+ m_model->setParent(this);
+ }
if (m_view) {
m_view->setModel(m_model);
KItemListView* oldView = m_view;
if (oldView) {
- disconnect(oldView, SIGNAL(scrollOffsetChanged(qreal,qreal)), this, SLOT(slotViewScrollOffsetChanged(qreal,qreal)));
+ disconnect(oldView, &KItemListView::scrollOffsetChanged, this, &KItemListController::slotViewScrollOffsetChanged);
+ oldView->deleteLater();
}
m_view = view;
if (m_view) {
+ m_view->setParent(this);
m_view->setController(this);
m_view->setModel(m_model);
- connect(m_view, SIGNAL(scrollOffsetChanged(qreal,qreal)), this, SLOT(slotViewScrollOffsetChanged(qreal,qreal)));
+ connect(m_view, &KItemListView::scrollOffsetChanged, this, &KItemListController::slotViewScrollOffsetChanged);
+ updateExtendedSelectionRegion();
}
emit viewChanged(m_view, oldView);
void KItemListController::setSelectionBehavior(SelectionBehavior behavior)
{
m_selectionBehavior = behavior;
+ updateExtendedSelectionRegion();
}
KItemListController::SelectionBehavior KItemListController::selectionBehavior() const
return m_selectionBehavior;
}
+void KItemListController::setAutoActivationBehavior(AutoActivationBehavior behavior)
+{
+ m_autoActivationBehavior = behavior;
+}
+
+KItemListController::AutoActivationBehavior KItemListController::autoActivationBehavior() const
+{
+ return m_autoActivationBehavior;
+}
+
+void KItemListController::setMouseDoubleClickAction(MouseDoubleClickAction action)
+{
+ m_mouseDoubleClickAction = action;
+}
+
+KItemListController::MouseDoubleClickAction KItemListController::mouseDoubleClickAction() const
+{
+ return m_mouseDoubleClickAction;
+}
+
void KItemListController::setAutoActivationDelay(int delay)
{
m_autoActivationTimer->setInterval(delay);
return m_autoActivationTimer->interval();
}
-void KItemListController::setSingleClickActivation(bool singleClick)
+void KItemListController::setSingleClickActivationEnforced(bool singleClick)
{
- m_singleClickActivation = singleClick;
+ m_singleClickActivationEnforced = singleClick;
}
-bool KItemListController::singleClickActivation() const
+bool KItemListController::singleClickActivationEnforced() const
{
- return m_singleClickActivation;
+ return m_singleClickActivationEnforced;
}
bool KItemListController::showEvent(QShowEvent* 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);
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;
+ const int expandedParentsCount = m_model->expandedParentsCount(index);
+ if (expandedParentsCount == 0) {
+ --index;
+ } else {
+ // Go to the parent of the current item.
+ do {
+ --index;
+ } while (index > 0 && m_model->expandedParentsCount(index) == expandedParentsCount);
+ }
m_keyboardAnchorIndex = index;
m_keyboardAnchorPos = keyboardAnchorPos(index);
}
case Qt::Key_Enter:
case Qt::Key_Return: {
- const QSet<int> selectedItems = m_selectionManager->selectedItems();
+ const KItemSet selectedItems = m_selectionManager->selectedItems();
if (selectedItems.count() >= 2) {
emit itemsActivated(selectedItems);
} else if (selectedItems.count() == 1) {
- emit itemActivated(selectedItems.toList().first());
+ emit itemActivated(selectedItems.first());
} else {
emit itemActivated(index);
}
break;
}
- 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);
- }
- 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();
+ const KItemSet 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();
+ ? currentItemIndex : selectedItems.first();
} else if (selectedItems.count() == 1) {
- index = selectedItems.toList().first();
+ index = selectedItems.first();
}
if (index >= 0) {
break;
}
+ case Qt::Key_Escape:
+ if (m_selectionBehavior != SingleSelection) {
+ m_selectionManager->clearSelection();
+ }
+ m_keyboardManager->cancelSearch();
+ emit escapePressed();
+ break;
+
+ case Qt::Key_Space:
+ if (m_selectionBehavior == MultiSelection) {
+ if (controlPressed) {
+ // Toggle the selection state of the current item.
+ m_selectionManager->endAnchoredSelection();
+ m_selectionManager->setSelected(index, 1, KItemListSelectionManager::Toggle);
+ m_selectionManager->beginAnchoredSelection(index);
+ break;
+ } else {
+ // Select the current item if it is not selected yet.
+ const int current = m_selectionManager->currentItem();
+ if (!m_selectionManager->isSelected(current)) {
+ m_selectionManager->setSelected(current);
+ break;
+ }
+ }
+ }
+ // Fall through to the default case and add the Space to the current search string.
+
default:
m_keyboardManager->addKeys(event->text());
+ // Make sure unconsumed events get propagated up the chain. #302329
+ event->ignore();
return false;
}
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);
}
if (index >= 0) {
m_selectionManager->setCurrentItem(index);
- m_selectionManager->clearSelection();
- m_selectionManager->setSelected(index, 1);
- m_selectionManager->beginAnchoredSelection(index);
+
+ if (m_selectionBehavior != NoSelection) {
+ m_selectionManager->clearSelection();
+ m_selectionManager->setSelected(index, 1);
+ m_selectionManager->beginAnchoredSelection(index);
+ }
+
m_view->scrollToItem(index);
}
}
return;
}
- if (m_model->supportsDropping(index)) {
+ /* m_view->isUnderMouse() fixes a bug in the Folder-View-Panel and in the
+ * Places-Panel.
+ *
+ * Bug: When you drag a file onto a Folder-View-Item or a Places-Item and
+ * then move away before the auto-activation timeout triggers, than the
+ * item still becomes activated/expanded.
+ *
+ * See Bug 293200 and 305783
+ */
+ if (m_model->supportsDropping(index) && m_view->isUnderMouse()) {
if (m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
const bool expanded = m_model->isExpanded(index);
m_model->setExpanded(index, !expanded);
- } else {
+ } else if (m_autoActivationBehavior != ExpansionOnly) {
emit itemActivated(index);
}
}
m_pressedMousePos = transform.map(event->pos());
m_pressedIndex = m_view->itemAt(m_pressedMousePos);
- if (m_pressedIndex >= 0) {
- emit itemPressed(m_pressedIndex, event->button());
+ emit mouseButtonPressed(m_pressedIndex, event->buttons());
+
+ // TODO: Qt5: Replace Qt::XButton1 by Qt::BackButton and Qt::XButton2 by Qt::ForwardButton
+ if (event->buttons() & (Qt::XButton1 | Qt::XButton2)) {
+ // Do not select items when clicking the back/forward buttons, see
+ // https://bugs.kde.org/show_bug.cgi?id=327412.
+ return true;
}
if (m_view->isAboveExpansionToggle(m_pressedIndex, m_pressedMousePos)) {
break;
case MultiSelection:
- if (controlPressed) {
+ if (controlPressed && !shiftPressed) {
m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
m_selectionManager->beginAnchoredSelection(m_pressedIndex);
} else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) {
rubberBand->setStartPosition(startPos);
rubberBand->setEndPosition(startPos);
rubberBand->setActive(true);
- connect(rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandChanged()));
+ connect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
m_view->setAutoScroll(true);
}
return false;
}
- if (m_pressedIndex >= 0) {
- emit itemReleased(m_pressedIndex, event->button());
- }
+ emit mouseButtonReleased(m_pressedIndex, event->buttons());
const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
if (isAboveSelectionToggle) {
KItemListRubberBand* rubberBand = m_view->rubberBand();
if (rubberBand->isActive()) {
- disconnect(rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandChanged()));
+ disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
rubberBand->setActive(false);
m_oldSelection.clear();
m_view->setAutoScroll(false);
} else if (shiftOrControlPressed) {
// The mouse click should only update the selection, not trigger the item
emitItemActivated = false;
- } else if (!m_singleClickActivation) {
+ } else if (!(KGlobalSettings::singleClick() || m_singleClickActivationEnforced)) {
emitItemActivated = false;
}
if (emitItemActivated) {
const QPointF pos = transform.map(event->pos());
const int index = m_view->itemAt(pos);
- bool emitItemActivated = !m_singleClickActivation &&
+ // Expand item if desired - See Bug 295573
+ if (m_mouseDoubleClickAction != ActivateItemOnly) {
+ if (m_view && m_model && m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
+ const bool expanded = m_model->isExpanded(index);
+ m_model->setExpanded(index, !expanded);
+ }
+ }
+
+ bool emitItemActivated = !(KGlobalSettings::singleClick() || m_singleClickActivationEnforced) &&
(event->button() & Qt::LeftButton) &&
index >= 0 && index < m_model->count();
if (emitItemActivated) {
{
Q_UNUSED(event);
Q_UNUSED(transform);
+
+ m_view->setAutoScroll(false);
+ m_view->hideDropIndicator();
+
+ KItemListWidget* widget = hoveredWidget();
+ if (widget) {
+ widget->setHovered(false);
+ emit itemUnhovered(widget->index());
+ }
return false;
}
bool KItemListController::dragMoveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
{
- Q_UNUSED(transform);
if (!m_model || !m_view) {
return false;
}
+ event->acceptProposedAction();
+
KItemListWidget* oldHoveredWidget = hoveredWidget();
const QPointF pos = transform.map(event->pos());
oldHoveredWidget->setHovered(false);
emit itemUnhovered(oldHoveredWidget->index());
}
+ }
- if (newHoveredWidget) {
- const int index = newHoveredWidget->index();
+ if (newHoveredWidget) {
+ bool droppingBetweenItems = false;
+ if (m_model->sortRole().isEmpty()) {
+ // The model supports inserting items between other items.
+ droppingBetweenItems = (m_view->showDropIndicator(pos) >= 0);
+ }
+
+ const int index = newHoveredWidget->index();
+ if (!droppingBetweenItems) {
if (m_model->supportsDropping(index)) {
- newHoveredWidget->setHovered(true);
- }
- emit itemHovered(index);
+ // Something has been dragged on an item.
+ m_view->hideDropIndicator();
+ if (!newHoveredWidget->isHovered()) {
+ newHoveredWidget->setHovered(true);
+ emit itemHovered(index);
+ }
- if (m_autoActivationTimer->interval() >= 0) {
- m_autoActivationTimer->setProperty("index", index);
- m_autoActivationTimer->start();
+ if (!m_autoActivationTimer->isActive() && m_autoActivationTimer->interval() >= 0) {
+ m_autoActivationTimer->setProperty("index", index);
+ m_autoActivationTimer->start();
+ }
+ }
+ } else {
+ m_autoActivationTimer->stop();
+ if (newHoveredWidget && newHoveredWidget->isHovered()) {
+ newHoveredWidget->setHovered(false);
+ emit itemUnhovered(index);
}
}
+ } else {
+ m_view->hideDropIndicator();
}
return false;
bool KItemListController::dropEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
{
- Q_UNUSED(transform)
if (!m_view) {
return false;
}
m_autoActivationTimer->stop();
+ m_view->setAutoScroll(false);
const QPointF pos = transform.map(event->pos());
- const int index = m_view->itemAt(pos);
- emit itemDropEvent(index, event);
+
+ int dropAboveIndex = -1;
+ if (m_model->sortRole().isEmpty()) {
+ // The model supports inserting of items between other items.
+ dropAboveIndex = m_view->showDropIndicator(pos);
+ }
+
+ if (dropAboveIndex >= 0) {
+ // Something has been dropped between two items.
+ m_view->hideDropIndicator();
+ emit aboveItemDropEvent(dropAboveIndex, event);
+ } else {
+ // Something has been dropped on an item or on an empty part of the view.
+ emit itemDropEvent(m_view->itemAt(pos), event);
+ }
+
+ QAccessible::updateAccessibility(view(), 0, QAccessible::DragDropEnd);
return true;
}
if (newHoveredWidget) {
newHoveredWidget->setHovered(true);
+ const QPointF mappedPos = newHoveredWidget->mapFromItem(m_view, pos);
+ newHoveredWidget->setHoverPosition(mappedPos);
emit itemHovered(newHoveredWidget->index());
}
+ } else if (oldHoveredWidget) {
+ const QPointF mappedPos = oldHoveredWidget->mapFromItem(m_view, pos);
+ oldHoveredWidget->setHoverPosition(mappedPos);
}
return false;
}
}
- QSet<int> selectedItems;
+ KItemSet selectedItems;
// Select all visible items that intersect with the rubberband
foreach (const KItemListWidget* widget, m_view->visibleItemListWidgets()) {
// Therefore, the new selection contains:
// 1. All previously selected items which are not inside the rubberband, and
// 2. all items inside the rubberband which have not been selected previously.
- m_selectionManager->setSelectedItems((m_oldSelection - selectedItems) + (selectedItems - m_oldSelection));
+ m_selectionManager->setSelectedItems(m_oldSelection ^ selectedItems);
}
else {
m_selectionManager->setSelectedItems(selectedItems + m_oldSelection);
return;
}
- const QSet<int> selectedItems = m_selectionManager->selectedItems();
+ const KItemSet selectedItems = m_selectionManager->selectedItems();
if (selectedItems.isEmpty()) {
return;
}
const QPixmap pixmap = m_view->createDragPixmap(selectedItems);
drag->setPixmap(pixmap);
+ const QPoint hotSpot(pixmap.width() / 2, 0);
+ drag->setHotSpot(hotSpot);
+
drag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::CopyAction);
+ QAccessible::updateAccessibility(view(), 0, QAccessible::DragDropStart);
}
KItemListWidget* KItemListController::hoveredWidget() const
return 0;
}
-#include "kitemlistcontroller.moc"
+void KItemListController::updateExtendedSelectionRegion()
+{
+ if (m_view) {
+ const bool extend = (m_selectionBehavior != MultiSelection);
+ KItemListStyleOption option = m_view->styleOption();
+ if (option.extendedSelectionRegion != extend) {
+ option.extendedSelectionRegion = extend;
+ m_view->setStyleOption(option);
+ }
+ }
+}
+