, m_selectionManager(new KItemListSelectionManager(this))
, m_keyboardManager(new KItemListKeyboardSearchManager(this))
, m_pressedIndex(std::nullopt)
- , m_pressedMousePos()
+ , m_pressedMouseGlobalPos()
, m_autoActivationTimer(nullptr)
, m_swipeGesture(Qt::CustomGesture)
, m_twoFingerTapGesture(Qt::CustomGesture)
int KItemListController::indexCloseToMousePressedPosition() const
{
+ const QPointF pressedMousePos = m_view->transform().map(m_view->scene()->views().first()->mapFromGlobal(m_pressedMouseGlobalPos.toPoint()));
+
QHashIterator<KItemListWidget *, KItemListGroupHeader *> it(m_view->m_visibleGroups);
while (it.hasNext()) {
it.next();
KItemListGroupHeader *groupHeader = it.value();
- const QPointF mappedToGroup = groupHeader->mapFromItem(nullptr, m_pressedMousePos);
+ const QPointF mappedToGroup = groupHeader->mapFromItem(nullptr, pressedMousePos);
if (groupHeader->contains(mappedToGroup)) {
return it.key()->index();
}
return m_selectionMode;
}
+bool KItemListController::isSearchAsYouTypeActive() const
+{
+ return m_keyboardManager->isSearchAsYouTypeActive();
+}
+
bool KItemListController::keyPressEvent(QKeyEvent *event)
{
int index = m_selectionManager->currentItem();
int key = event->key();
+ const bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
// Handle the expanding/collapsing of items
- if (m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
- if (key == Qt::Key_Right) {
- if (m_model->setExpanded(index, true)) {
- return true;
+ // expand / collapse all selected directories
+ if (m_view->supportsItemExpanding() && m_model->isExpandable(index) && (key == Qt::Key_Right || key == Qt::Key_Left)) {
+ const bool expandOrCollapse = key == Qt::Key_Right ? true : false;
+ bool shouldReturn = m_model->setExpanded(index, expandOrCollapse);
+
+ // edit in reverse to preserve index of the first handled items
+ const auto selectedItems = m_selectionManager->selectedItems();
+ for (auto it = selectedItems.rbegin(); it != selectedItems.rend(); ++it) {
+ shouldReturn |= m_model->setExpanded(*it, expandOrCollapse);
+ if (!shiftPressed) {
+ m_selectionManager->setSelected(*it);
}
- } else if (key == Qt::Key_Left) {
- if (m_model->setExpanded(index, false)) {
- return true;
+ }
+ if (shouldReturn) {
+ // update keyboard anchors
+ if (shiftPressed) {
+ m_keyboardAnchorIndex = selectedItems.count() > 0 ? qMin(index, selectedItems.last()) : index;
+ m_keyboardAnchorPos = keyboardAnchorPos(m_keyboardAnchorIndex);
}
+
+ event->ignore();
+ return true;
}
}
- const bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
const bool controlPressed = event->modifiers() & Qt::ControlModifier;
const bool shiftOrControlPressed = shiftPressed || controlPressed;
const bool navigationPressed = key == Qt::Key_Home || key == Qt::Key_End || key == Qt::Key_PageUp || key == Qt::Key_PageDown || key == Qt::Key_Up
}
}
+ // For right to left languages, exchange right and left arrow keys.
+ if (m_view->layoutDirection() == Qt::RightToLeft) {
+ switch (key) {
+ case Qt::Key_Left:
+ key = Qt::Key_Right;
+ break;
+ case Qt::Key_Right:
+ key = Qt::Key_Left;
+ break;
+ default:
+ break;
+ }
+ }
+
const bool selectSingleItem = m_selectionBehavior != NoSelection && itemCount == 1 && navigationPressed;
if (selectSingleItem) {
case Qt::Key_Up:
updateKeyboardAnchor();
+ if (shiftPressed && !m_selectionManager->isAnchoredSelectionActive() && m_selectionManager->isSelected(index)) {
+ m_selectionManager->beginAnchoredSelection(index);
+ }
index = previousRowIndex(index);
break;
case Qt::Key_Down:
updateKeyboardAnchor();
+ if (shiftPressed && !m_selectionManager->isAnchoredSelectionActive() && m_selectionManager->isSelected(index)) {
+ m_selectionManager->beginAnchoredSelection(index);
+ }
index = nextRowIndex(index);
break;
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 KItemSet selectedItems = m_selectionManager->selectedItems();
- int index = -1;
- if (selectedItems.count() >= 2) {
- const int currentItemIndex = m_selectionManager->currentItem();
- index = selectedItems.contains(currentItemIndex) ? currentItemIndex : selectedItems.first();
- } else if (selectedItems.count() == 1) {
- index = selectedItems.first();
- }
-
- if (index >= 0) {
- const QRectF contextRect = m_view->itemContextRect(index);
- const QPointF pos(m_view->scene()->views().first()->mapToGlobal(contextRect.bottomRight().toPoint()));
- Q_EMIT itemContextMenuRequested(index, pos);
- } else {
- Q_EMIT viewContextMenuRequested(QCursor::pos());
- }
- break;
- }
-
case Qt::Key_Escape:
if (m_selectionMode) {
Q_EMIT selectionModeChangeRequested(false);
m_selectionManager->setSelected(index, 1, KItemListSelectionManager::Toggle);
m_selectionManager->beginAnchoredSelection(index);
break;
- } else if (m_keyboardManager->addKeyBeginsNewSearch()) { // File names shouldn't start with a space,
- // so we can use this press as a keyboard shortcut instead.
- Q_EMIT selectionModeChangeRequested(!m_selectionMode);
- 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;
+ }
}
}
Q_FALLTHROUGH(); // fall through to the default case and add the Space to the current search string.
m_selectionManager->beginAnchoredSelection(index);
}
- m_view->scrollToItem(index);
+ m_view->scrollToItem(index, KItemListView::ViewItemPosition::Beginning);
}
}
bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent *event, const QTransform &transform)
{
m_mousePress = true;
+ m_pressedMouseGlobalPos = event->screenPos();
if (event->source() == Qt::MouseEventSynthesizedByQt && m_isTouchEvent) {
return false;
return false;
}
- m_pressedMousePos = transform.map(event->pos());
- m_pressedIndex = m_view->itemAt(m_pressedMousePos);
+ const QPointF pressedMousePos = transform.map(event->pos());
+ m_pressedIndex = m_view->itemAt(pressedMousePos);
const Qt::MouseButtons buttons = event->buttons();
return false;
}
- const QPointF pos = transform.map(event->pos());
-
if (m_pressedIndex.has_value() && !m_view->rubberBand()->isActive()) {
// Check whether a dragging should be started
if (event->buttons() & Qt::LeftButton) {
- if ((pos - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) {
+ const auto distance = (event->screenPos() - m_pressedMouseGlobalPos).manhattanLength();
+ if (distance >= QApplication::startDragDistance()) {
if (!m_selectionManager->isSelected(m_pressedIndex.value())) {
// Always assure that the dragged item gets selected. Usually this is already
// done on the mouse-press event, but when using the selection-toggle on a
if (m_view->scrollOrientation() == Qt::Vertical) {
endPos.ry() += m_view->scrollOffset();
- if (m_view->itemSize().width() < 0) {
- // Use a special rubberband for views that have only one column and
- // expand the rubberband to use the whole width of the view.
- endPos.setX(m_view->size().width());
- }
} else {
endPos.rx() += m_view->scrollOffset();
}
}
if (event->button() & Qt::RightButton) {
- m_selectionManager->clearSelection();
- if (index.has_value()) {
- m_selectionManager->setSelected(index.value());
- Q_EMIT itemContextMenuRequested(index.value(), event->screenPos());
- } else {
- const QRectF headerBounds = m_view->headerBoundaries();
- if (headerBounds.contains(event->pos())) {
- Q_EMIT headerContextMenuRequested(event->screenPos());
- } else {
- Q_EMIT viewContextMenuRequested(event->screenPos());
- }
- }
- return true;
+ return false;
}
bool emitItemActivated = !(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced)
return false;
}
+bool KItemListController::contextMenuEvent(QContextMenuEvent *event)
+{
+ if (event->reason() == QContextMenuEvent::Keyboard) {
+ // Emit the signal itemContextMenuRequested() if at least one item is selected.
+ // Otherwise the signal viewContextMenuRequested() will be emitted.
+ 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.first();
+ } else if (selectedItems.count() == 1) {
+ index = selectedItems.first();
+ }
+
+ if (index >= 0) {
+ const QRectF contextRect = m_view->itemContextRect(index);
+ const QPointF pos(m_view->scene()->views().first()->mapToGlobal(contextRect.bottomRight().toPoint()));
+ Q_EMIT itemContextMenuRequested(index, pos);
+ } else {
+ Q_EMIT viewContextMenuRequested(event->globalPos());
+ }
+ return true;
+ }
+
+ const auto pos = event->pos();
+ const auto globalPos = event->globalPos();
+
+ if (m_view->headerBoundaries().contains(pos)) {
+ Q_EMIT headerContextMenuRequested(globalPos);
+ return true;
+ }
+
+ const auto pressedItem = m_view->itemAt(pos);
+ // We only open a context menu for the pressed item if it is selected.
+ // That's because the same click might have de-selected the item or because the press was only in the row of the item but not on it.
+ if (pressedItem && m_selectionManager->selectedItems().contains(pressedItem.value())) {
+ // The selection rectangle for an item was clicked
+ Q_EMIT itemContextMenuRequested(m_pressedIndex.value(), globalPos);
+ return true;
+ }
+
+ // Remove any hover highlights so the context menu doesn't look like it applies to a row.
+ const auto widgets = m_view->visibleItemListWidgets();
+ for (KItemListWidget *widget : widgets) {
+ if (widget->isHovered()) {
+ widget->setHovered(false);
+ Q_EMIT itemUnhovered(widget->index());
+ }
+ }
+ Q_EMIT viewContextMenuRequested(globalPos);
+ return true;
+}
+
bool KItemListController::dragEnterEvent(QGraphicsSceneDragDropEvent *event, const QTransform &transform)
{
Q_UNUSED(event)
if (!m_autoActivationTimer->isActive() && m_autoActivationTimer->interval() >= 0) {
m_autoActivationTimer->setProperty("index", index);
m_autoActivationTimer->start();
+ newHoveredWidget->startActivateSoonAnimation(m_autoActivationTimer->remainingTime());
}
} else {
m_autoActivationTimer->stop();
m_view->m_tapAndHoldIndicator->setActive(false);
}
- m_pressedMousePos = transform.map(tap->position());
- m_pressedIndex = m_view->itemAt(m_pressedMousePos);
-
+ const QPointF pressedMousePos = transform.map(tap->position());
+ m_pressedIndex = m_view->itemAt(pressedMousePos);
if (m_dragActionOrRightClick) {
m_dragActionOrRightClick = false;
} else {
- onPress(tap->hotSpot().toPoint(), tap->position().toPoint(), Qt::NoModifier, Qt::LeftButton);
+ onPress(m_pressedMouseGlobalPos.toPoint(), tap->position().toPoint(), Qt::NoModifier, Qt::LeftButton);
onRelease(transform.map(tap->position()), Qt::NoModifier, Qt::LeftButton, true);
}
m_isTouchEvent = false;
if (m_pinchGestureInProgress) {
return;
}
- m_pressedMousePos = transform.map(event->mapToGraphicsScene(tap->position()));
- m_pressedIndex = m_view->itemAt(m_pressedMousePos);
-
- if (m_pressedIndex.has_value() && !m_selectionManager->isSelected(m_pressedIndex.value())) {
- m_selectionManager->clearSelection();
- m_selectionManager->setSelected(m_pressedIndex.value());
+ const QPointF pressedMousePos = transform.map(event->mapToGraphicsScene(tap->position()));
+ m_pressedIndex = m_view->itemAt(pressedMousePos);
+ if (m_pressedIndex.has_value()) {
+ if (!m_selectionManager->isSelected(m_pressedIndex.value())) {
+ m_selectionManager->clearSelection();
+ m_selectionManager->setSelected(m_pressedIndex.value());
+ }
if (!m_selectionMode) {
Q_EMIT selectionModeChangeRequested(true);
}
- } else if (!m_pressedIndex.has_value()) {
+ } else {
m_selectionManager->clearSelection();
startRubberBand();
}
Q_EMIT scrollerStop();
- m_view->m_tapAndHoldIndicator->setStartPosition(m_pressedMousePos);
+ m_view->m_tapAndHoldIndicator->setStartPosition(pressedMousePos);
m_view->m_tapAndHoldIndicator->setActive(true);
m_dragActionOrRightClick = true;
}
if (twoTap->state() == Qt::GestureStarted) {
- m_pressedMousePos = transform.map(twoTap->pos());
- m_pressedIndex = m_view->itemAt(m_pressedMousePos);
+ const QPointF pressedMousePos = transform.map(twoTap->pos());
+ m_pressedIndex = m_view->itemAt(pressedMousePos);
if (m_pressedIndex.has_value()) {
onPress(twoTap->screenPos().toPoint(), twoTap->pos().toPoint(), Qt::ControlModifier, Qt::LeftButton);
onRelease(transform.map(twoTap->pos()), Qt::ControlModifier, Qt::LeftButton, false);
return mouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent *>(event), QTransform());
case QEvent::GraphicsSceneMouseDoubleClick:
return mouseDoubleClickEvent(static_cast<QGraphicsSceneMouseEvent *>(event), QTransform());
+ case QEvent::ContextMenu:
+ return contextMenuEvent(static_cast<QContextMenuEvent *>(event));
case QEvent::GraphicsSceneWheel:
return wheelEvent(static_cast<QGraphicsSceneWheelEvent *>(event), QTransform());
case QEvent::GraphicsSceneDragEnter:
const QRectF widgetRect = m_view->itemRect(index);
if (widgetRect.intersects(rubberBandRect)) {
+ // Select the full row intersecting with the rubberband rectangle
+ const QRectF selectionRect = widget->selectionRect().translated(widgetRect.topLeft());
const QRectF iconRect = widget->iconRect().translated(widgetRect.topLeft());
- const QRectF textRect = widget->textRect().translated(widgetRect.topLeft());
- if (iconRect.intersects(rubberBandRect) || textRect.intersects(rubberBandRect)) {
+ if (selectionRect.intersects(rubberBandRect) || iconRect.intersects(rubberBandRect)) {
selectedItems.insert(index);
}
}
{
Q_ASSERT(m_view);
+ if (m_view->headerBoundaries().contains(pos)) {
+ return nullptr;
+ }
+
const auto widgets = m_view->visibleItemListWidgets();
for (KItemListWidget *widget : widgets) {
const QPointF mappedPos = widget->mapFromItem(m_view, pos);
{
Q_ASSERT(m_view);
+ if (m_view->headerBoundaries().contains(pos)) {
+ return nullptr;
+ }
+
const auto widgets = m_view->visibleItemListWidgets();
for (KItemListWidget *widget : widgets) {
const QPointF mappedPos = widget->mapFromItem(m_view, pos);
return index;
}
+ const bool leftToRight = m_view->layoutDirection() != Qt::RightToLeft;
+
// Calculate the index of the last column inside the row of the current index
int lastColumnIndex = index;
- while (keyboardAnchorPos(lastColumnIndex + 1) > keyboardAnchorPos(lastColumnIndex)) {
+ while ((leftToRight && keyboardAnchorPos(lastColumnIndex + 1) > keyboardAnchorPos(lastColumnIndex))
+ || (!leftToRight && keyboardAnchorPos(lastColumnIndex + 1) < keyboardAnchorPos(lastColumnIndex))) {
++lastColumnIndex;
if (lastColumnIndex >= maxIndex) {
return index;
int nextRowIndex = lastColumnIndex + 1;
int searchIndex = nextRowIndex;
qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(nextRowIndex));
- while (searchIndex < maxIndex && keyboardAnchorPos(searchIndex + 1) > keyboardAnchorPos(searchIndex)) {
+ while (searchIndex < maxIndex
+ && ((leftToRight && keyboardAnchorPos(searchIndex + 1) > keyboardAnchorPos(searchIndex))
+ || (!leftToRight && keyboardAnchorPos(searchIndex + 1) < keyboardAnchorPos(searchIndex)))) {
++searchIndex;
const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
if (searchDiff < minDiff) {
return index;
}
+ const bool leftToRight = m_view->layoutDirection() != Qt::RightToLeft;
+
// Calculate the index of the first column inside the row of the current index
int firstColumnIndex = index;
- while (keyboardAnchorPos(firstColumnIndex - 1) < keyboardAnchorPos(firstColumnIndex)) {
+ while ((leftToRight && keyboardAnchorPos(firstColumnIndex - 1) < keyboardAnchorPos(firstColumnIndex))
+ || (!leftToRight && keyboardAnchorPos(firstColumnIndex - 1) > keyboardAnchorPos(firstColumnIndex))) {
--firstColumnIndex;
if (firstColumnIndex <= 0) {
return index;
int previousRowIndex = firstColumnIndex - 1;
int searchIndex = previousRowIndex;
qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(previousRowIndex));
- while (searchIndex > 0 && keyboardAnchorPos(searchIndex - 1) < keyboardAnchorPos(searchIndex)) {
+ while (searchIndex > 0
+ && ((leftToRight && keyboardAnchorPos(searchIndex - 1) < keyboardAnchorPos(searchIndex))
+ || (!leftToRight && keyboardAnchorPos(searchIndex - 1) > keyboardAnchorPos(searchIndex)))) {
--searchIndex;
const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
if (searchDiff < minDiff) {
return true;
}
- if (m_view->isAboveExpansionToggle(m_pressedIndex.value_or(-1), m_pressedMousePos)) {
+ const QPointF pressedMousePos = m_view->transform().map(pos);
+
+ if (m_view->isAboveExpansionToggle(m_pressedIndex.value_or(-1), pressedMousePos)) {
m_selectionManager->endAnchoredSelection();
m_selectionManager->setCurrentItem(m_pressedIndex.value());
m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
return true;
}
- m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex.value_or(-1), m_pressedMousePos);
+ m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex.value_or(-1), pressedMousePos);
if (m_selectionTogglePressed) {
m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
// The previous anchored selection has been finished already in
// clear the selection in mouseReleaseEvent(), unless the items are dragged.
m_clearSelectionIfItemsAreNotDragged = true;
- if (m_selectionManager->selectedItems().count() == 1 && m_view->isAboveText(m_pressedIndex.value_or(-1), m_pressedMousePos)) {
+ if (m_selectionManager->selectedItems().count() == 1 && m_view->isAboveText(m_pressedIndex.value_or(-1), pressedMousePos)) {
Q_EMIT selectedItemTextPressed(m_pressedIndex.value_or(-1));
}
}
}
if (rightClick) {
- // Do header hit check and short circuit before commencing any state changing effects
- if (m_view->headerBoundaries().contains(pos)) {
- Q_EMIT headerContextMenuRequested(screenPos);
- return true;
- }
-
// Stop rubber band from persisting after right-clicks
KItemListRubberBand *rubberBand = m_view->rubberBand();
if (rubberBand->isActive()) {
if (m_pressedIndex.has_value()) {
// The hover highlight area of an item is being pressed.
- m_selectionManager->setCurrentItem(m_pressedIndex.value());
const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); // anything outside of row.contains() will be the empty region of the row rect
const bool hitTargetIsRowEmptyRegion = !row->contains(row->mapFromItem(m_view, pos));
// again, when this method returns false, a rubberBand selection is created as the event is not consumed;
bool createRubberBand = (hitTargetIsRowEmptyRegion && m_selectionManager->selectedItems().isEmpty());
if (rightClick && hitTargetIsRowEmptyRegion) {
- // We have a right click outside the icon and text rect but within the hover highlight area
- // but it is unclear if this means that a selection rectangle for an item was clicked or the background of the view.
- if (m_selectionManager->selectedItems().contains(m_pressedIndex.value())) {
- // The selection rectangle for an item was clicked
- Q_EMIT itemContextMenuRequested(m_pressedIndex.value(), screenPos);
- } else {
- row->setHovered(false); // Removes the hover highlight so the context menu doesn't look like it applies to the row.
- Q_EMIT viewContextMenuRequested(screenPos);
- }
+ // We have a right click outside the icon and text rect but within the hover highlight area.
+ // We don't want items to get selected through this, so we return now.
return true;
}
+ m_selectionManager->setCurrentItem(m_pressedIndex.value());
+
switch (m_selectionBehavior) {
case NoSelection:
break;
break;
}
- if (rightClick) {
- Q_EMIT itemContextMenuRequested(m_pressedIndex.value(), screenPos);
- }
return !createRubberBand;
}
- if (rightClick) {
- // header right click handling would have been done before this so just normal context
- // menu here is fine
- Q_EMIT viewContextMenuRequested(screenPos);
- return true;
- }
-
return false;
}
bool KItemListController::onRelease(const QPointF &pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch)
{
- const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex.value_or(-1), m_pressedMousePos);
+ const QPointF pressedMousePos = pos;
+ const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex.value_or(-1), pressedMousePos);
if (isAboveSelectionToggle) {
m_selectionTogglePressed = false;
return true;
}
}
- m_pressedMousePos = QPointF();
+ m_pressedMouseGlobalPos = QPointF();
m_pressedIndex = std::nullopt;
m_clearSelectionIfItemsAreNotDragged = false;
return false;
void KItemListController::startRubberBand()
{
if (m_selectionBehavior == MultiSelection) {
- QPointF startPos = m_pressedMousePos;
+ QPoint startPos = m_view->transform().map(m_view->scene()->views().first()->mapFromGlobal(m_pressedMouseGlobalPos.toPoint()));
if (m_view->scrollOrientation() == Qt::Vertical) {
startPos.ry() += m_view->scrollOffset();
- if (m_view->itemSize().width() < 0) {
- // Use a special rubberband for views that have only one column and
- // expand the rubberband to use the whole width of the view.
- startPos.setX(0);
- }
} else {
startPos.rx() += m_view->scrollOffset();
}
m_scrollerIsScrolling = false;
}
}
+
+#include "moc_kitemlistcontroller.cpp"