From 3957884bf2e44619a4ed35ba0ffead519988885b Mon Sep 17 00:00:00 2001 From: Peter Penz Date: Mon, 8 Aug 2011 23:41:18 +0200 Subject: [PATCH] Improvements for selections, smooth scrolling, tooltips and info-panel --- src/CMakeLists.txt | 1 + src/kitemviews/kfileitemlistwidget.cpp | 47 ++- src/kitemviews/kfileitemlistwidget.h | 2 + src/kitemviews/kfileitemmodel.cpp | 9 +- src/kitemviews/kitemlistcontainer.cpp | 155 ++++++++-- src/kitemviews/kitemlistcontainer.h | 6 + src/kitemviews/kitemlistcontroller.cpp | 87 +++++- src/kitemviews/kitemlistcontroller.h | 13 + src/kitemviews/kitemlistselectionmanager.cpp | 186 ++++++++++- src/kitemviews/kitemlistselectionmanager.h | 28 +- src/kitemviews/kitemliststyleoption.cpp | 17 +- src/kitemviews/kitemliststyleoption.h | 11 +- src/kitemviews/kitemlistview.cpp | 178 +++++------ src/kitemviews/kitemlistview.h | 16 +- src/kitemviews/kitemlistwidget.cpp | 123 ++++++-- src/kitemviews/kitemlistwidget.h | 15 + src/kitemviews/kitemmodelbase.h | 39 ++- src/settings/dolphin_generalsettings.kcfgc | 4 +- src/settings/dolphinsettings.cpp | 5 +- src/settings/general/behaviorsettingspage.cpp | 2 +- src/tests/CMakeLists.txt | 9 + src/tests/kfileitemlistviewtest.cpp | 14 - src/tests/kfileitemmodeltest.cpp | 63 +++- src/tests/kitemlistselectionmanagertest.cpp | 172 +++++++++++ src/views/dolphinview.cpp | 292 ++++++++---------- src/views/dolphinview.h | 39 +-- src/views/tooltips/tooltipmanager.cpp | 115 +++---- src/views/tooltips/tooltipmanager.h | 24 +- 28 files changed, 1164 insertions(+), 508 deletions(-) create mode 100644 src/tests/kitemlistselectionmanagertest.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 314c6cb280..31f5fa4904 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -154,6 +154,7 @@ kde4_add_kcfg_files(dolphin_SRCS panels/information/dolphin_informationpanelsettings.kcfgc settings/dolphin_compactmodesettings.kcfgc settings/dolphin_detailsmodesettings.kcfgc + settings/dolphin_generalsettings.kcfgc settings/dolphin_iconsmodesettings.kcfgc search/dolphin_searchsettings.kcfgc settings/dolphin_versioncontrolsettings.kcfgc diff --git a/src/kitemviews/kfileitemlistwidget.cpp b/src/kitemviews/kfileitemlistwidget.cpp index 91c0cb597e..a5251439fd 100644 --- a/src/kitemviews/kfileitemlistwidget.cpp +++ b/src/kitemviews/kfileitemlistwidget.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -91,6 +92,7 @@ void KFileItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsIte const_cast(this)->updateCache(); } + // Draw expansion toggle '>' or 'V' if (m_isDir && !m_expansionArea.isEmpty()) { QStyleOption arrowOption; arrowOption.rect = m_expansionArea.toRect(); @@ -99,10 +101,8 @@ void KFileItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsIte style()->drawPrimitive(arrow, &arrowOption, painter); } - const bool isHovered = (hoverOpacity() > 0.0); - const KItemListStyleOption& itemListStyleOption = styleOption(); - if (isHovered) { + if (isHovered()) { // Blend the unhovered and hovered pixmap if the hovering // animation is ongoing if (hoverOpacity() < 1.0) { @@ -127,11 +127,11 @@ void KFileItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsIte drawPixmap(painter, m_pixmap); } - QFont font(itemListStyleOption.font); - if (itemListStyleOption.state & QStyle::State_HasFocus) { - font.setUnderline(true); + if (isCurrent()) { + drawFocusIndicator(painter); } - painter->setFont(font); + + painter->setFont(itemListStyleOption.font); painter->setPen(itemListStyleOption.palette.text().color()); painter->drawStaticText(m_textPos[Name], m_text[Name]); @@ -244,6 +244,12 @@ void KFileItemListWidget::styleOptionChanged(const KItemListStyleOption& current m_dirtyLayout = true; } +void KFileItemListWidget::hoveredChanged(bool hovered) +{ + Q_UNUSED(hovered); + m_dirtyLayout = true; +} + void KFileItemListWidget::resizeEvent(QGraphicsSceneResizeEvent* event) { KItemListWidget::resizeEvent(event); @@ -374,7 +380,7 @@ void KFileItemListWidget::updatePixmapCache() m_hoverPixmapRect.moveTopLeft(QPointF(x, y)); // Prepare the pixmap that is used when the item gets hovered - if (option.state & QStyle::State_MouseOver) { + if (isHovered()) { m_hoverPixmap = m_pixmap; KIconEffect* effect = KIconLoader::global()->iconEffect(); // In the KIconLoader terminology, active = hover. @@ -681,6 +687,31 @@ void KFileItemListWidget::drawPixmap(QPainter* painter, const QPixmap& pixmap) } } +void KFileItemListWidget::drawFocusIndicator(QPainter* painter) +{ + // Ideally style()->drawPrimitive(QStyle::PE_FrameFocusRect...) + // should be used, but Oxygen only draws indicators within classes + // derived from QAbstractItemView or Q3ListView. As a workaround + // the indicator is drawn manually. Code copied from oxygenstyle.cpp + // Copyright ( C ) 2009-2010 Hugo Pereira Da Costa + // TODO: Clarify with Oxygen maintainers how to proceed with this. + + const KItemListStyleOption& option = styleOption(); + const QPalette palette = option.palette; + const QRect rect = m_textsBoundingRect.toRect().adjusted(0, 0, 0, -1); + + QLinearGradient gradient(rect.bottomLeft(), rect.bottomRight()); + gradient.setColorAt(0.0, Qt::transparent); + gradient.setColorAt(1.0, Qt::transparent); + gradient.setColorAt(0.2, palette.color(QPalette::Text)); + gradient.setColorAt(0.8, palette.color(QPalette::Text)); + + painter->setRenderHint(QPainter::Antialiasing, false); + painter->setPen(QPen(gradient, 1)); + painter->drawLine(rect.bottomLeft(), rect.bottomRight()); + painter->setRenderHint(QPainter::Antialiasing, true); +} + QPixmap KFileItemListWidget::pixmapForIcon(const QString& name, int size) { const KIcon icon(name); diff --git a/src/kitemviews/kfileitemlistwidget.h b/src/kitemviews/kfileitemlistwidget.h index 3ce953106f..1d1b6fd86b 100644 --- a/src/kitemviews/kfileitemlistwidget.h +++ b/src/kitemviews/kfileitemlistwidget.h @@ -57,6 +57,7 @@ protected: virtual void visibleRolesChanged(const QHash& current, const QHash& previous); virtual void visibleRolesSizesChanged(const QHash& current, const QHash& previous); virtual void styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous); + virtual void hoveredChanged(bool hovered); virtual void resizeEvent(QGraphicsSceneResizeEvent* event); private: @@ -85,6 +86,7 @@ private: QString roleText(TextId textId, const QVariant& roleValue) const; void drawPixmap(QPainter* painter, const QPixmap& pixmap); + void drawFocusIndicator(QPainter* painter); static QPixmap pixmapForIcon(const QString& name, int size); static TextId roleTextId(const QByteArray& role); diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index b191abab60..ddc56209b8 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -403,8 +403,9 @@ void KFileItemModel::insertItems(const KFileItemList& items) KItemRangeList itemRanges; int targetIndex = 0; int sourceIndex = 0; - int insertedAtIndex = -1; - int insertedCount = 0; + int insertedAtIndex = -1; // Index for the current item-range + int insertedCount = 0; // Count for the current item-range + int previouslyInsertedCount = 0; // Sum of previously inserted items for all ranges while (sourceIndex < sortedItems.count()) { // Find target index from m_items to insert the current item // in a sorted order @@ -418,7 +419,8 @@ void KFileItemModel::insertItems(const KFileItemList& items) if (targetIndex - previousTargetIndex > 0 && insertedAtIndex >= 0) { itemRanges << KItemRange(insertedAtIndex, insertedCount); - insertedAtIndex = targetIndex; + previouslyInsertedCount += insertedCount; + insertedAtIndex = targetIndex - previouslyInsertedCount; insertedCount = 0; } @@ -431,6 +433,7 @@ void KFileItemModel::insertItems(const KFileItemList& items) if (insertedAtIndex < 0) { insertedAtIndex = targetIndex; + Q_ASSERT(previouslyInsertedCount == 0); } ++targetIndex; ++sourceIndex; diff --git a/src/kitemviews/kitemlistcontainer.cpp b/src/kitemviews/kitemlistcontainer.cpp index 09fb505d64..e247df0c7f 100644 --- a/src/kitemviews/kitemlistcontainer.cpp +++ b/src/kitemviews/kitemlistcontainer.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -56,7 +57,9 @@ public: KItemListContainer::KItemListContainer(KItemListController* controller, QWidget* parent) : QAbstractScrollArea(parent), - m_controller(controller) + m_controller(controller), + m_sliderMovedByUser(false), + m_viewOffsetAnimation(0) { Q_ASSERT(controller); controller->setParent(this); @@ -99,8 +102,86 @@ void KItemListContainer::scrollContentsBy(int dx, int dy) } const qreal currentOffset = view->offset(); - const qreal offsetDiff = (view->scrollOrientation() == Qt::Vertical) ? dy : dx; - view->setOffset(currentOffset - offsetDiff); + qreal offsetDiff = (view->scrollOrientation() == Qt::Vertical) ? dy : dx; + + const bool animRunning = (m_viewOffsetAnimation->state() == QAbstractAnimation::Running); + if (animRunning) { + // Stopping a running animation means skipping the range from the current offset + // until the target offset. To prevent skipping of the range the difference + // is added to the new target offset. + m_viewOffsetAnimation->stop(); + const qreal targetOffset = m_viewOffsetAnimation->endValue().toReal(); + offsetDiff += (currentOffset - targetOffset); + } + + const qreal newOffset = currentOffset - offsetDiff; + + if (m_sliderMovedByUser || animRunning) { + m_viewOffsetAnimation->stop(); + m_viewOffsetAnimation->setStartValue(currentOffset); + m_viewOffsetAnimation->setEndValue(newOffset); + m_viewOffsetAnimation->setEasingCurve(animRunning ? QEasingCurve::OutQuad : QEasingCurve::InOutQuad); + m_viewOffsetAnimation->start(); + } else { + view->setOffset(newOffset); + } +} + +bool KItemListContainer::eventFilter(QObject* obj, QEvent* event) +{ + Q_ASSERT(obj == horizontalScrollBar() || obj == verticalScrollBar()); + + // Check whether the scrollbar has been adjusted by a mouse-event + // triggered by the user and remember this in m_sliderMovedByUser. + // The smooth scrolling will only get active if m_sliderMovedByUser + // is true (see scrollContentsBy()). + const bool scrollVertical = (m_controller->view()->scrollOrientation() == Qt::Vertical); + const bool checkEvent = ( scrollVertical && obj == verticalScrollBar()) || + (!scrollVertical && obj == horizontalScrollBar()); + if (checkEvent) { + switch (event->type()) { + case QEvent::MouseButtonPress: + m_sliderMovedByUser = true; + break; + + case QEvent::MouseButtonRelease: + m_sliderMovedByUser = false; + break; + + case QEvent::Wheel: + wheelEvent(static_cast(event)); + break; + + default: + break; + } + } + + return QAbstractScrollArea::eventFilter(obj, event); +} + +void KItemListContainer::wheelEvent(QWheelEvent* event) +{ + KItemListView* view = m_controller->view(); + if (!view || event->orientation() != view->scrollOrientation()) { + return; + } + + const int numDegrees = event->delta() / 8; + const int numSteps = numDegrees / 15; + + const bool previous = m_sliderMovedByUser; + m_sliderMovedByUser = true; + if (view->scrollOrientation() == Qt::Vertical) { + const int value = verticalScrollBar()->value(); + verticalScrollBar()->setValue(value - numSteps * view->size().height()); + } else { + const int value = horizontalScrollBar()->value(); + horizontalScrollBar()->setValue(value - numSteps * view->size().width()); + } + m_sliderMovedByUser = previous; + + event->accept(); } void KItemListContainer::slotModelChanged(KItemModelBase* current, KItemModelBase* previous) @@ -116,37 +197,57 @@ void KItemListContainer::slotViewChanged(KItemListView* current, KItemListView* scene->removeItem(previous); disconnect(previous, SIGNAL(offsetChanged(int,int)), this, SLOT(updateScrollBars())); disconnect(previous, SIGNAL(maximumOffsetChanged(int,int)), this, SLOT(updateScrollBars())); + m_viewOffsetAnimation->setTargetObject(0); } if (current) { scene->addItem(current); - connect(previous, SIGNAL(offsetChanged(int,int)), this, SLOT(updateScrollBars())); + connect(current, SIGNAL(offsetChanged(int,int)), this, SLOT(updateScrollBars())); connect(current, SIGNAL(maximumOffsetChanged(int,int)), this, SLOT(updateScrollBars())); + m_viewOffsetAnimation->setTargetObject(current); } } void KItemListContainer::updateScrollBars() { - const QSizeF size = m_controller->view()->size(); - - if (m_controller->view()->scrollOrientation() == Qt::Vertical) { - QScrollBar* scrollBar = verticalScrollBar(); - const int value = m_controller->view()->offset(); - const int maximum = qMax(0, int(m_controller->view()->maximumOffset() - size.height())); - scrollBar->setPageStep(size.height()); - scrollBar->setMinimum(0); - scrollBar->setMaximum(maximum); - scrollBar->setValue(value); - horizontalScrollBar()->setMaximum(0); - } else { - QScrollBar* scrollBar = horizontalScrollBar(); - const int value = m_controller->view()->offset(); - const int maximum = qMax(0, int(m_controller->view()->maximumOffset() - size.width())); - scrollBar->setPageStep(size.width()); - scrollBar->setMinimum(0); - scrollBar->setMaximum(maximum); - scrollBar->setValue(value); - verticalScrollBar()->setMaximum(0); + const KItemListView* view = m_controller->view(); + if (!view) { + return; } + + QScrollBar* scrollBar = 0; + int singleStep = 0; + int pageStep = 0; + if (view->scrollOrientation() == Qt::Vertical) { + scrollBar = verticalScrollBar(); + singleStep = view->itemSize().height(); + pageStep = view->size().height(); + } else { + scrollBar = horizontalScrollBar(); + singleStep = view->itemSize().width(); + pageStep = view->size().width(); + } + + const int value = view->offset(); + const int maximum = qMax(0, int(view->maximumOffset() - pageStep)); + if (m_viewOffsetAnimation->state() == QAbstractAnimation::Running) { + if (maximum == scrollBar->maximum()) { + // The value has been changed by the animation, no update + // of the scrollbars is required as their target state will be + // reached with the end of the animation. + return; + } + + // The maximum has been changed which indicates that the content + // of the view has been changed. Stop the animation in any case and + // update the scrollbars immediately. + m_viewOffsetAnimation->stop(); + } + + scrollBar->setSingleStep(singleStep); + scrollBar->setPageStep(pageStep); + scrollBar->setMinimum(0); + scrollBar->setMaximum(maximum); + scrollBar->setValue(value); } void KItemListContainer::updateGeometries() @@ -186,6 +287,12 @@ void KItemListContainer::initialize() QGraphicsView* graphicsView = new KItemListContainerViewport(new QGraphicsScene(this), this); setViewport(graphicsView); + + m_viewOffsetAnimation = new QPropertyAnimation(this, "offset"); + m_viewOffsetAnimation->setDuration(500); + + horizontalScrollBar()->installEventFilter(this); + verticalScrollBar()->installEventFilter(this); } #include "kitemlistcontainer.moc" diff --git a/src/kitemviews/kitemlistcontainer.h b/src/kitemviews/kitemlistcontainer.h index 83044c4f8e..4994eb2499 100644 --- a/src/kitemviews/kitemlistcontainer.h +++ b/src/kitemviews/kitemlistcontainer.h @@ -30,6 +30,7 @@ class KItemListController; class KItemListView; class KItemModelBase; +class QPropertyAnimation; /** * @brief Provides a QWidget based scrolling view for a KItemListController. @@ -51,6 +52,8 @@ protected: virtual void showEvent(QShowEvent* event); virtual void resizeEvent(QResizeEvent* event); virtual void scrollContentsBy(int dx, int dy); + virtual bool eventFilter(QObject* obj, QEvent* event); + virtual void wheelEvent(QWheelEvent* event); private slots: void slotModelChanged(KItemModelBase* current, KItemModelBase* previous); @@ -63,6 +66,9 @@ private: private: KItemListController* m_controller; + + bool m_sliderMovedByUser; + QPropertyAnimation* m_viewOffsetAnimation; }; #endif diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp index 2d72a96de1..dcd62ad52c 100644 --- a/src/kitemviews/kitemlistcontroller.cpp +++ b/src/kitemviews/kitemlistcontroller.cpp @@ -82,14 +82,9 @@ void KItemListController::setView(KItemListView* view) KItemListView* oldView = m_view; m_view = view; - if (oldView) { - disconnect(m_selectionManager, SIGNAL(currentChanged(int,int)), oldView, SLOT(currentChanged(int,int))); - } - if (m_view) { m_view->setController(this); m_view->setModel(m_model); - connect(m_selectionManager, SIGNAL(currentChanged(int,int)), m_view, SLOT(currentChanged(int,int))); } emit viewChanged(m_view, oldView); @@ -139,14 +134,20 @@ bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent* event, const const QPointF pos = transform.map(event->pos()); m_pressedIndex = m_view->itemAt(pos); - m_selectionManager->setCurrentItem(m_pressedIndex); - - // The anchor for the current selection is updated except for Shift+LeftButton events - // (the current selection is continued with the previous anchor in that case). - if (!(event->buttons() & Qt::LeftButton && event->modifiers() & Qt::ShiftModifier)) { + const bool shiftOrControlPressed = event->modifiers() & Qt::ShiftModifier || + event->modifiers() & Qt::ControlModifier; + if (!shiftOrControlPressed) { + m_selectionManager->clearSelection(); m_selectionManager->setAnchorItem(m_pressedIndex); } + if (m_pressedIndex >= 0) { + m_selectionManager->setCurrentItem(m_pressedIndex); + if (!m_view->isAboveExpansionToggle(m_pressedIndex, pos)) { + m_selectionManager->setSelected(m_pressedIndex); + } + } + return false; } @@ -162,15 +163,18 @@ bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, con if (m_view) { const QPointF pos = transform.map(event->pos()); const int index = m_view->itemAt(pos); + const bool shiftOrControlPressed = event->modifiers() & Qt::ShiftModifier || event->modifiers() & Qt::ControlModifier; + if (index >= 0 && index == m_pressedIndex) { + // The release event is done above the same item as the press event bool emitItemClicked = true; if (event->button() & Qt::LeftButton) { if (m_view->isAboveExpansionToggle(index, pos)) { emit itemExpansionToggleClicked(index); emitItemClicked = false; } - else if (event->modifiers() & Qt::ShiftModifier || event->modifiers() & Qt::ControlModifier) { - // The mouse click should only update the selection, not trigger the item. + else if (shiftOrControlPressed) { + // The mouse click should only update the selection, not trigger the item emitItemClicked = false; } } @@ -178,6 +182,8 @@ bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, con if (emitItemClicked) { emit itemClicked(index, event->button()); } + } else if (!shiftOrControlPressed) { + m_selectionManager->clearSelection(); } } @@ -229,8 +235,49 @@ bool KItemListController::hoverEnterEvent(QGraphicsSceneHoverEvent* event, const bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform) { - Q_UNUSED(event); + // The implementation assumes that only one item can get hovered no matter + // whether they overlap or not. + Q_UNUSED(transform); + if (!m_model || !m_view) { + return false; + } + + // Search the previously hovered item that might get unhovered + KItemListWidget* unhoveredWidget = 0; + foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) { + if (widget->isHovered()) { + unhoveredWidget = widget; + break; + } + } + + // Search the currently hovered item + KItemListWidget* hoveredWidget = 0; + foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) { + const QPointF mappedPos = widget->mapFromItem(m_view, event->pos()); + + const bool hovered = widget->contains(mappedPos) && + !widget->expansionToggleRect().contains(mappedPos) && + !widget->selectionToggleRect().contains(mappedPos); + if (hovered) { + hoveredWidget = widget; + break; + } + } + + if (unhoveredWidget != hoveredWidget) { + if (unhoveredWidget) { + unhoveredWidget->setHovered(false); + emit itemUnhovered(unhoveredWidget->index()); + } + + if (hoveredWidget) { + hoveredWidget->setHovered(true); + emit itemHovered(hoveredWidget->index()); + } + } + return false; } @@ -238,6 +285,17 @@ bool KItemListController::hoverLeaveEvent(QGraphicsSceneHoverEvent* event, const { Q_UNUSED(event); Q_UNUSED(transform); + + if (!m_model || !m_view) { + return false; + } + + foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) { + if (widget->isHovered()) { + widget->setHovered(false); + emit itemUnhovered(widget->index()); + } + } return false; } @@ -262,9 +320,6 @@ bool KItemListController::processEvent(QEvent* event, const QTransform& transfor } switch (event->type()) { -// case QEvent::FocusIn: -// case QEvent::FocusOut: -// return focusEvent(static_cast(event)); case QEvent::KeyPress: return keyPressEvent(static_cast(event)); case QEvent::InputMethod: diff --git a/src/kitemviews/kitemlistcontroller.h b/src/kitemviews/kitemlistcontroller.h index 86f2b4ea62..092a7bc621 100644 --- a/src/kitemviews/kitemlistcontroller.h +++ b/src/kitemviews/kitemlistcontroller.h @@ -101,6 +101,19 @@ public: signals: void itemClicked(int index, Qt::MouseButton button); + + /** + * Is emitted if the item with the index \p index gets hovered. + */ + void itemHovered(int index); + + /** + * Is emitted if the item with the index \p index gets unhovered. + * It is assured that the signal itemHovered() for this index + * has been emitted before. + */ + void itemUnhovered(int index); + void itemExpansionToggleClicked(int index); void modelChanged(KItemModelBase* current, KItemModelBase* previous); diff --git a/src/kitemviews/kitemlistselectionmanager.cpp b/src/kitemviews/kitemlistselectionmanager.cpp index 6fe9ed8185..ee8ba929ee 100644 --- a/src/kitemviews/kitemlistselectionmanager.cpp +++ b/src/kitemviews/kitemlistselectionmanager.cpp @@ -23,11 +23,13 @@ #include "kitemlistselectionmanager.h" #include "kitemmodelbase.h" +#include KItemListSelectionManager::KItemListSelectionManager(QObject* parent) : QObject(parent), m_currentItem(-1), m_anchorItem(-1), + m_selectedItems(), m_model(0) { } @@ -39,7 +41,7 @@ KItemListSelectionManager::~KItemListSelectionManager() void KItemListSelectionManager::setCurrentItem(int current) { const int previous = m_currentItem; - if (m_model && current < m_model->count()) { + if (m_model && current >= 0 && current < m_model->count()) { m_currentItem = current; } else { m_currentItem = -1; @@ -55,6 +57,88 @@ int KItemListSelectionManager::currentItem() const return m_currentItem; } +void KItemListSelectionManager::setSelectedItems(const QSet& items) +{ + if (m_selectedItems != items) { + const QSet previous = m_selectedItems; + m_selectedItems = items; + emit selectionChanged(m_selectedItems, previous); + } +} + +QSet KItemListSelectionManager::selectedItems() const +{ + return m_selectedItems; +} + +bool KItemListSelectionManager::hasSelection() const +{ + return !m_selectedItems.isEmpty(); +} + +void KItemListSelectionManager::setSelected(int index, int count, SelectionMode mode) +{ + if (index < 0 || count < 1 || !m_model || index >= m_model->count()) { + return; + } + + const QSet previous = m_selectedItems; + + count = qMin(count, m_model->count() - index); + + const int endIndex = index + count -1; + switch (mode) { + case Select: + for (int i = index; i <= endIndex; ++i) { + m_selectedItems.insert(i); + } + break; + + case Deselect: + for (int i = index; i <= endIndex; ++i) { + m_selectedItems.remove(i); + } + break; + + case Toggle: + for (int i = index; i <= endIndex; ++i) { + if (m_selectedItems.contains(i)) { + m_selectedItems.remove(i); + } else { + m_selectedItems.insert(i); + } + } + break; + + default: + Q_ASSERT(false); + break; + } + + if (m_selectedItems != previous) { + emit selectionChanged(m_selectedItems, previous); + } +} + +void KItemListSelectionManager::clearSelection() +{ + if (!m_selectedItems.isEmpty()) { + const QSet previous = m_selectedItems; + m_selectedItems.clear(); + emit selectionChanged(m_selectedItems, previous); + } +} + +void KItemListSelectionManager::beginAnchoredSelection(int anchor, SelectionMode mode) +{ + Q_UNUSED(anchor); + Q_UNUSED(mode); +} + +void KItemListSelectionManager::endAnchoredSelection() +{ +} + void KItemListSelectionManager::setAnchorItem(int anchor) { const int previous = m_anchorItem; @@ -82,6 +166,106 @@ KItemModelBase* KItemListSelectionManager::model() const void KItemListSelectionManager::setModel(KItemModelBase* model) { m_model = model; + if (model && model->count() > 0) { + m_currentItem = 0; + } +} + +void KItemListSelectionManager::itemsInserted(const KItemRangeList& itemRanges) +{ + // Update the current item + if (m_currentItem < 0) { + setCurrentItem(0); + } else { + int inc = 0; + foreach (const KItemRange& itemRange, itemRanges) { + if (m_currentItem < itemRange.index) { + break; + } + inc += itemRange.count; + } + setCurrentItem(m_currentItem + inc); + } + + // Update the selections + if (!m_selectedItems.isEmpty()) { + const QSet previous = m_selectedItems; + + QSet current; + current.reserve(m_selectedItems.count()); + QSetIterator it(m_selectedItems); + while (it.hasNext()) { + const int index = it.next(); + int inc = 0; + foreach (const KItemRange& itemRange, itemRanges) { + if (index < itemRange.index) { + break; + } + inc += itemRange.count; + } + current.insert(index + inc); + } + + if (current != previous) { + m_selectedItems = current; + emit selectionChanged(current, previous); + } + } +} + +void KItemListSelectionManager::itemsRemoved(const KItemRangeList& itemRanges) +{ + // Update the current item + if (m_currentItem >= 0) { + int currentItem = m_currentItem; + foreach (const KItemRange& itemRange, itemRanges) { + if (currentItem < itemRange.index) { + break; + } + if (currentItem >= itemRange.index + itemRange.count) { + currentItem -= itemRange.count; + } else if (currentItem >= m_model->count()) { + currentItem = m_model->count() - 1; + } + } + setCurrentItem(currentItem); + } + + // Update the selections + if (!m_selectedItems.isEmpty()) { + const QSet previous = m_selectedItems; + + QSet current; + current.reserve(m_selectedItems.count()); + QSetIterator it(m_selectedItems); + while (it.hasNext()) { + int index = it.next(); + int dec = 0; + foreach (const KItemRange& itemRange, itemRanges) { + if (index < itemRange.index) { + break; + } + + if (index < itemRange.index + itemRange.count) { + // The selection is part of the removed range + // and will get deleted + index = -1; + break; + } + + dec += itemRange.count; + } + index -= dec; + if (index >= 0) { + current.insert(index); + } + } + + if (current != previous) { + m_selectedItems = current; + emit selectionChanged(current, previous); + } + } } #include "kitemlistselectionmanager.moc" diff --git a/src/kitemviews/kitemlistselectionmanager.h b/src/kitemviews/kitemlistselectionmanager.h index 5c8e846142..5b329b40ee 100644 --- a/src/kitemviews/kitemlistselectionmanager.h +++ b/src/kitemviews/kitemlistselectionmanager.h @@ -25,10 +25,16 @@ #include +#include + #include +#include class KItemModelBase; +/** + * @brief Allows to select and deselect items of a KItemListView. + */ class LIBDOLPHINPRIVATE_EXPORT KItemListSelectionManager : public QObject { Q_OBJECT @@ -39,13 +45,22 @@ public: Deselect, Toggle }; - + KItemListSelectionManager(QObject* parent = 0); virtual ~KItemListSelectionManager(); void setCurrentItem(int current); int currentItem() const; + void setSelectedItems(const QSet& items); + QSet selectedItems() const; + bool hasSelection() const; + + void setSelected(int index, int count = 1, SelectionMode mode = Select); + void clearSelection(); + + void beginAnchoredSelection(int anchor, SelectionMode mode = Select); + void endAnchoredSelection(); void setAnchorItem(int anchor); int anchorItem() const; @@ -53,17 +68,24 @@ public: signals: void currentChanged(int current, int previous); + void selectionChanged(const QSet& current, const QSet& previous); void anchorChanged(int anchor, int previous); -protected: +private: void setModel(KItemModelBase* model); + void itemsInserted(const KItemRangeList& itemRanges); + void itemsRemoved(const KItemRangeList& itemRanges); private: int m_currentItem; int m_anchorItem; + QSet m_selectedItems; + KItemModelBase* m_model; - friend class KItemListController; + friend class KItemListController; // Calls setModel() + friend class KItemListView; // Calls itemsInserted() and itemsRemoved() + friend class KItemListSelectionManagerTest; }; #endif diff --git a/src/kitemviews/kitemliststyleoption.cpp b/src/kitemviews/kitemliststyleoption.cpp index 261dfc07b9..f26b220bc1 100644 --- a/src/kitemviews/kitemliststyleoption.cpp +++ b/src/kitemviews/kitemliststyleoption.cpp @@ -20,16 +20,23 @@ #include "kitemliststyleoption.h" KItemListStyleOption::KItemListStyleOption() : - QStyleOption(QStyleOption::Version, QStyleOption::SO_CustomBase + 1) + rect(), + font(), + fontMetrics(QFont()), + palette(), + margin(0), + iconSize(0) { } KItemListStyleOption::KItemListStyleOption(const KItemListStyleOption& other) : - QStyleOption(other) + rect(other.rect), + font(other.font), + fontMetrics(other.fontMetrics), + palette(other.palette), + margin(other.margin), + iconSize(other.iconSize) { - margin = other.margin; - iconSize = other.iconSize; - font = other.font; } KItemListStyleOption::~KItemListStyleOption() diff --git a/src/kitemviews/kitemliststyleoption.h b/src/kitemviews/kitemliststyleoption.h index d181204d7e..bafb81d3a9 100644 --- a/src/kitemviews/kitemliststyleoption.h +++ b/src/kitemviews/kitemliststyleoption.h @@ -23,18 +23,23 @@ #include #include -#include +#include +#include +#include -class LIBDOLPHINPRIVATE_EXPORT KItemListStyleOption : public QStyleOption +class LIBDOLPHINPRIVATE_EXPORT KItemListStyleOption { public: KItemListStyleOption(); KItemListStyleOption(const KItemListStyleOption& other); virtual ~KItemListStyleOption(); + QRect rect; + QFont font; + QFontMetrics fontMetrics; + QPalette palette; int margin; int iconSize; - QFont font; }; #endif diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index b89f4d0f55..9c054e1191 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -256,10 +256,6 @@ void KItemListView::setGeometry(const QRectF& rect) int KItemListView::itemAt(const QPointF& pos) const { - if (!m_model) { - return -1; - } - QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); @@ -315,6 +311,11 @@ QHash KItemListView::visibleRoleSizes() const return QHash(); } +QRectF KItemListView::itemBoundingRect(int index) const +{ + return m_layouter->itemBoundingRect(index); +} + void KItemListView::beginTransaction() { ++m_activeTransactions; @@ -412,56 +413,6 @@ void KItemListView::mousePressEvent(QGraphicsSceneMouseEvent* event) event->accept(); } -void KItemListView::hoverMoveEvent(QGraphicsSceneHoverEvent* event) -{ - if (!m_model) { - return; - } - - QHashIterator it(m_visibleItems); - while (it.hasNext()) { - it.next(); - - KItemListWidget* widget = it.value(); - KItemListStyleOption styleOption = widget->styleOption(); - const QPointF mappedPos = widget->mapFromItem(this, event->pos()); - - const bool hovered = widget->contains(mappedPos) && - !widget->expansionToggleRect().contains(mappedPos) && - !widget->selectionToggleRect().contains(mappedPos); - if (hovered) { - if (!(styleOption.state & QStyle::State_MouseOver)) { - styleOption.state |= QStyle::State_MouseOver; - widget->setStyleOption(styleOption); - } - } else if (styleOption.state & QStyle::State_MouseOver) { - styleOption.state &= ~QStyle::State_MouseOver; - widget->setStyleOption(styleOption); - } - } -} - -void KItemListView::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) -{ - Q_UNUSED(event); - - if (!m_model) { - return; - } - - QHashIterator it(m_visibleItems); - while (it.hasNext()) { - it.next(); - - KItemListWidget* widget = it.value(); - KItemListStyleOption styleOption = widget->styleOption(); - if (styleOption.state & QStyle::State_MouseOver) { - styleOption.state &= ~QStyle::State_MouseOver; - widget->setStyleOption(styleOption); - } - } -} - QList KItemListView::visibleItemListWidgets() const { return m_visibleItems.values(); @@ -476,13 +427,18 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) beginTransaction(); } + int previouslyInsertedCount = 0; foreach (const KItemRange& range, itemRanges) { - const int index = range.index; + // range.index is related to the model before anything has been inserted. + // As in each loop the current item-range gets inserted the index must + // be increased by the already previoulsy inserted items. + const int index = range.index + previouslyInsertedCount; const int count = range.count; if (index < 0 || count <= 0) { kWarning() << "Invalid item range (index:" << index << ", count:" << count << ")"; continue; } + previouslyInsertedCount += count; m_sizeHintResolver->itemsInserted(index, count); @@ -524,16 +480,10 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) doLayout(Animation, index, count); update(); } + } - if (m_controller) { - KItemListSelectionManager* selectionManager = m_controller->selectionManager(); - const int current = selectionManager->currentItem(); - if (current < 0) { - selectionManager->setCurrentItem(0); - } else if (current >= index) { - selectionManager->setCurrentItem(current + count); - } - } + if (m_controller) { + m_controller->selectionManager()->itemsInserted(itemRanges); } if (hasMultipleRanges) { @@ -611,14 +561,10 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) doLayout(Animation, index, -count); update(); } + } - /*KItemListSelectionManager* selectionManager = m_controller->selectionManager(); - const int current = selectionManager->currentItem(); - if (count() <= 0) { - selectionManager->setCurrentItem(-1); - } else if (current >= index) { - selectionManager->setCurrentItem(current + count); - }*/ + if (m_controller) { + m_controller->selectionManager()->itemsRemoved(itemRanges); } if (hasMultipleRanges) { @@ -645,24 +591,33 @@ void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, } } -void KItemListView::currentChanged(int current, int previous) +void KItemListView::slotCurrentChanged(int current, int previous) +{ + Q_UNUSED(previous); + + KItemListWidget* previousWidget = m_visibleItems.value(previous, 0); + if (previousWidget) { + Q_ASSERT(previousWidget->isCurrent()); + previousWidget->setCurrent(false); + } + + KItemListWidget* currentWidget = m_visibleItems.value(current, 0); + if (currentWidget) { + Q_ASSERT(!currentWidget->isCurrent()); + currentWidget->setCurrent(true); + } +} + +void KItemListView::slotSelectionChanged(const QSet& current, const QSet& previous) { Q_UNUSED(previous); QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); - + const int index = it.key(); KItemListWidget* widget = it.value(); - KItemListStyleOption styleOption = widget->styleOption(); - if (it.key() == current) { - styleOption.state |= QStyle::State_HasFocus; - widget->setStyleOption(styleOption); - } - else if (styleOption.state & QStyle::State_HasFocus) { - styleOption.state &= ~QStyle::State_HasFocus; - widget->setStyleOption(styleOption); - } + widget->setSelected(current.contains(index)); } } @@ -717,7 +672,20 @@ void KItemListView::setController(KItemListController* controller) { if (m_controller != controller) { KItemListController* previous = m_controller; + if (previous) { + KItemListSelectionManager* selectionManager = previous->selectionManager(); + disconnect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int))); + disconnect(selectionManager, SIGNAL(selectionChanged(QSet,QSet)), this, SLOT(slotSelectionChanged(QSet,QSet))); + } + m_controller = controller; + + if (controller) { + KItemListSelectionManager* selectionManager = controller->selectionManager(); + connect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int))); + connect(selectionManager, SIGNAL(selectionChanged(QSet,QSet)), this, SLOT(slotSelectionChanged(QSet,QSet))); + } + onControllerChanged(controller, previous); } } @@ -918,17 +886,7 @@ void KItemListView::emitOffsetChanges() KItemListWidget* KItemListView::createWidget(int index) { KItemListWidget* widget = m_widgetCreator->create(this); - widget->setVisibleRoles(m_visibleRoles); - widget->setVisibleRolesSizes(m_visibleRolesSizes); - - KItemListStyleOption option = m_styleOption; - if (index == m_controller->selectionManager()->currentItem()) { - option.state |= QStyle::State_HasFocus; - } - widget->setStyleOption(option); - - widget->setIndex(index); - widget->setData(m_model->data(index)); + updateWidgetProperties(widget, index); m_visibleItems.insert(index, widget); if (m_grouped) { @@ -982,17 +940,7 @@ void KItemListView::setWidgetIndex(KItemListWidget* widget, int index) const int oldIndex = widget->index(); m_visibleItems.remove(oldIndex); - widget->setVisibleRoles(m_visibleRoles); - widget->setVisibleRolesSizes(m_visibleRolesSizes); - - KItemListStyleOption option = m_styleOption; - if (index == m_controller->selectionManager()->currentItem()) { - option.state |= QStyle::State_HasFocus; - } - widget->setStyleOption(option); - - widget->setIndex(index); - widget->setData(m_model->data(index)); + updateWidgetProperties(widget, index); m_visibleItems.insert(index, widget); initializeItemListWidget(widget); @@ -1099,6 +1047,28 @@ void KItemListView::applyDynamicItemSize() } } +void KItemListView::updateWidgetProperties(KItemListWidget* widget, int index) +{ + widget->setVisibleRoles(m_visibleRoles); + widget->setVisibleRolesSizes(m_visibleRolesSizes); + widget->setStyleOption(m_styleOption); + + const KItemListSelectionManager* selectionManager = m_controller->selectionManager(); + widget->setCurrent(index == selectionManager->currentItem()); + + if (selectionManager->hasSelection()) { + const QSet selectedItems = selectionManager->selectedItems(); + widget->setSelected(selectedItems.contains(index)); + } else { + widget->setSelected(false); + } + + widget->setHovered(false); + + widget->setIndex(index); + widget->setData(m_model->data(index)); +} + KItemListCreatorBase::~KItemListCreatorBase() { qDeleteAll(m_recycleableWidgets); diff --git a/src/kitemviews/kitemlistview.h b/src/kitemviews/kitemlistview.h index 81ad52ac3c..23181db6e8 100644 --- a/src/kitemviews/kitemlistview.h +++ b/src/kitemviews/kitemlistview.h @@ -30,6 +30,7 @@ #include #include #include +#include class KItemListController; class KItemListWidgetCreatorBase; @@ -62,6 +63,8 @@ class LIBDOLPHINPRIVATE_EXPORT KItemListView : public QGraphicsWidget { Q_OBJECT + Q_PROPERTY(qreal offset READ offset WRITE setOffset) + public: KItemListView(QGraphicsWidget* parent = 0); virtual ~KItemListView(); @@ -132,6 +135,8 @@ public: virtual QSizeF itemSizeHint(int index) const; virtual QHash visibleRoleSizes() const; + QRectF itemBoundingRect(int index) const; + void beginTransaction(); void endTransaction(); bool isTransactionActive() const; @@ -157,8 +162,6 @@ protected: virtual bool event(QEvent* event); virtual void mousePressEvent(QGraphicsSceneMouseEvent* event); - virtual void hoverMoveEvent(QGraphicsSceneHoverEvent* event); - virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent* event); QList visibleItemListWidgets() const; @@ -169,7 +172,8 @@ protected slots: const QSet& roles); private slots: - void currentChanged(int current, int previous); + void slotCurrentChanged(int current, int previous); + void slotSelectionChanged(const QSet& current, const QSet& previous); void slotAnimationFinished(QGraphicsWidget* widget, KItemListViewAnimation::AnimationType type); void slotLayoutTimerFinished(); @@ -229,6 +233,12 @@ private: */ void applyDynamicItemSize(); + /** + * Helper method for createWidget() and setWidgetIndex() to update the properties + * of the itemlist widget. + */ + void updateWidgetProperties(KItemListWidget* widget, int index); + private: bool m_grouped; int m_activeTransactions; // Counter for beginTransaction()/endTransaction() diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp index 3f08d9f7a6..ef4c1f797a 100644 --- a/src/kitemviews/kitemlistwidget.cpp +++ b/src/kitemviews/kitemlistwidget.cpp @@ -30,10 +30,14 @@ #include #include #include +#include KItemListWidget::KItemListWidget(QGraphicsItem* parent) : QGraphicsWidget(parent, 0), m_index(-1), + m_selected(false), + m_current(false), + m_hovered(false), m_data(), m_visibleRoles(), m_visibleRolesSizes(), @@ -90,11 +94,21 @@ QHash KItemListWidget::data() const void KItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option); + + const QRect hoverBounds = hoverBoundingRect().toRect(); + if (m_selected) { + QStyleOptionViewItemV4 viewItemOption; + viewItemOption.initFrom(widget); + viewItemOption.rect = hoverBounds; + viewItemOption.state = QStyle::State_Enabled | QStyle::State_Selected | QStyle::State_Item; + viewItemOption.viewItemPosition = QStyleOptionViewItemV4::OnlyOne; + widget->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &viewItemOption, painter, widget); + } + if (m_hoverOpacity <= 0.0) { return; } - const QRect hoverBounds = hoverBoundingRect().toRect(); if (!m_hoverCache) { m_hoverCache = new QPixmap(hoverBounds.size()); m_hoverCache->fill(Qt::transparent); @@ -104,7 +118,7 @@ void KItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* o QStyleOptionViewItemV4 viewItemOption; viewItemOption.initFrom(widget); viewItemOption.rect = QRect(0, 0, hoverBounds.width(), hoverBounds.height()); - viewItemOption.state = QStyle::State_Enabled | QStyle::State_MouseOver; + viewItemOption.state = QStyle::State_Enabled | QStyle::State_MouseOver | QStyle::State_Item; viewItemOption.viewItemPosition = QStyleOptionViewItemV4::OnlyOne; widget->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &viewItemOption, &pixmapPainter, widget); @@ -143,34 +157,8 @@ QHash KItemListWidget::visibleRolesSizes() const void KItemListWidget::setStyleOption(const KItemListStyleOption& option) { const KItemListStyleOption previous = m_styleOption; - if (m_index >= 0) { - clearCache(); - - const bool wasHovered = (previous.state & QStyle::State_MouseOver); - m_styleOption = option; - const bool isHovered = (m_styleOption.state & QStyle::State_MouseOver); - - if (wasHovered != isHovered) { - // The hovering state has been changed. Assure that a fade-animation - // is done to the new state. - if (!m_hoverAnimation) { - m_hoverAnimation = new QPropertyAnimation(this, "hoverOpacity", this); - m_hoverAnimation->setDuration(200); - } - m_hoverAnimation->stop(); - - if (!wasHovered && isHovered) { - m_hoverAnimation->setEndValue(1.0); - } else { - Q_ASSERT(wasHovered && !isHovered); - m_hoverAnimation->setEndValue(0.0); - } - - m_hoverAnimation->start(); - } - } else { - m_styleOption = option; - } + clearCache(); + m_styleOption = option; styleOptionChanged(option, previous); } @@ -180,6 +168,66 @@ const KItemListStyleOption& KItemListWidget::styleOption() const return m_styleOption; } +void KItemListWidget::setSelected(bool selected) +{ + if (m_selected != selected) { + m_selected = selected; + selectedChanged(selected); + update(); + } +} + +bool KItemListWidget::isSelected() const +{ + return m_selected; +} + +void KItemListWidget::setCurrent(bool current) +{ + if (m_current != current) { + m_current = current; + currentChanged(current); + update(); + } +} + +bool KItemListWidget::isCurrent() const +{ + return m_current; +} + +void KItemListWidget::setHovered(bool hovered) +{ + if (hovered == m_hovered) { + return; + } + + m_hovered = hovered; + + if (!m_hoverAnimation) { + m_hoverAnimation = new QPropertyAnimation(this, "hoverOpacity", this); + m_hoverAnimation->setDuration(200); + } + m_hoverAnimation->stop(); + + if (hovered) { + m_hoverAnimation->setEndValue(1.0); + } else { + m_hoverAnimation->setEndValue(0.0); + } + + m_hoverAnimation->start(); + + hoveredChanged(hovered); + + update(); +} + +bool KItemListWidget::isHovered() const +{ + return m_hovered; +} + bool KItemListWidget::contains(const QPointF& point) const { return hoverBoundingRect().contains(point) || @@ -234,6 +282,21 @@ void KItemListWidget::styleOptionChanged(const KItemListStyleOption& current, update(); } +void KItemListWidget::currentChanged(bool current) +{ + Q_UNUSED(current); +} + +void KItemListWidget::selectedChanged(bool selected) +{ + Q_UNUSED(selected); +} + +void KItemListWidget::hoveredChanged(bool hovered) +{ + Q_UNUSED(hovered); +} + void KItemListWidget::resizeEvent(QGraphicsSceneResizeEvent* event) { QGraphicsWidget::resizeEvent(event); diff --git a/src/kitemviews/kitemlistwidget.h b/src/kitemviews/kitemlistwidget.h index eb2ebf4558..d655042b8c 100644 --- a/src/kitemviews/kitemlistwidget.h +++ b/src/kitemviews/kitemlistwidget.h @@ -72,6 +72,15 @@ public: void setStyleOption(const KItemListStyleOption& option); const KItemListStyleOption& styleOption() const; + void setSelected(bool selected); + bool isSelected() const; + + void setCurrent(bool current); + bool isCurrent() const; + + void setHovered(bool hovered); + bool isHovered() const; + /** * @return True if \a point is inside KItemListWidget::hoverBoundingRect(), * KItemListWidget::selectionToggleRect() or KItemListWidget::expansionToggleRect(). @@ -104,6 +113,9 @@ protected: virtual void visibleRolesChanged(const QHash& current, const QHash& previous); virtual void visibleRolesSizesChanged(const QHash& current, const QHash& previous); virtual void styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous); + virtual void currentChanged(bool current); + virtual void selectedChanged(bool selected); + virtual void hoveredChanged(bool hovered); virtual void resizeEvent(QGraphicsSceneResizeEvent* event); /** @@ -120,6 +132,9 @@ private: Q_PROPERTY(qreal hoverOpacity READ hoverOpacity WRITE setHoverOpacity) int m_index; + bool m_selected; + bool m_current; + bool m_hovered; QHash m_data; QHash m_visibleRoles; QHash m_visibleRolesSizes; diff --git a/src/kitemviews/kitemmodelbase.h b/src/kitemviews/kitemmodelbase.h index 4eb96c8fd8..44135d2f49 100644 --- a/src/kitemviews/kitemmodelbase.h +++ b/src/kitemviews/kitemmodelbase.h @@ -106,9 +106,46 @@ public: virtual QString roleDescription(const QByteArray& role) const; signals: + /** + * Is emitted if one or more items have been inserted. Each item-range consists + * of: + * - an index where items have been inserted + * - the number of inserted items. + * The index of each item-range represents the index of the model + * before the items have been inserted. + * + * For the item-ranges it is assured that: + * - They don't overlap + * - The index of item-range n is smaller than the index of item-range n + 1. + */ void itemsInserted(const KItemRangeList& itemRanges); + + /** + * Is emitted if one or more items have been removed. Each item-range consists + * of: + * - an index where items have been inserted + * - the number of inserted items. + * The index of each item-range represents the index of the model + * before the items have been removed. + * + * For the item-ranges it is assured that: + * - They don't overlap + * - The index of item-range n is smaller than the index of item-range n + 1. + */ void itemsRemoved(const KItemRangeList& itemRanges); - void itemsMoved(const KItemRangeList& itemRanges); + + /** + * Is emitted if one ore more items get moved. + * @param itemRanges Item-ranges that get moved to a new position. + * @param movedToIndexes New position of the ranges. + * It is assured that the itemRanges list has the same size as the movedToIndexes list. + * + * For the item-ranges it is assured that: + * - They don't overlap + * - The index of item-range n is smaller than the index of item-range n + 1. + */ + void itemsMoved(const KItemRangeList& itemRanges, const QList movedToIndexes); + void itemsChanged(const KItemRangeList& itemRanges, const QSet& roles); void groupRoleChanged(const QByteArray& current, const QByteArray& previous); diff --git a/src/settings/dolphin_generalsettings.kcfgc b/src/settings/dolphin_generalsettings.kcfgc index 91b1fee468..7090dbce41 100644 --- a/src/settings/dolphin_generalsettings.kcfgc +++ b/src/settings/dolphin_generalsettings.kcfgc @@ -1,4 +1,4 @@ File=dolphin_generalsettings.kcfg -ClassName=GeneralSettings -Singleton=false +ClassName=GeneralSettings +Singleton=yes Mutators=true diff --git a/src/settings/dolphinsettings.cpp b/src/settings/dolphinsettings.cpp index 9fc0cea3d3..aae684201c 100644 --- a/src/settings/dolphinsettings.cpp +++ b/src/settings/dolphinsettings.cpp @@ -48,15 +48,12 @@ void DolphinSettings::save() DolphinSettings::DolphinSettings() { - m_generalSettings = new GeneralSettings(); + m_generalSettings = GeneralSettings::self(); m_placesModel = new KFilePlacesModel(); } DolphinSettings::~DolphinSettings() { - delete m_generalSettings; - m_generalSettings = 0; - delete m_placesModel; m_placesModel = 0; } diff --git a/src/settings/general/behaviorsettingspage.cpp b/src/settings/general/behaviorsettingspage.cpp index 814304801c..8a5b070ce2 100644 --- a/src/settings/general/behaviorsettingspage.cpp +++ b/src/settings/general/behaviorsettingspage.cpp @@ -122,7 +122,7 @@ void BehaviorSettingsPage::applySettings() const bool useGlobalProps = m_globalProps->isChecked(); - GeneralSettings* settings = DolphinSettings::instance().generalSettings(); + GeneralSettings* settings = GeneralSettings::self(); settings->setGlobalViewProps(useGlobalProps); if (useGlobalProps) { diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 5038ee8d7d..b35dc3b8b8 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -1,6 +1,15 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BUILD_DIR}/.. ${KDE4_INCLUDES} ) +# KItemListSelectionManagerTest +set(kitemlistselectionmanagertest_SRCS + kitemlistselectionmanagertest.cpp + ../kitemviews/kitemlistselectionmanager.cpp + ../kitemviews/kitemmodelbase.cpp +) +kde4_add_unit_test(kitemlistselectionmanagertest TEST ${kitemlistselectionmanagertest_SRCS}) +target_link_libraries(kitemlistselectionmanagertest dolphinprivate ${KDE4_KIO_LIBS} ${QT_QTTEST_LIBRARY}) + # KFileItemListViewTest set(kfileitemlistviewtest_SRCS kfileitemlistviewtest.cpp diff --git a/src/tests/kfileitemlistviewtest.cpp b/src/tests/kfileitemlistviewtest.cpp index 1b9e1b3126..a7493d17f9 100644 --- a/src/tests/kfileitemlistviewtest.cpp +++ b/src/tests/kfileitemlistviewtest.cpp @@ -38,8 +38,6 @@ private slots: void init(); void cleanup(); - void testFeffi(); - private: KFileItemListView* m_listView; KFileItemModel* m_model; @@ -83,18 +81,6 @@ void KFileItemListViewTest::cleanup() m_testDir = 0; } -void KFileItemListViewTest::testFeffi() -{ - QStringList files; - files << "a.txt" << "b.txt" << "c.txt"; - m_testDir->createFiles(files); - - m_dirLister->openUrl(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - - QCOMPARE(m_model->count(), 3); -} - QTEST_KDEMAIN(KFileItemListViewTest, GUI) #include "kfileitemlistviewtest.moc" diff --git a/src/tests/kfileitemmodeltest.cpp b/src/tests/kfileitemmodeltest.cpp index 82fc3fbc0f..74706d49ae 100644 --- a/src/tests/kfileitemmodeltest.cpp +++ b/src/tests/kfileitemmodeltest.cpp @@ -24,9 +24,11 @@ #include "testdir.h" namespace { - const int DefaultTimeout = 2000; + const int DefaultTimeout = 5000; }; +Q_DECLARE_METATYPE(KItemRangeList) + class KFileItemModelTest : public QObject { Q_OBJECT @@ -39,7 +41,8 @@ private slots: void testDefaultSortRole(); void testDefaultGroupRole(); void testNewItems(); - void testInsertingItems(); + void testModelConsistencyWhenInsertingItems(); + void testItemRangeConsistencyWhenInsertingItems(); void testExpansionLevelsCompare_data(); void testExpansionLevelsCompare(); @@ -120,9 +123,9 @@ void KFileItemModelTest::testNewItems() QVERIFY(isModelConsistent()); } -void KFileItemModelTest::testInsertingItems() +void KFileItemModelTest::testModelConsistencyWhenInsertingItems() { - // QSKIP("Temporary disabled", SkipSingle); + QSKIP("Temporary disabled", SkipSingle); // KFileItemModel prevents that inserting a punch of items sequentially // results in an itemsInserted()-signal for each item. Instead internally @@ -164,6 +167,58 @@ void KFileItemModelTest::testInsertingItems() QCOMPARE(m_model->count(), 201); } +void KFileItemModelTest::testItemRangeConsistencyWhenInsertingItems() +{ + QStringList files; + files << "B" << "E" << "G"; + m_testDir->createFiles(files); + + // Due to inserting the 3 items one item-range with index == 0 and + // count == 3 must be given + QSignalSpy spy1(m_model, SIGNAL(itemsInserted(KItemRangeList))); + m_dirLister->openUrl(m_testDir->url()); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + + QCOMPARE(spy1.count(), 1); + QList arguments = spy1.takeFirst(); + KItemRangeList itemRangeList = arguments.at(0).value(); + QCOMPARE(itemRangeList.count(), 1); + KItemRange itemRange = itemRangeList.first(); + QCOMPARE(itemRange.index, 0); + QCOMPARE(itemRange.count, 3); + + // The indexes of the item-ranges must always be related to the model before + // the items have been inserted. Having: + // 0 1 2 + // B E G + // and inserting A, C, D, F the resulting model will be: + // 0 1 2 3 4 5 6 + // A B C D E F G + // and the item-ranges must be: + // index: 0, count: 1 for A + // index: 1, count: 2 for B, C + // index: 2, count: 1 for G + + files.clear(); + files << "A" << "C" << "D" << "F"; + m_testDir->createFiles(files); + + QSignalSpy spy2(m_model, SIGNAL(itemsInserted(KItemRangeList))); + m_dirLister->updateDirectory(m_testDir->url()); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + + QCOMPARE(spy2.count(), 1); + arguments = spy2.takeFirst(); + itemRangeList = arguments.at(0).value(); + QCOMPARE(itemRangeList.count(), 3); + QCOMPARE(itemRangeList.at(0).index, 0); + QCOMPARE(itemRangeList.at(0).count, 1); + QCOMPARE(itemRangeList.at(1).index, 1); + QCOMPARE(itemRangeList.at(1).count, 2); + QCOMPARE(itemRangeList.at(2).index, 2); + QCOMPARE(itemRangeList.at(2).count, 1); +} + void KFileItemModelTest::testExpansionLevelsCompare_data() { QTest::addColumn("urlA"); diff --git a/src/tests/kitemlistselectionmanagertest.cpp b/src/tests/kitemlistselectionmanagertest.cpp new file mode 100644 index 0000000000..6fdf01b84c --- /dev/null +++ b/src/tests/kitemlistselectionmanagertest.cpp @@ -0,0 +1,172 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include + +#include "kitemviews/kitemmodelbase.h" +#include "kitemviews/kitemlistselectionmanager.h" + +class DummyModel : public KItemModelBase +{ +public: + DummyModel(); + virtual int count() const; + virtual QHash data(int index) const; +}; + +DummyModel::DummyModel() : + KItemModelBase() +{ +} + +int DummyModel::count() const +{ + return 100; +} + +QHash DummyModel::data(int index) const +{ + Q_UNUSED(index); + return QHash(); +} + + + +class KItemListSelectionManagerTest : public QObject +{ + Q_OBJECT + +private slots: + void init(); + void cleanup(); + + void testConstructor(); + + void testSetSelected_data(); + void testSetSelected(); + void testItemsInserted(); + void testItemsRemoved(); + +private: + KItemListSelectionManager* m_selectionManager; +}; + +void KItemListSelectionManagerTest::init() +{ + m_selectionManager = new KItemListSelectionManager(); + m_selectionManager->setModel(new DummyModel()); +} + +void KItemListSelectionManagerTest::cleanup() +{ + delete m_selectionManager->model(); + delete m_selectionManager; + m_selectionManager = 0; +} + +void KItemListSelectionManagerTest::testConstructor() +{ + QVERIFY(!m_selectionManager->hasSelection()); + QCOMPARE(m_selectionManager->selectedItems().count(), 0); + QCOMPARE(m_selectionManager->currentItem(), 0); + QCOMPARE(m_selectionManager->anchorItem(), -1); +} + +void KItemListSelectionManagerTest::testSetSelected_data() +{ + QTest::addColumn("index"); + QTest::addColumn("count"); + QTest::addColumn("expectedSelectionCount"); + + QTest::newRow("Select all") << 0 << 100 << 100; + QTest::newRow("Sub selection 15 items") << 20 << 15 << 15; + QTest::newRow("Sub selection 1 item") << 20 << 1 << 1; + QTest::newRow("Too small index") << -1 << 100 << 0; + QTest::newRow("Too large index") << 100 << 100 << 0; + QTest::newRow("Too large count") << 0 << 100000 << 100; + QTest::newRow("Too small count") << 0 << 0 << 0; +} + +void KItemListSelectionManagerTest::testSetSelected() +{ + QFETCH(int, index); + QFETCH(int, count); + QFETCH(int, expectedSelectionCount); + m_selectionManager->setSelected(index, count); + QCOMPARE(m_selectionManager->selectedItems().count(), expectedSelectionCount); +} + +void KItemListSelectionManagerTest::testItemsInserted() +{ + // Select items 10 to 12 + m_selectionManager->setSelected(10, 3); + QSet selectedItems = m_selectionManager->selectedItems(); + QVERIFY(selectedItems.contains(10)); + QVERIFY(selectedItems.contains(11)); + QVERIFY(selectedItems.contains(12)); + + // Insert items 0 to 4 -> selection must be 15 to 17 + m_selectionManager->itemsInserted(KItemRangeList() << KItemRange(0, 5)); + selectedItems = m_selectionManager->selectedItems(); + QVERIFY(selectedItems.contains(15)); + QVERIFY(selectedItems.contains(16)); + QVERIFY(selectedItems.contains(17)); + + // Insert 3 items between the selections + m_selectionManager->itemsInserted(KItemRangeList() << + KItemRange(15, 1) << + KItemRange(16, 1) << + KItemRange(17, 1)); + selectedItems = m_selectionManager->selectedItems(); + QVERIFY(selectedItems.contains(16)); + QVERIFY(selectedItems.contains(18)); + QVERIFY(selectedItems.contains(20)); +} + +void KItemListSelectionManagerTest::testItemsRemoved() +{ + // Select items 10 to 15 + m_selectionManager->setSelected(10, 6); + QSet selectedItems = m_selectionManager->selectedItems(); + for (int i = 10; i <= 15; ++i) { + QVERIFY(selectedItems.contains(i)); + } + + // Remove items 0 to 4 -> selection must be 5 to 10 + m_selectionManager->itemsRemoved(KItemRangeList() << KItemRange(0, 5)); + selectedItems = m_selectionManager->selectedItems(); + for (int i = 5; i <= 10; ++i) { + QVERIFY(selectedItems.contains(i)); + } + + // Remove the items 6 , 8 and 10 + m_selectionManager->itemsRemoved(KItemRangeList() << + KItemRange(6, 1) << + KItemRange(8, 1) << + KItemRange(10, 1)); + selectedItems = m_selectionManager->selectedItems(); + QCOMPARE(selectedItems.count(), 3); + QVERIFY(selectedItems.contains(5)); + QVERIFY(selectedItems.contains(6)); + QVERIFY(selectedItems.contains(7)); +} + +QTEST_KDEMAIN(KItemListSelectionManagerTest, NoGUI) + +#include "kitemlistselectionmanagertest.moc" diff --git a/src/views/dolphinview.cpp b/src/views/dolphinview.cpp index 95a90edd88..7076094b9e 100644 --- a/src/views/dolphinview.cpp +++ b/src/views/dolphinview.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -57,15 +58,14 @@ #include "dolphinnewfilemenuobserver.h" #include "dolphin_detailsmodesettings.h" #include "dolphin_generalsettings.h" +#include "dolphinitemlistcontainer.h" #include "renamedialog.h" #include "settings/dolphinsettings.h" #include "viewmodecontroller.h" #include "viewproperties.h" +#include "views/tooltips/tooltipmanager.h" #include "zoomlevelinfo.h" -// TODO: -#include "dolphinitemlistcontainer.h" - namespace { const int MaxModeEnum = DolphinView::CompactView; const int MaxSortingEnum = DolphinView::SortByPath; @@ -76,7 +76,6 @@ DolphinView::DolphinView(const KUrl& url, QWidget* parent) : m_active(true), m_tabsForFiles(false), m_assureVisibleCurrentIndex(false), - m_expanderActive(false), m_isFolderWritable(true), m_url(url), m_mode(DolphinView::IconsView), @@ -84,12 +83,12 @@ DolphinView::DolphinView(const KUrl& url, QWidget* parent) : m_topLayout(0), m_dirLister(0), m_container(0), + m_toolTipManager(0), m_selectionChangedTimer(0), - m_activeItemUrl(), + m_currentItemIndex(-1), m_restoredContentsPosition(), m_createdItemUrl(), - m_selectedItems(), - m_newFileNames() + m_selectedItems() { m_topLayout = new QVBoxLayout(this); m_topLayout->setSpacing(0); @@ -167,6 +166,14 @@ DolphinView::DolphinView(const KUrl& url, QWidget* parent) : connect(controller, SIGNAL(itemClicked(int,Qt::MouseButton)), this, SLOT(slotItemClicked(int,Qt::MouseButton))); connect(controller, SIGNAL(itemExpansionToggleClicked(int)), this, SLOT(slotItemExpansionToggleClicked(int))); + connect(controller, SIGNAL(itemHovered(int)), this, SLOT(slotItemHovered(int))); + connect(controller, SIGNAL(itemUnhovered(int)), this, SLOT(slotItemUnhovered(int))); + + KItemListSelectionManager* selectionManager = controller->selectionManager(); + connect(selectionManager, SIGNAL(selectionChanged(QSet,QSet)), + this, SLOT(slotSelectionChanged(QSet,QSet))); + + m_toolTipManager = new ToolTipManager(this); applyViewProperties(); m_topLayout->addWidget(m_container); @@ -211,7 +218,6 @@ void DolphinView::setActive(bool active) // view->setFocus(); //} emit activated(); - emitSelectionChangedSignal(); emit writeStateChanged(m_isFolderWritable); } @@ -261,35 +267,23 @@ KFileItemList DolphinView::items() const KFileItemList DolphinView::selectedItems() const { - return KFileItemList(); -/* KFileItemList itemList; - const QAbstractItemView* view = m_viewAccessor.itemView(); - if (!view) { - return itemList; + const KFileItemModel* model = fileItemModel(); + const KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); + const QSet selectedIndexes = selectionManager->selectedItems(); + + KFileItemList selectedItems; + QSetIterator it(selectedIndexes); + while (it.hasNext()) { + const int index = it.next(); + selectedItems.append(model->fileItem(index)); } - - const QItemSelection selection = m_viewAccessor.proxyModel()->mapSelectionToSource(view->selectionModel()->selection()); - - const QModelIndexList indexList = selection.indexes(); - foreach (const QModelIndex &index, indexList) { - KFileItem item = m_viewAccessor.dirModel()->itemForIndex(index); - if (!item.isNull()) { - itemList.append(item); - } - } - - return itemList;*/ + return selectedItems; } int DolphinView::selectedItemsCount() const { - return 0; - /*const QAbstractItemView* view = m_viewAccessor.itemView(); - if (!view) { - return 0; - } - - return view->selectionModel()->selectedIndexes().count();*/ + const KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); + return selectionManager->selectedItems().count(); } void DolphinView::markUrlsAsSelected(const QList& urls) @@ -509,11 +503,12 @@ void DolphinView::setUrl(const KUrl& url) } emit urlAboutToBeChanged(url); - - const bool hadSelection = hasSelection(); - m_newFileNames.clear(); m_url = url; + if (GeneralSettings::showToolTips()) { + m_toolTipManager->hideToolTip(); + } + // It is important to clear the items from the model before // applying the view properties, otherwise expensive operations // might be done on the existing items although they get cleared @@ -523,36 +518,28 @@ void DolphinView::setUrl(const KUrl& url) loadDirectory(url); emit urlChanged(url); - if (hadSelection || hasSelection()) { - emitSelectionChangedSignal(); - } } void DolphinView::selectAll() { - //m_viewAccessor.itemView()->selectAll(); + KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); + selectionManager->setSelected(0, fileItemModel()->count()); } void DolphinView::invertSelection() { -/* // Implementation note: Using selectionModel->select(selection, QItemSelectionModel::Toggle) does not - // work, as QItemSelectionModel::hasSelection() provides invalid values in this case. This might be a Qt-issue - - // when changing the implementation with an updated Qt-version don't forget to run the Dolphin-unit-tests that - // verify this usecase. - const KFileItemList selItems = selectedItems(); - clearSelection(); + KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); + const QSet selectedItems = selectionManager->selectedItems(); + QSet invertedSelectedItems; - QItemSelection invertedSelection; - foreach (const KFileItem& item, items()) { - if (!selItems.contains(item)) { - const QModelIndex index = m_viewAccessor.proxyModel()->mapFromSource(m_viewAccessor.dirModel()->indexForItem(item)); - invertedSelection.select(index, index); - } - } + const int maxIndex = fileItemModel()->count() - 1; + for (int i = 0; i <= maxIndex; ++i) { + if (!selectedItems.contains(i)) { + invertedSelectedItems.insert(i); + } + } - QItemSelectionModel* selectionModel = m_viewAccessor.itemView()->selectionModel(); - selectionModel->select(invertedSelection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current); - */ + selectionManager->setSelectedItems(invertedSelectedItems); } void DolphinView::clearSelection() @@ -711,23 +698,54 @@ void DolphinView::slotItemClicked(int index, Qt::MouseButton button) emit tabRequested(item.url()); } } else if (button & Qt::RightButton) { - // TODO: attach customActions for the details-view + if (GeneralSettings::showToolTips()) { + m_toolTipManager->hideToolTip(); + } emit requestContextMenu(item, url(), QList()); } } void DolphinView::slotItemExpansionToggleClicked(int index) { + // TODO: When doing a model->setExpanded(false) it should + // be checked here whether the current index is part of the + // closed sub-tree. If this is the case, the current index + // should be adjusted to the parent index. KFileItemModel* model = fileItemModel(); const bool expanded = model->isExpanded(index); model->setExpanded(index, !expanded); } -void DolphinView::slotSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +void DolphinView::slotItemHovered(int index) { - const int count = selectedItemsCount(); - const bool selectionStateChanged = ((count > 0) && (selected.count() == count)) || - ((count == 0) && !deselected.isEmpty()); + const KFileItem item = fileItemModel()->fileItem(index); + + if (GeneralSettings::showToolTips()) { + QRectF itemRect = m_container->controller()->view()->itemBoundingRect(index); + const QPoint pos = m_container->mapToGlobal(itemRect.topLeft().toPoint()); + itemRect.moveTo(pos); + + m_toolTipManager->showToolTip(item, itemRect); + } + + emit requestItemInfo(item); +} + +void DolphinView::slotItemUnhovered(int index) +{ + Q_UNUSED(index); + if (GeneralSettings::showToolTips()) { + m_toolTipManager->hideToolTip(); + } + emit requestItemInfo(KFileItem()); +} + +void DolphinView::slotSelectionChanged(const QSet& current, const QSet& previous) +{ + const int currentCount = current.count(); + const int previousCount = previous.count(); + const bool selectionStateChanged = (currentCount == 0 && previousCount > 0) || + (currentCount > 0 && previousCount == 0); // If nothing has been selected before and something got selected (or if something // was selected before and now nothing is selected) the selectionChangedSignal must @@ -745,19 +763,12 @@ void DolphinView::emitSelectionChangedSignal() void DolphinView::openContextMenu(const QPoint& pos, const QList& customActions) { - Q_UNUSED(pos); KFileItem item; - /*QAbstractItemView* view = m_viewAccessor.itemView(); - QModelIndex index; - if (view) { - index = view->indexAt(pos); + const int index = m_container->controller()->view()->itemAt(pos); + if (index >= 0) { + item = fileItemModel()->fileItem(index); } - if (index.isValid() && (index.column() == DolphinModel::Name)) { - const QModelIndex dolphinModelIndex = m_viewAccessor.proxyModel()->mapToSource(index); - item = m_viewAccessor.dirModel()->itemForIndex(dolphinModelIndex); - }*/ - emit requestContextMenu(item, url(), customActions); } @@ -767,7 +778,7 @@ void DolphinView::dropUrls(const KFileItem& destItem, { Q_UNUSED(destItem); Q_UNUSED(destPath); - addNewFileNames(event->mimeData()); + markPastedUrlsAsSelected(event->mimeData()); //DragAndDropHelper::instance().dropUrls(destItem, destPath, event, this); } @@ -824,8 +835,8 @@ bool DolphinView::itemsExpandable() const void DolphinView::restoreState(QDataStream& stream) { - // Restore the URL of the current item that had the keyboard focus - stream >> m_activeItemUrl; + // Restore the current item that had the keyboard focus + stream >> m_currentItemIndex; // Restore the view position stream >> m_restoredContentsPosition; @@ -845,20 +856,13 @@ void DolphinView::restoreState(QDataStream& stream) void DolphinView::saveState(QDataStream& stream) { - // Save the URL of the current item that has the keyboard focus - - KUrl currentItemUrl; - //if (!currentItem.isNull()) { - // currentItemUrl = currentItem.url(); - //} - - stream << currentItemUrl; + // Save the current item that has the keyboard focus + stream << m_container->controller()->selectionManager()->currentItem(); // Save view position const qreal x = m_container->horizontalScrollBar()->value(); const qreal y = m_container->verticalScrollBar()->value(); stream << QPoint(x, y); - kDebug() << "saving view state" << QPoint(x, y); // Save expanded folders (only relevant for the details view - the set will be empty in other view modes) //stream << m_viewAccessor.expandedUrls(); @@ -866,9 +870,7 @@ void DolphinView::saveState(QDataStream& stream) bool DolphinView::hasSelection() const { - //const QAbstractItemView* view = m_viewAccessor.itemView(); - //return view && view->selectionModel()->hasSelection(); - return false; + return m_container->controller()->selectionManager()->hasSelection(); } KFileItem DolphinView::rootItem() const @@ -907,8 +909,14 @@ void DolphinView::slotRedirection(const KUrl& oldUrl, const KUrl& newUrl) } } -void DolphinView::restoreContentsPosition() +void DolphinView::updateViewState() { + if (m_currentItemIndex >= 0) { + KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); + selectionManager->setCurrentItem(m_currentItemIndex); + m_currentItemIndex =-1; + } + if (!m_restoredContentsPosition.isNull()) { const int x = m_restoredContentsPosition.x(); const int y = m_restoredContentsPosition.y(); @@ -917,13 +925,23 @@ void DolphinView::restoreContentsPosition() m_container->horizontalScrollBar()->setValue(x); m_container->verticalScrollBar()->setValue(y); } -} -/*void DolphinView::slotUrlChangeRequested(const KUrl& url) -{ - m_viewModeController->setUrl(url); - updateWritableState(); -}*/ + if (!m_selectedItems.isEmpty()) { + KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); + QSet selectedItems = selectionManager->selectedItems(); + const KFileItemModel* model = fileItemModel(); + + foreach (const KFileItem& selectedItem, m_selectedItems) { + const int index = model->index(selectedItem); + if (index >= 0) { + selectedItems.insert(index); + } + } + + selectionManager->setSelectedItems(selectedItems); + m_selectedItems.clear(); + } +} void DolphinView::showHoverInformation(const KFileItem& item) { @@ -958,81 +976,15 @@ void DolphinView::slotDirListerStarted(const KUrl& url) void DolphinView::slotDirListerCompleted() { - if (!m_expanderActive) { - slotLoadingCompleted(); - } - - if (!m_newFileNames.isEmpty()) { - // select all newly added items created by a paste operation or - // a drag & drop operation, and clear the previous selection - /*QAbstractItemView* view = m_viewAccessor.itemView(); - if (view) { - view->clearSelection(); - const int rowCount = m_viewAccessor.proxyModel()->rowCount(); - QItemSelection selection; - for (int row = 0; row < rowCount; ++row) { - const QModelIndex proxyIndex = m_viewAccessor.proxyModel()->index(row, 0); - const QModelIndex dirIndex = m_viewAccessor.proxyModel()->mapToSource(proxyIndex); - const KUrl url = m_viewAccessor.dirModel()->itemForIndex(dirIndex).url(); - if (m_newFileNames.contains(url.fileName())) { - selection.merge(QItemSelection(proxyIndex, proxyIndex), QItemSelectionModel::Select); - } - } - view->selectionModel()->select(selection, QItemSelectionModel::Select); - }*/ - - m_newFileNames.clear(); - } - - updateWritableState(); -} - -void DolphinView::slotLoadingCompleted() -{ - m_expanderActive = false; - - if (!m_activeItemUrl.isEmpty()) { - // assure that the current item remains visible - /*const QModelIndex dirIndex = m_viewAccessor.dirModel()->indexForUrl(m_activeItemUrl); - if (dirIndex.isValid()) { - const QModelIndex proxyIndex = m_viewAccessor.proxyModel()->mapFromSource(dirIndex); - QAbstractItemView* view = m_viewAccessor.itemView(); - if (view) { - const bool clearSelection = !hasSelection(); - view->setCurrentIndex(proxyIndex); - if (clearSelection) { - view->clearSelection(); - } - } - m_activeItemUrl.clear(); - }*/ - } - - if (!m_selectedItems.isEmpty()) { - /*const KUrl& baseUrl = url(); - KUrl url; - QItemSelection newSelection; - foreach(const KFileItem& item, m_selectedItems) { - url = item.url().upUrl(); - if (baseUrl.equals(url, KUrl::CompareWithoutTrailingSlash)) { - const QModelIndex index = m_viewAccessor.proxyModel()->mapFromSource(m_viewAccessor.dirModel()->indexForItem(item)); - newSelection.select(index, index); - } - } - QAbstractItemView* view = m_viewAccessor.itemView(); - if (view) { - view->selectionModel()->select(newSelection, - QItemSelectionModel::ClearAndSelect - | QItemSelectionModel::Current); - }*/ - m_selectedItems.clear(); - } - - // Restore the contents position. This has to be done using a Qt::QueuedConnection - // because the view might not be in its final state yet. - QTimer::singleShot(0, this, SLOT(restoreContentsPosition())); + // Update the view-state. This has to be done using a Qt::QueuedConnection + // because the view might not be in its final state yet (the view also + // listens to the completed()-signal from KDirLister and the order of + // of slots is undefined). + QTimer::singleShot(0, this, SLOT(updateViewState())); emit finishedPathLoading(url()); + + updateWritableState(); } void DolphinView::slotRefreshItems() @@ -1172,7 +1124,7 @@ void DolphinView::applyAdditionalInfoListToView() void DolphinView::pasteToUrl(const KUrl& url) { - addNewFileNames(QApplication::clipboard()->mimeData()); + markPastedUrlsAsSelected(QApplication::clipboard()->mimeData()); KonqOperations::doPaste(this, url); } @@ -1213,12 +1165,10 @@ QMimeData* DolphinView::selectionMimeData() const return 0; } -void DolphinView::addNewFileNames(const QMimeData* mimeData) +void DolphinView::markPastedUrlsAsSelected(const QMimeData* mimeData) { const KUrl::List urls = KUrl::List::fromMimeData(mimeData); - foreach (const KUrl& url, urls) { - m_newFileNames.insert(url.fileName()); - } + markUrlsAsSelected(urls); } QItemSelection DolphinView::childrenMatchingPattern(const QModelIndex& parent, const QRegExp& pattern) const @@ -1252,7 +1202,7 @@ void DolphinView::updateWritableState() const bool wasFolderWritable = m_isFolderWritable; m_isFolderWritable = true; - const KFileItem item; // = m_viewAccessor.dirLister()->rootItem(); + const KFileItem item = m_dirLister->rootItem(); if (!item.isNull()) { KFileItemListProperties capabilities(KFileItemList() << item); m_isFolderWritable = capabilities.supportsWriting(); diff --git a/src/views/dolphinview.h b/src/views/dolphinview.h index c9be8a56a1..f49bd3f7ba 100644 --- a/src/views/dolphinview.h +++ b/src/views/dolphinview.h @@ -47,6 +47,7 @@ class KAction; class KActionCollection; class KFileItemModel; class KUrl; +class ToolTipManager; class ViewProperties; class QRegExp; @@ -553,17 +554,18 @@ private slots: void activate(); void slotItemClicked(int index, Qt::MouseButton button); - void slotItemExpansionToggleClicked(int index); + void slotItemHovered(int index); + void slotItemUnhovered(int index); /** * Emits the signal \a selectionChanged() with a small delay. This is - * because getting all file items for the signal can be an expensive + * because getting all file items for the selection can be an expensive * operation. Fast selection changes are collected in this case and * the signal is emitted only after no selection change has been done * within a small delay. */ - void slotSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + void slotSelectionChanged(const QSet& current, const QSet& previous); /** * Is called by emitDelayedSelectionChangedSignal() and emits the @@ -637,12 +639,6 @@ private slots: */ void slotDirListerCompleted(); - /** - * Invoked when the loading of the directory is finished. - * Restores the active item and the scroll position if possible. - */ - void slotLoadingCompleted(); - /** * Is invoked when the KDirLister indicates refreshed items. */ @@ -670,9 +666,10 @@ private slots: void slotRedirection(const KUrl& oldUrl, const KUrl& newUrl); /** - * Restores the contents position, if history information about the old position is available. + * Applies the state that has been restored by restoreViewState() + * to the view. */ - void restoreContentsPosition(); + void updateViewState(); //void slotUrlChangeRequested(const KUrl& url); @@ -717,11 +714,11 @@ private: /** * Is invoked after a paste operation or a drag & drop - * operation and adds the filenames of all URLs from \a mimeData to - * m_newFileNames. This allows to select all newly added - * items in slotDirListerCompleted(). + * operation and URLs from \a mimeData as selected. + * This allows to select all newly pasted + * items in restoreViewState(). */ - void addNewFileNames(const QMimeData* mimeData); + void markPastedUrlsAsSelected(const QMimeData* mimeData); /** * Helper method for DolphinView::setItemSelectionEnabled(): Returns the selection for @@ -742,7 +739,6 @@ private: bool m_active : 1; bool m_tabsForFiles : 1; bool m_assureVisibleCurrentIndex : 1; - bool m_expanderActive : 1; bool m_isFolderWritable : 1; KUrl m_url; @@ -754,20 +750,15 @@ private: DolphinDirLister* m_dirLister; DolphinItemListContainer* m_container; + ToolTipManager* m_toolTipManager; + QTimer* m_selectionChangedTimer; - KUrl m_activeItemUrl; + int m_currentItemIndex; QPoint m_restoredContentsPosition; KUrl m_createdItemUrl; // URL for a new item that got created by the "Create New..." menu KFileItemList m_selectedItems; // this is used for making the View to remember selections after F5 - /** - * Remembers the filenames that have been added by a paste operation - * or a drag & drop operation. Allows to select the items in - * slotDirListerCompleted(). - */ - QSet m_newFileNames; - // For unit tests friend class TestBase; friend class DolphinDetailsViewTest; diff --git a/src/views/tooltips/tooltipmanager.cpp b/src/views/tooltips/tooltipmanager.cpp index 8f18dbde40..df89a882e6 100644 --- a/src/views/tooltips/tooltipmanager.cpp +++ b/src/views/tooltips/tooltipmanager.cpp @@ -22,18 +22,18 @@ #include "filemetadatatooltip.h" #include #include -#include #include #include #include #include #include +#include #include ToolTipManager::ToolTipManager(QWidget* parent) : QObject(parent), - m_view(parent), + m_parentWidget(parent), m_showToolTipTimer(0), m_contentRetrievalTimer(0), m_fileMetaDataToolTip(0), @@ -43,13 +43,6 @@ ToolTipManager::ToolTipManager(QWidget* parent) : m_item(), m_itemRect() { - //m_dolphinModel = static_cast(m_proxyModel->sourceModel()); - //connect(parent, SIGNAL(entered(QModelIndex)), - // this, SLOT(requestToolTip(QModelIndex))); - //connect(parent, SIGNAL(viewportEntered()), - // this, SLOT(hideToolTip())); - - // Initialize timers m_showToolTipTimer = new QTimer(this); m_showToolTipTimer->setSingleShot(true); m_showToolTipTimer->setInterval(500); @@ -61,22 +54,36 @@ ToolTipManager::ToolTipManager(QWidget* parent) : connect(m_contentRetrievalTimer, SIGNAL(timeout()), this, SLOT(startContentRetrieval())); Q_ASSERT(m_contentRetrievalTimer->interval() < m_showToolTipTimer->interval()); - - // When the mousewheel is used, the items don't get a hovered indication - // (Qt-issue #200665). To assure that the tooltip still gets hidden, - // the scrollbars are observed. - /*connect(parent->horizontalScrollBar(), SIGNAL(valueChanged(int)), - this, SLOT(hideToolTip())); - connect(parent->verticalScrollBar(), SIGNAL(valueChanged(int)), - this, SLOT(hideToolTip()));*/ - - Q_ASSERT(m_view); - //m_view->viewport()->installEventFilter(this); - //m_view->installEventFilter(this); } ToolTipManager::~ToolTipManager() { + delete m_fileMetaDataToolTip; + m_fileMetaDataToolTip = 0; +} + +void ToolTipManager::showToolTip(const KFileItem& item, const QRectF& itemRect) +{ + hideToolTip(); + + m_itemRect = itemRect.toRect(); + + const int margin = toolTipMargin(); + m_itemRect.adjust(-margin, -margin, margin, margin); + m_item = item; + + // Only start the retrieving of the content, when the mouse has been over this + // item for 200 milliseconds. This prevents a lot of useless preview jobs and + // meta data retrieval, when passing rapidly over a lot of items. + Q_ASSERT(!m_fileMetaDataToolTip); + m_fileMetaDataToolTip = new FileMetaDataToolTip(m_parentWidget); + connect(m_fileMetaDataToolTip, SIGNAL(metaDataRequestFinished(KFileItemList)), + this, SLOT(slotMetaDataRequestFinished())); + + m_contentRetrievalTimer->start(); + m_showToolTipTimer->start(); + m_toolTipRequested = true; + Q_ASSERT(!m_metaDataRequested); } void ToolTipManager::hideToolTip() @@ -91,56 +98,10 @@ void ToolTipManager::hideToolTip() m_showToolTipTimer->stop(); m_contentRetrievalTimer->stop(); - delete m_fileMetaDataToolTip; - m_fileMetaDataToolTip = 0; -} - - -bool ToolTipManager::eventFilter(QObject* watched, QEvent* event) -{ - /*if (watched == m_view->viewport()) { - switch (event->type()) { - case QEvent::Leave: - case QEvent::MouseButtonPress: - hideToolTip(); - break; - default: - break; - } - } else if ((watched == m_view) && (event->type() == QEvent::KeyPress)) { - hideToolTip(); - }*/ - - return QObject::eventFilter(watched, event); -} - -void ToolTipManager::requestToolTip(const QModelIndex& index) -{ - Q_UNUSED(index); - hideToolTip(); - - // Only request a tooltip for the name column and when no selection or - // drag & drop operation is done (indicated by the left mouse button) - if (!(QApplication::mouseButtons() & Qt::LeftButton)) { - m_itemRect = QRect(); //m_view->visualRect(index); - const QPoint pos; // = m_view->viewport()->mapToGlobal(m_itemRect.topLeft()); - m_itemRect.moveTo(pos); - - //const QModelIndex dirIndex = m_proxyModel->mapToSource(index); - //m_item = m_dolphinModel->itemForIndex(dirIndex); - - // Only start the retrieving of the content, when the mouse has been over this - // item for 200 milliseconds. This prevents a lot of useless preview jobs and - // meta data retrieval, when passing rapidly over a lot of items. - Q_ASSERT(!m_fileMetaDataToolTip); - m_fileMetaDataToolTip = new FileMetaDataToolTip(m_view); - connect(m_fileMetaDataToolTip, SIGNAL(metaDataRequestFinished(KFileItemList)), - this, SLOT(slotMetaDataRequestFinished())); - - m_contentRetrievalTimer->start(); - m_showToolTipTimer->start(); - m_toolTipRequested = true; - Q_ASSERT(!m_metaDataRequested); + if (m_fileMetaDataToolTip) { + m_fileMetaDataToolTip->hide(); + delete m_fileMetaDataToolTip; + m_fileMetaDataToolTip = 0; } } @@ -223,10 +184,6 @@ void ToolTipManager::showToolTip() m_appliedWaitCursor = false; } - if (QApplication::mouseButtons() & Qt::LeftButton) { - return; - } - if (m_fileMetaDataToolTip->preview().isNull() || m_metaDataRequested) { Q_ASSERT(!m_appliedWaitCursor); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); @@ -259,7 +216,7 @@ void ToolTipManager::showToolTip() // It must be assured that: // - the content is fully visible // - the content is not drawn inside m_itemRect - const int margin = 3; + const int margin = toolTipMargin(); const bool hasRoomToLeft = (m_itemRect.left() - size.width() - margin >= screen.left()); const bool hasRoomToRight = (m_itemRect.right() + size.width() + margin <= screen.right()); const bool hasRoomAbove = (m_itemRect.top() - size.height() - margin >= screen.top()); @@ -300,4 +257,10 @@ void ToolTipManager::showToolTip() m_toolTipRequested = false; } +int ToolTipManager::toolTipMargin() const +{ + const int margin = m_parentWidget->style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth); + return qMax(4, margin); +} + #include "tooltipmanager.moc" diff --git a/src/views/tooltips/tooltipmanager.h b/src/views/tooltips/tooltipmanager.h index 11ef3d3aca..b13641109f 100644 --- a/src/views/tooltips/tooltipmanager.h +++ b/src/views/tooltips/tooltipmanager.h @@ -47,19 +47,20 @@ public: explicit ToolTipManager(QWidget* parent); virtual ~ToolTipManager(); -public slots: /** - * Hides the currently shown tooltip. Invoking this method is - * only needed when the tooltip should be hidden although - * an item is hovered. + * Triggers the showing of the tooltip for the item \p item + * where the item has the maximum boundaries of \p itemRect. + * The tooltip manager takes care that the tooltip is shown + * slightly delayed. + */ + void showToolTip(const KFileItem& item, const QRectF& itemRect); + + /** + * Hides the currently shown tooltip. */ void hideToolTip(); -protected: - virtual bool eventFilter(QObject* watched, QEvent* event); - private slots: - void requestToolTip(const QModelIndex& index); void startContentRetrieval(); void setPreviewPix(const KFileItem& item, const QPixmap& pix); void previewFailed(); @@ -67,9 +68,10 @@ private slots: void showToolTip(); private: - QWidget* m_view; - DolphinModel* m_dolphinModel; - DolphinSortFilterProxyModel* m_proxyModel; + int toolTipMargin() const; + +private: + QWidget* m_parentWidget; /// Timeout from requesting a tooltip until the tooltip /// should be shown