diff --git a/autotests/kfileplacesmodeltest.cpp b/autotests/kfileplacesmodeltest.cpp --- a/autotests/kfileplacesmodeltest.cpp +++ b/autotests/kfileplacesmodeltest.cpp @@ -117,7 +117,7 @@ urls << m_places->url(index).toDisplayString(QUrl::PreferLocalFile); } return urls; -} + } #define CHECK_PLACES_URLS(urls) \ if (placesUrls() != urls) { \ @@ -188,7 +188,7 @@ QString(), QString()); urls = initialListOfUrls(); - urls << QStringLiteral("/foo"); + urls.insert(4, QStringLiteral("/foo")); CHECK_PLACES_URLS(urls); // reparse the bookmark file @@ -203,7 +203,7 @@ // try to remove item - m_places->removePlace(m_places->index(9, 0)); + m_places->removePlace(m_places->index(4, 0)); urls = initialListOfUrls(); CHECK_PLACES_URLS(urls); @@ -316,39 +316,40 @@ KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); KBookmarkGroup root = bookmarkManager->root(); - KBookmark trash = m_places->bookmarkForIndex(m_places->index(3, 0)); - KBookmark before_trash = m_places->bookmarkForIndex(m_places->index(2, 0)); + KBookmark remote = m_places->bookmarkForIndex(m_places->index(1, 0)); + KBookmark before_remote = m_places->bookmarkForIndex(m_places->index(0, 0)); - // Move the trash at the end of the list + // Trying move the remote at the end of the list, should move it to the end of places section instead + // to keep it grouped KBookmark last = root.first(); while (!root.next(last).isNull()) { last = root.next(last); } - root.moveBookmark(trash, last); + root.moveBookmark(remote, last); bookmarkManager->emitChanged(root); QStringList urls; - urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) - << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign") << QStringLiteral("trash:/"); + urls << QDir::homePath() << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") << QStringLiteral("remote:/") + << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); - QCOMPARE(args.at(1).toInt(), 8); - QCOMPARE(args.at(2).toInt(), 8); + QCOMPARE(args.at(1).toInt(), 3); + QCOMPARE(args.at(2).toInt(), 3); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); - QCOMPARE(args.at(1).toInt(), 3); - QCOMPARE(args.at(2).toInt(), 3); + QCOMPARE(args.at(1).toInt(), 1); + QCOMPARE(args.at(2).toInt(), 1); - // Move the trash at the beginning of the list - root.moveBookmark(trash, KBookmark()); + // Move the remote at the beginning of the list + root.moveBookmark(remote, KBookmark()); bookmarkManager->emitChanged(root); urls.clear(); - urls << QStringLiteral("trash:/") << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) + urls << QStringLiteral("remote:/") << QDir::homePath() << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); CHECK_PLACES_URLS(urls); @@ -360,21 +361,21 @@ QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); - QCOMPARE(args.at(1).toInt(), 9); - QCOMPARE(args.at(2).toInt(), 9); + QCOMPARE(args.at(1).toInt(), 4); + QCOMPARE(args.at(2).toInt(), 4); - // Move the trash in the list (at its original place) - root.moveBookmark(trash, before_trash); + // Move the remote in the list (at its original place) + root.moveBookmark(remote, before_remote); bookmarkManager->emitChanged(root); urls.clear(); urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); - QCOMPARE(args.at(1).toInt(), 3); - QCOMPARE(args.at(2).toInt(), 3); + QCOMPARE(args.at(1).toInt(), 1); + QCOMPARE(args.at(2).toInt(), 1); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); @@ -392,51 +393,51 @@ QSignalSpy spy_inserted(m_places, SIGNAL(rowsInserted(QModelIndex,int,int))); QSignalSpy spy_removed(m_places, SIGNAL(rowsRemoved(QModelIndex,int,int))); - // Move the trash at the end of the list + // Move the remote at the end of the places list QModelIndexList indexes; - indexes << m_places->index(3, 0); + indexes << m_places->index(1, 0); QMimeData *mimeData = m_places->mimeData(indexes); - QVERIFY(m_places->dropMimeData(mimeData, Qt::MoveAction, -1, 0, QModelIndex())); + QVERIFY(m_places->dropMimeData(mimeData, Qt::MoveAction, 4, 0, QModelIndex())); QStringList urls; - urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) - << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign") << QStringLiteral("trash:/"); + urls << QDir::homePath() << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") << QStringLiteral("remote:/") + << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 0); QCOMPARE(spy_moved.count(), 1); args = spy_moved.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); - QCOMPARE(args.at(1).toInt(), 3); - QCOMPARE(args.at(2).toInt(), 3); + QCOMPARE(args.at(1).toInt(), 1); + QCOMPARE(args.at(2).toInt(), 1); QCOMPARE(args.at(3).toModelIndex(), QModelIndex()); - QCOMPARE(args.at(4).toInt(), 9); + QCOMPARE(args.at(4).toInt(), 4); - // Move the trash at the beginning of the list + // Move the remote at the beginning of the list indexes.clear(); - indexes << m_places->index(8, 0); + indexes << m_places->index(3, 0); mimeData = m_places->mimeData(indexes); QVERIFY(m_places->dropMimeData(mimeData, Qt::MoveAction, 0, 0, QModelIndex())); urls.clear(); - urls << QStringLiteral("trash:/") << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) + urls << QStringLiteral("remote:/") << QDir::homePath() << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 0); QCOMPARE(spy_removed.count(), 0); QCOMPARE(spy_moved.count(), 1); args = spy_moved.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); - QCOMPARE(args.at(1).toInt(), 8); - QCOMPARE(args.at(2).toInt(), 8); + QCOMPARE(args.at(1).toInt(), 3); + QCOMPARE(args.at(2).toInt(), 3); QCOMPARE(args.at(3).toModelIndex(), QModelIndex()); QCOMPARE(args.at(4).toInt(), 0); - // Move the trash in the list (at its original place) + // Move the remote in the list (at its original place) indexes.clear(); indexes << m_places->index(0, 0); mimeData = m_places->mimeData(indexes); - QVERIFY(m_places->dropMimeData(mimeData, Qt::MoveAction, 4, 0, QModelIndex())); + QVERIFY(m_places->dropMimeData(mimeData, Qt::MoveAction, 2, 0, QModelIndex())); urls.clear(); urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") @@ -450,7 +451,7 @@ QCOMPARE(args.at(1).toInt(), 0); QCOMPARE(args.at(2).toInt(), 0); QCOMPARE(args.at(3).toModelIndex(), QModelIndex()); - QCOMPARE(args.at(4).toInt(), 4); + QCOMPARE(args.at(4).toInt(), 2); // Dropping on an item is not allowed indexes.clear(); @@ -473,21 +474,21 @@ m_places->addPlace(QStringLiteral("Foo"), QUrl::fromLocalFile(QStringLiteral("/home/foo"))); QStringList urls; - urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") - << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign") << QStringLiteral("/home/foo"); + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") << QStringLiteral("/home/foo") + << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); - QCOMPARE(args.at(1).toInt(), 9); - QCOMPARE(args.at(2).toInt(), 9); + QCOMPARE(args.at(1).toInt(), 4); + QCOMPARE(args.at(2).toInt(), 4); QCOMPARE(spy_removed.count(), 0); KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); KBookmarkGroup root = bookmarkManager->root(); KBookmark before_trash = m_places->bookmarkForIndex(m_places->index(2, 0)); - KBookmark foo = m_places->bookmarkForIndex(m_places->index(9, 0)); + KBookmark foo = m_places->bookmarkForIndex(m_places->index(4, 0)); root.moveBookmark(foo, before_trash); bookmarkManager->emitChanged(root); @@ -499,13 +500,13 @@ QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); - QCOMPARE(args.at(1).toInt(), 3); - QCOMPARE(args.at(2).toInt(), 3); + QCOMPARE(args.at(1).toInt(), 4); + QCOMPARE(args.at(2).toInt(), 4); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); - QCOMPARE(args.at(1).toInt(), 10); - QCOMPARE(args.at(2).toInt(), 10); + QCOMPARE(args.at(1).toInt(), 3); + QCOMPARE(args.at(2).toInt(), 3); m_places->editPlace(m_places->index(3, 0), QStringLiteral("Foo"), QUrl::fromLocalFile(QStringLiteral("/mnt/foo"))); @@ -602,24 +603,24 @@ KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); KBookmarkGroup root = bookmarkManager->root(); - KBookmark before_trash = m_places->bookmarkForIndex(m_places->index(2, 0)); + KBookmark before_nfs = m_places->bookmarkForIndex(m_places->index(3, 0)); KBookmark device = root.first(); // The device we'll move is the 7th bookmark for (int i = 0; i < 6; i++) { device = root.next(device); } - root.moveBookmark(device, before_trash); + root.moveBookmark(device, before_nfs); bookmarkManager->emitChanged(root); urls.clear(); - urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("/media/XO-Y4") - << QStringLiteral("trash:/") << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") + << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); - QCOMPARE(args.at(1).toInt(), 3); - QCOMPARE(args.at(2).toInt(), 3); + QCOMPARE(args.at(1).toInt(), 4); + QCOMPARE(args.at(2).toInt(), 4); QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); @@ -636,20 +637,20 @@ QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); - QCOMPARE(args.at(1).toInt(), 3); - QCOMPARE(args.at(2).toInt(), 3); + QCOMPARE(args.at(1).toInt(), 4); + QCOMPARE(args.at(2).toInt(), 4); fakeManager()->call(QStringLiteral("plug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); urls.clear(); - urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("/media/XO-Y4") - << QStringLiteral("trash:/") << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") + << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign"); CHECK_PLACES_URLS(urls); QCOMPARE(spy_inserted.count(), 1); args = spy_inserted.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); - QCOMPARE(args.at(1).toInt(), 3); - QCOMPARE(args.at(2).toInt(), 3); + QCOMPARE(args.at(1).toInt(), 4); + QCOMPARE(args.at(2).toInt(), 4); QCOMPARE(spy_removed.count(), 0); KBookmark seventh = root.first(); @@ -671,8 +672,8 @@ QCOMPARE(spy_removed.count(), 1); args = spy_removed.takeFirst(); QCOMPARE(args.at(0).toModelIndex(), QModelIndex()); - QCOMPARE(args.at(1).toInt(), 3); - QCOMPARE(args.at(2).toInt(), 3); + QCOMPARE(args.at(1).toInt(), 4); + QCOMPARE(args.at(2).toInt(), 4); } void KFilePlacesModelTest::testDeviceSetupTeardown() diff --git a/src/filewidgets/kfileplacesitem.cpp b/src/filewidgets/kfileplacesitem.cpp --- a/src/filewidgets/kfileplacesitem.cpp +++ b/src/filewidgets/kfileplacesitem.cpp @@ -108,6 +108,25 @@ } else { m_text = bookmark.text(); } + + const GroupType type = groupType(); + switch (type) { + case PlacesType: + m_groupName = i18nc("@item", "Places"); + break; + case RecentlySavedType: + m_groupName = i18nc("@item", "Recently Saved"); + break; + case SearchForType: + m_groupName = i18nc("@item", "Search For"); + break; + case DevicesType: + m_groupName = i18nc("@item", "Devices"); + break; + default: + Q_UNREACHABLE(); + break; + } } Solid::Device KFilePlacesItem::device() const @@ -131,15 +150,38 @@ QVariant KFilePlacesItem::data(int role) const { - QVariant returnData; - - if (role != KFilePlacesModel::HiddenRole && role != Qt::BackgroundRole && isDevice()) { - returnData = deviceData(role); + if (role == KFilePlacesModel::GroupRole) { + return QVariant(m_groupName); + } else if (role != KFilePlacesModel::HiddenRole && + role != Qt::BackgroundRole && isDevice()) { + return deviceData(role); } else { - returnData = bookmarkData(role); + return bookmarkData(role); + } +} + +KFilePlacesItem::GroupType KFilePlacesItem::groupType() const +{ + if (!isDevice()) { + const QString protocol = bookmark().url().scheme(); + if (protocol == QLatin1String("timeline")) { + return RecentlySavedType; + } + + if (protocol.contains(QLatin1String("search"))) { + return SearchForType; + } + + if (protocol == QLatin1String("bluetooth") || + protocol == QLatin1String("obexftp") || + protocol == QLatin1String("kdeconnect")) { + return DevicesType; + } + + return PlacesType; } - return returnData; + return DevicesType; } QVariant KFilePlacesItem::bookmarkData(int role) const diff --git a/src/filewidgets/kfileplacesitem_p.h b/src/filewidgets/kfileplacesitem_p.h --- a/src/filewidgets/kfileplacesitem_p.h +++ b/src/filewidgets/kfileplacesitem_p.h @@ -41,6 +41,14 @@ { Q_OBJECT public: + enum GroupType + { + PlacesType = 0, + RecentlySavedType = 1, + SearchForType = 2, + DevicesType = 3 + }; + KFilePlacesItem(KBookmarkManager *manager, const QString &address, const QString &udi = QString()); @@ -53,6 +61,7 @@ void setBookmark(const KBookmark &bookmark); Solid::Device device() const; QVariant data(int role) const; + GroupType groupType() const; static KBookmark createBookmark(KBookmarkManager *manager, const QString &label, @@ -94,6 +103,7 @@ mutable QPointer m_mtp; QString m_iconPath; QStringList m_emblems; + QString m_groupName; }; #endif diff --git a/src/filewidgets/kfileplacesmodel.h b/src/filewidgets/kfileplacesmodel.h --- a/src/filewidgets/kfileplacesmodel.h +++ b/src/filewidgets/kfileplacesmodel.h @@ -47,7 +47,8 @@ HiddenRole = 0x0741CAAC, SetupNeededRole = 0x059A935D, FixedDeviceRole = 0x332896C1, - CapacityBarRecommendedRole = 0x1548C5C4 + CapacityBarRecommendedRole = 0x1548C5C4, + GroupRole = 0x0a5b64ee }; KFilePlacesModel(QObject *parent = nullptr); diff --git a/src/filewidgets/kfileplacesmodel.cpp b/src/filewidgets/kfileplacesmodel.cpp --- a/src/filewidgets/kfileplacesmodel.cpp +++ b/src/filewidgets/kfileplacesmodel.cpp @@ -470,6 +470,12 @@ } } + // return a sorted list based on groups + qStableSort(items.begin(), items.end(), + [](KFilePlacesItem *itemA, KFilePlacesItem *itemB) { + return (itemA->groupType() < itemB->groupType()); + }); + return items; } diff --git a/src/filewidgets/kfileplacesview.h b/src/filewidgets/kfileplacesview.h --- a/src/filewidgets/kfileplacesview.h +++ b/src/filewidgets/kfileplacesview.h @@ -78,6 +78,8 @@ void dragMoveEvent(QDragMoveEvent *event) Q_DECL_OVERRIDE; void dropEvent(QDropEvent *event) Q_DECL_OVERRIDE; void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; + void startDrag(Qt::DropActions supportedActions) Q_DECL_OVERRIDE; + void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; protected Q_SLOTS: void rowsInserted(const QModelIndex &parent, int start, int end) Q_DECL_OVERRIDE; diff --git a/src/filewidgets/kfileplacesview.cpp b/src/filewidgets/kfileplacesview.cpp --- a/src/filewidgets/kfileplacesview.cpp +++ b/src/filewidgets/kfileplacesview.cpp @@ -84,7 +84,24 @@ qreal contentsOpacity(const QModelIndex &index) const; + bool pointIsHeaderArea(const QPoint &pos); + + void startDrag(); + + int sectionHeaderHeight() const; + private: + QString groupNameFromIndex(const QModelIndex &index) const; + QModelIndex previousVisibleIndex(const QModelIndex &index) const; + bool indexIsSectionHeader(const QModelIndex &index) const; + void drawSectionHeader(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + QColor textColor(const QStyleOption &option) const; + QColor baseColor(const QStyleOption &option) const; + QColor mixedColor(const QColor &c1, const QColor &c2, int c1Percent) const; + KFilePlacesView *m_view; int m_iconSize; @@ -97,6 +114,7 @@ qreal m_disappearingOpacity; bool m_showHoverIndication; + mutable bool m_dragStarted; QMap m_timeLineMap; QMap m_timeLineInverseMap; @@ -110,7 +128,8 @@ m_appearingOpacity(0.0), m_disappearingIconSize(0), m_disappearingOpacity(0.0), - m_showHoverIndication(true) + m_showHoverIndication(true), + m_dragStarted(false) { } @@ -128,26 +147,48 @@ iconSize = m_disappearingIconSize; } - const KFilePlacesModel *filePlacesModel = static_cast(index.model()); - Solid::Device device = filePlacesModel->deviceForIndex(index); + int height = option.fontMetrics.height() / 2 + qMax(iconSize, option.fontMetrics.height()); + + if (indexIsSectionHeader(index)) { + height += sectionHeaderHeight(); + } - return QSize(option.rect.width(), option.fontMetrics.height() / 2 + qMax(iconSize, option.fontMetrics.height())); + return QSize(option.rect.width(), height); } void KFilePlacesViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { painter->save(); + QStyleOptionViewItem opt = option; + + // draw header when necessary + if (indexIsSectionHeader(index)) { + // If we are drawing the floating element used by drag/drop, do not draw the header + if (!m_dragStarted) { + drawSectionHeader(painter, opt, index); + } + + const int headerHeight = sectionHeaderHeight(); + const int headerSpace = (headerHeight / 2) + qMax(2, m_view->spacing()); + painter->translate(0, headerSpace); + opt.rect.translate(0, headerSpace); + opt.rect.setHeight(opt.rect.height() - headerHeight); + } + + m_dragStarted = false; + + // draw item if (m_appearingItems.contains(index)) { painter->setOpacity(m_appearingOpacity); } else if (m_disappearingItems.contains(index)) { painter->setOpacity(m_disappearingOpacity); } - QStyleOptionViewItem opt = option; if (!m_showHoverIndication) { opt.state &= ~QStyle::State_MouseOver; } + QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter); const KFilePlacesModel *placesModel = static_cast(index.model()); @@ -307,6 +348,121 @@ return 0; } +bool KFilePlacesViewDelegate::pointIsHeaderArea(const QPoint &pos) +{ + // we only accept drag events starting from item body, ignore drag request from header + QModelIndex index = m_view->indexAt(pos); + if (!index.isValid()) { + return false; + } + + if (indexIsSectionHeader(index)) { + const QRect vRect = m_view->visualRect(index); + const int delegateY = pos.y() - vRect.y(); + if (delegateY <= sectionHeaderHeight()) { + return true; + } + } + return false; +} + +void KFilePlacesViewDelegate::startDrag() +{ + m_dragStarted = true; +} + +QString KFilePlacesViewDelegate::groupNameFromIndex(const QModelIndex &index) const +{ + if (index.isValid()) { + return index.data(KFilePlacesModel::GroupRole).toString(); + } else { + return QString(); + } +} + +QModelIndex KFilePlacesViewDelegate::previousVisibleIndex(const QModelIndex &index) const +{ + if (index.row() == 0) { + return QModelIndex(); + } + + const QAbstractItemModel *model = index.model(); + QModelIndex prevIndex = model->index(index.row() - 1, index.column(), index.parent()); + + while (m_view->isRowHidden(prevIndex.row())) { + if (prevIndex.row() == 0) { + return QModelIndex(); + } + prevIndex = model->index(prevIndex.row() - 1, index.column(), index.parent()); + } + + return prevIndex; +} + +bool KFilePlacesViewDelegate::indexIsSectionHeader(const QModelIndex &index) const +{ + if (m_view->isRowHidden(index.row())) { + return false; + } + + if (index.row() == 0) { + return true; + } + + const auto groupName = groupNameFromIndex(index); + const auto previousGroupName = groupNameFromIndex(previousVisibleIndex(index)); + return groupName != previousGroupName; +} + +void KFilePlacesViewDelegate::drawSectionHeader(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + const QString category = index.data(KFilePlacesModel::GroupRole).toString(); + QRect textRect(option.rect); + textRect.setLeft(textRect.left() + 3); + textRect.setY(textRect.y() + qMax(2, m_view->spacing())); + textRect.setHeight(sectionHeaderHeight()); + + painter->save(); + + // based on dolphoin colors + const QColor c1 = textColor(option); + const QColor c2 = baseColor(option); + QColor penColor = mixedColor(c1, c2, 60); + + painter->setPen(penColor); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignBottom, category); + painter->restore(); +} + +QColor KFilePlacesViewDelegate::textColor(const QStyleOption &option) const +{ + const QPalette::ColorGroup group = m_view->isActiveWindow() ? QPalette::Active : QPalette::Inactive; + return option.palette.color(group, QPalette::WindowText); +} + +QColor KFilePlacesViewDelegate::baseColor(const QStyleOption &option) const +{ + const QPalette::ColorGroup group = m_view->isActiveWindow() ? QPalette::Active : QPalette::Inactive; + return option.palette.color(group, QPalette::Window); +} + +QColor KFilePlacesViewDelegate::mixedColor(const QColor& c1, const QColor& c2, int c1Percent) const +{ + Q_ASSERT(c1Percent >= 0 && c1Percent <= 100); + + const int c2Percent = 100 - c1Percent; + return QColor((c1.red() * c1Percent + c2.red() * c2Percent) / 100, + (c1.green() * c1Percent + c2.green() * c2Percent) / 100, + (c1.blue() * c1Percent + c2.blue() * c2Percent) / 100); +} + +int KFilePlacesViewDelegate::sectionHeaderHeight() const +{ + return QApplication::fontMetrics().height() + + (qMax(2, m_view->spacing()) * 2); +} + + class KFilePlacesView::Private { public: @@ -337,6 +493,7 @@ bool insertBelow(const QRect &itemRect, const QPoint &pos) const; int insertIndicatorHeight(int itemHeight) const; void fadeCapacityBar(const QModelIndex &index, FadeType fadeType); + int sectionsCount() const; void _k_placeClicked(const QModelIndex &index); void _k_placeEntered(const QModelIndex &index); @@ -577,7 +734,8 @@ QAction *add = nullptr; QAction *mainSeparator = nullptr; - if (index.isValid()) { + const bool clickOverHeader = delegate->pointIsHeaderArea(event->pos()); + if (!clickOverHeader && index.isValid()) { if (!placesModel->isDevice(index)) { if (placesModel->url(index).toString() == QLatin1String("trash:/")) { emptyTrash = menu.addAction(QIcon::fromTheme(QStringLiteral("trash-empty")), i18nc("@action:inmenu", "Empty Trash")); @@ -628,7 +786,7 @@ } QAction *remove = nullptr; - if (index.isValid() && !placesModel->isDevice(index)) { + if (!clickOverHeader && index.isValid() && !placesModel->isDevice(index)) { remove = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("&Remove Entry '%1'", label)); } @@ -835,6 +993,26 @@ } } +void KFilePlacesView::startDrag(Qt::DropActions supportedActions) +{ + KFilePlacesViewDelegate *delegate = static_cast(itemDelegate()); + + delegate->startDrag(); + QListView::startDrag(supportedActions); +} + +void KFilePlacesView::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + KFilePlacesViewDelegate *delegate = static_cast(itemDelegate()); + // does not accept drags from section header area + if (delegate->pointIsHeaderArea(event->pos())) { + return; + } + } + QListView::mousePressEvent(event); +} + void KFilePlacesView::setModel(QAbstractItemModel *model) { QListView::setModel(model); @@ -968,7 +1146,10 @@ const int margin = q->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, q) + 1; const int maxWidth = q->viewport()->width() - textWidth - 4 * margin - 1; - const int maxHeight = ((q->height() - (fm.height() / 2) * rowCount) / rowCount) - 1; + + const int totalItemsHeight = (fm.height() / 2) * rowCount; + const int totalSectionsHeight = delegate->sectionHeaderHeight() * sectionsCount(); + const int maxHeight = ((q->height() - totalSectionsHeight - totalItemsHeight) / rowCount) - 1; int size = qMin(maxHeight, maxWidth); @@ -1070,6 +1251,26 @@ timeLine->start(); } +int KFilePlacesView::Private::sectionsCount() const +{ + int count = 0; + QString prevSection; + const int rowCount = q->model()->rowCount(); + + for(int i = 0; i < rowCount; i++) { + if (!q->isRowHidden(i)) { + const QModelIndex index = q->model()->index(i, 0); + const QString sectionName = index.data(KFilePlacesModel::GroupRole).toString(); + if (prevSection != sectionName) { + prevSection = sectionName; + count++; + } + } + } + + return count; +} + void KFilePlacesView::Private::_k_placeClicked(const QModelIndex &index) { KFilePlacesModel *placesModel = qobject_cast(q->model()); @@ -1203,4 +1404,4 @@ #include "moc_kfileplacesview.cpp" #include "moc_kfileplacesview_p.cpp" -#include "kfileplacesview.moc" \ No newline at end of file +#include "kfileplacesview.moc"