diff --git a/src/filterbar/filterbar.cpp b/src/filterbar/filterbar.cpp index caa2c1c92..97f76cff4 100644 --- a/src/filterbar/filterbar.cpp +++ b/src/filterbar/filterbar.cpp @@ -1,141 +1,140 @@ /*************************************************************************** * Copyright (C) 2006-2010 by Peter Penz * * Copyright (C) 2006 by Gregor Kališnik * * Copyright (C) 2012 by Stuart Citrin * * * * 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 "filterbar.h" #include #include #include #include -#include #include #include FilterBar::FilterBar(QWidget* parent) : QWidget(parent) { // Create close button QToolButton *closeButton = new QToolButton(this); closeButton->setAutoRaise(true); closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); closeButton->setToolTip(i18nc("@info:tooltip", "Hide Filter Bar")); connect(closeButton, &QToolButton::clicked, this, &FilterBar::closeRequest); // Create button to lock text when changing folders m_lockButton = new QToolButton(this); m_lockButton->setAutoRaise(true); m_lockButton->setCheckable(true); m_lockButton->setIcon(QIcon::fromTheme(QStringLiteral("object-unlocked"))); m_lockButton->setToolTip(i18nc("@info:tooltip", "Keep Filter When Changing Folders")); connect(m_lockButton, &QToolButton::toggled, this, &FilterBar::slotToggleLockButton); // Create label QLabel* filterLabel = new QLabel(i18nc("@label:textbox", "Filter:"), this); // Create filter editor m_filterInput = new QLineEdit(this); m_filterInput->setLayoutDirection(Qt::LeftToRight); m_filterInput->setClearButtonEnabled(true); connect(m_filterInput, &QLineEdit::textChanged, this, &FilterBar::filterChanged); setFocusProxy(m_filterInput); // Apply layout QHBoxLayout* hLayout = new QHBoxLayout(this); hLayout->setMargin(0); hLayout->addWidget(closeButton); hLayout->addWidget(filterLabel); hLayout->addWidget(m_filterInput); hLayout->addWidget(m_lockButton); filterLabel->setBuddy(m_filterInput); } FilterBar::~FilterBar() { } void FilterBar::closeFilterBar() { hide(); clear(); if (m_lockButton) { m_lockButton->setChecked(false); } } void FilterBar::selectAll() { m_filterInput->selectAll(); } void FilterBar::clear() { m_filterInput->clear(); } void FilterBar::slotUrlChanged() { if (!m_lockButton || !(m_lockButton->isChecked())) { clear(); } } void FilterBar::slotToggleLockButton(bool checked) { if (checked) { m_lockButton->setIcon(QIcon::fromTheme(QStringLiteral("object-locked"))); } else { m_lockButton->setIcon(QIcon::fromTheme(QStringLiteral("object-unlocked"))); clear(); } } void FilterBar::showEvent(QShowEvent* event) { if (!event->spontaneous()) { m_filterInput->setFocus(); } } void FilterBar::keyReleaseEvent(QKeyEvent* event) { QWidget::keyReleaseEvent(event); switch (event->key()) { case Qt::Key_Escape: if (m_filterInput->text().isEmpty()) { emit closeRequest(); } else { m_filterInput->clear(); } break; case Qt::Key_Enter: case Qt::Key_Return: emit focusViewRequest(); break; default: break; } } diff --git a/src/kitemviews/kfileitemlistview.cpp b/src/kitemviews/kfileitemlistview.cpp index 41f8aeade..dd8167d79 100644 --- a/src/kitemviews/kfileitemlistview.cpp +++ b/src/kitemviews/kfileitemlistview.cpp @@ -1,424 +1,423 @@ /*************************************************************************** * 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 "kfileitemlistview.h" #include "kfileitemmodelrolesupdater.h" #include "kfileitemlistwidget.h" #include "kfileitemmodel.h" #include "private/kpixmapmodifier.h" -#include #include #include #include #include #include // #define KFILEITEMLISTVIEW_DEBUG namespace { // If the visible index range changes, KFileItemModelRolesUpdater is not // informed immediatetly, but with a short delay. This ensures that scrolling // always feels smooth and is not interrupted by icon loading (which can be // quite expensive if a disk access is required to determine the final icon). const int ShortInterval = 50; // If the icon size changes, a longer delay is used. This prevents that // the expensive re-generation of all previews is triggered repeatedly when // changing the zoom level. const int LongInterval = 300; } KFileItemListView::KFileItemListView(QGraphicsWidget* parent) : KStandardItemListView(parent), m_modelRolesUpdater(nullptr), m_updateVisibleIndexRangeTimer(nullptr), m_updateIconSizeTimer(nullptr) { setAcceptDrops(true); setScrollOrientation(Qt::Vertical); m_updateVisibleIndexRangeTimer = new QTimer(this); m_updateVisibleIndexRangeTimer->setSingleShot(true); m_updateVisibleIndexRangeTimer->setInterval(ShortInterval); connect(m_updateVisibleIndexRangeTimer, &QTimer::timeout, this, &KFileItemListView::updateVisibleIndexRange); m_updateIconSizeTimer = new QTimer(this); m_updateIconSizeTimer->setSingleShot(true); m_updateIconSizeTimer->setInterval(LongInterval); connect(m_updateIconSizeTimer, &QTimer::timeout, this, &KFileItemListView::updateIconSize); setVisibleRoles({"text"}); } KFileItemListView::~KFileItemListView() { } void KFileItemListView::setPreviewsShown(bool show) { if (!m_modelRolesUpdater) { return; } if (m_modelRolesUpdater->previewsShown() != show) { beginTransaction(); m_modelRolesUpdater->setPreviewsShown(show); onPreviewsShownChanged(show); endTransaction(); } } bool KFileItemListView::previewsShown() const { return m_modelRolesUpdater ? m_modelRolesUpdater->previewsShown() : false; } void KFileItemListView::setEnlargeSmallPreviews(bool enlarge) { if (m_modelRolesUpdater) { m_modelRolesUpdater->setEnlargeSmallPreviews(enlarge); } } bool KFileItemListView::enlargeSmallPreviews() const { return m_modelRolesUpdater ? m_modelRolesUpdater->enlargeSmallPreviews() : false; } void KFileItemListView::setEnabledPlugins(const QStringList& list) { if (m_modelRolesUpdater) { m_modelRolesUpdater->setEnabledPlugins(list); } } QStringList KFileItemListView::enabledPlugins() const { return m_modelRolesUpdater ? m_modelRolesUpdater->enabledPlugins() : QStringList(); } QPixmap KFileItemListView::createDragPixmap(const KItemSet& indexes) const { if (!model()) { return QPixmap(); } const int itemCount = indexes.count(); Q_ASSERT(itemCount > 0); if (itemCount == 1) { return KItemListView::createDragPixmap(indexes); } // If more than one item is dragged, align the items inside a // rectangular grid. The maximum grid size is limited to 5 x 5 items. int xCount; int size; if (itemCount > 16) { xCount = 5; size = KIconLoader::SizeSmall; } else if (itemCount > 9) { xCount = 4; size = KIconLoader::SizeSmallMedium; } else { xCount = 3; size = KIconLoader::SizeMedium; } if (itemCount < xCount) { xCount = itemCount; } int yCount = itemCount / xCount; if (itemCount % xCount != 0) { ++yCount; } if (yCount > xCount) { yCount = xCount; } const qreal dpr = scene()->views()[0]->devicePixelRatio(); // Draw the selected items into the grid cells. QPixmap dragPixmap(QSize(xCount * size + xCount, yCount * size + yCount) * dpr); dragPixmap.setDevicePixelRatio(dpr); dragPixmap.fill(Qt::transparent); QPainter painter(&dragPixmap); int x = 0; int y = 0; for (int index : indexes) { QPixmap pixmap = model()->data(index).value("iconPixmap").value(); if (pixmap.isNull()) { QIcon icon = QIcon::fromTheme(model()->data(index).value("iconName").toString()); pixmap = icon.pixmap(size, size); } else { KPixmapModifier::scale(pixmap, QSize(size, size) * dpr); } painter.drawPixmap(x, y, pixmap); x += size + 1; if (x >= dragPixmap.width()) { x = 0; y += size + 1; } if (y >= dragPixmap.height()) { break; } } return dragPixmap; } KItemListWidgetCreatorBase* KFileItemListView::defaultWidgetCreator() const { return new KItemListWidgetCreator(); } void KFileItemListView::initializeItemListWidget(KItemListWidget* item) { KStandardItemListView::initializeItemListWidget(item); // Make sure that the item has an icon. QHash data = item->data(); if (!data.contains("iconName") && data["iconPixmap"].value().isNull()) { Q_ASSERT(qobject_cast(model())); KFileItemModel* fileItemModel = static_cast(model()); const KFileItem fileItem = fileItemModel->fileItem(item->index()); data.insert("iconName", fileItem.iconName()); item->setData(data, {"iconName"}); } } void KFileItemListView::onPreviewsShownChanged(bool shown) { Q_UNUSED(shown); } void KFileItemListView::onItemLayoutChanged(ItemLayout current, ItemLayout previous) { KStandardItemListView::onItemLayoutChanged(current, previous); triggerVisibleIndexRangeUpdate(); } void KFileItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous) { Q_ASSERT(qobject_cast(current)); KStandardItemListView::onModelChanged(current, previous); delete m_modelRolesUpdater; m_modelRolesUpdater = nullptr; if (current) { m_modelRolesUpdater = new KFileItemModelRolesUpdater(static_cast(current), this); m_modelRolesUpdater->setIconSize(availableIconSize()); applyRolesToModel(); } } void KFileItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous) { KStandardItemListView::onScrollOrientationChanged(current, previous); triggerVisibleIndexRangeUpdate(); } void KFileItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous) { Q_UNUSED(current); Q_UNUSED(previous); triggerVisibleIndexRangeUpdate(); } void KFileItemListView::onScrollOffsetChanged(qreal current, qreal previous) { KStandardItemListView::onScrollOffsetChanged(current, previous); triggerVisibleIndexRangeUpdate(); } void KFileItemListView::onVisibleRolesChanged(const QList& current, const QList& previous) { KStandardItemListView::onVisibleRolesChanged(current, previous); applyRolesToModel(); } void KFileItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous) { KStandardItemListView::onStyleOptionChanged(current, previous); triggerIconSizeUpdate(); } void KFileItemListView::onSupportsItemExpandingChanged(bool supportsExpanding) { applyRolesToModel(); KStandardItemListView::onSupportsItemExpandingChanged(supportsExpanding); triggerVisibleIndexRangeUpdate(); } void KFileItemListView::onTransactionBegin() { if (m_modelRolesUpdater) { m_modelRolesUpdater->setPaused(true); } } void KFileItemListView::onTransactionEnd() { if (!m_modelRolesUpdater) { return; } // Only unpause the model-roles-updater if no timer is active. If one // timer is still active the model-roles-updater will be unpaused later as // soon as the timer has been exceeded. const bool timerActive = m_updateVisibleIndexRangeTimer->isActive() || m_updateIconSizeTimer->isActive(); if (!timerActive) { m_modelRolesUpdater->setPaused(false); } } void KFileItemListView::resizeEvent(QGraphicsSceneResizeEvent* event) { KStandardItemListView::resizeEvent(event); triggerVisibleIndexRangeUpdate(); } void KFileItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) { KStandardItemListView::slotItemsRemoved(itemRanges); } void KFileItemListView::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous) { const QByteArray sortRole = model()->sortRole(); if (!visibleRoles().contains(sortRole)) { applyRolesToModel(); } KStandardItemListView::slotSortRoleChanged(current, previous); } void KFileItemListView::triggerVisibleIndexRangeUpdate() { if (!model()) { return; } m_modelRolesUpdater->setPaused(true); // If the icon size has been changed recently, wait until // m_updateIconSizeTimer expires. if (!m_updateIconSizeTimer->isActive()) { m_updateVisibleIndexRangeTimer->start(); } } void KFileItemListView::updateVisibleIndexRange() { if (!m_modelRolesUpdater) { return; } const int index = firstVisibleIndex(); const int count = lastVisibleIndex() - index + 1; m_modelRolesUpdater->setMaximumVisibleItems(maximumVisibleItems()); m_modelRolesUpdater->setVisibleIndexRange(index, count); m_modelRolesUpdater->setPaused(isTransactionActive()); } void KFileItemListView::triggerIconSizeUpdate() { if (!model()) { return; } m_modelRolesUpdater->setPaused(true); m_updateIconSizeTimer->start(); // The visible index range will be updated when m_updateIconSizeTimer expires. // Stop m_updateVisibleIndexRangeTimer to prevent an expensive re-generation // of all previews (note that the user might change the icon size again soon). m_updateVisibleIndexRangeTimer->stop(); } void KFileItemListView::updateIconSize() { if (!m_modelRolesUpdater) { return; } m_modelRolesUpdater->setIconSize(availableIconSize()); // Update the visible index range (which has most likely changed after the // icon size change) before unpausing m_modelRolesUpdater. const int index = firstVisibleIndex(); const int count = lastVisibleIndex() - index + 1; m_modelRolesUpdater->setVisibleIndexRange(index, count); m_modelRolesUpdater->setPaused(isTransactionActive()); } void KFileItemListView::applyRolesToModel() { if (!model()) { return; } Q_ASSERT(qobject_cast(model())); KFileItemModel* fileItemModel = static_cast(model()); // KFileItemModel does not distinct between "visible" and "invisible" roles. // Add all roles that are mandatory for having a working KFileItemListView: QSet roles = visibleRoles().toSet(); roles.insert("iconPixmap"); roles.insert("iconName"); roles.insert("text"); roles.insert("isDir"); roles.insert("isLink"); roles.insert("isHidden"); if (supportsItemExpanding()) { roles.insert("isExpanded"); roles.insert("isExpandable"); roles.insert("expandedParentsCount"); } // Assure that the role that is used for sorting will be determined roles.insert(fileItemModel->sortRole()); fileItemModel->setRoles(roles); m_modelRolesUpdater->setRoles(roles); } QSize KFileItemListView::availableIconSize() const { const KItemListStyleOption& option = styleOption(); const int iconSize = option.iconSize; if (itemLayout() == IconsLayout) { const int maxIconWidth = itemSize().width() - 2 * option.padding; return QSize(maxIconWidth, iconSize); } return QSize(iconSize, iconSize); } diff --git a/src/kitemviews/kfileitemlistwidget.cpp b/src/kitemviews/kfileitemlistwidget.cpp index 5fa3e548c..92b84b145 100644 --- a/src/kitemviews/kfileitemlistwidget.cpp +++ b/src/kitemviews/kfileitemlistwidget.cpp @@ -1,164 +1,161 @@ /*************************************************************************** * 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 "kfileitemlistwidget.h" #include "kfileitemmodel.h" #include "kitemlistview.h" #include -#include -#include #include #include -#include KFileItemListWidgetInformant::KFileItemListWidgetInformant() : KStandardItemListWidgetInformant() { } KFileItemListWidgetInformant::~KFileItemListWidgetInformant() { } QString KFileItemListWidgetInformant::itemText(int index, const KItemListView* view) const { Q_ASSERT(qobject_cast(view->model())); KFileItemModel* fileItemModel = static_cast(view->model()); const KFileItem item = fileItemModel->fileItem(index); return item.text(); } bool KFileItemListWidgetInformant::itemIsLink(int index, const KItemListView* view) const { Q_ASSERT(qobject_cast(view->model())); KFileItemModel* fileItemModel = static_cast(view->model()); const KFileItem item = fileItemModel->fileItem(index); return item.isLink(); } QString KFileItemListWidgetInformant::roleText(const QByteArray& role, const QHash& values) const { QString text; const QVariant roleValue = values.value(role); // Implementation note: In case if more roles require a custom handling // use a hash + switch for a linear runtime. if (role == "size") { if (values.value("isDir").toBool()) { // The item represents a directory. Show the number of sub directories // instead of the file size of the directory. if (!roleValue.isNull()) { const int count = roleValue.toInt(); if (count < 0) { text = i18nc("@item:intable", "Unknown"); } else { text = i18ncp("@item:intable", "%1 item", "%1 items", count); } } } else { const KIO::filesize_t size = roleValue.value(); text = KFormat().formatByteSize(size); } } else if (role == "modificationtime" || role == "accesstime" || role == "deletiontime" || role == "imageDateTime") { const QDateTime dateTime = roleValue.toDateTime(); text = QLocale().toString(dateTime, QLocale::ShortFormat); } else { text = KStandardItemListWidgetInformant::roleText(role, values); } return text; } QFont KFileItemListWidgetInformant::customizedFontForLinks(const QFont& baseFont) const { // The customized font should be italic if the file is a symbolic link. QFont font(baseFont); font.setItalic(true); return font; } KFileItemListWidget::KFileItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent) : KStandardItemListWidget(informant, parent) { } KFileItemListWidget::~KFileItemListWidget() { } KItemListWidgetInformant* KFileItemListWidget::createInformant() { return new KFileItemListWidgetInformant(); } bool KFileItemListWidget::isRoleRightAligned(const QByteArray& role) const { return role == "size"; } bool KFileItemListWidget::isHidden() const { return data().value("isHidden").toBool(); } QFont KFileItemListWidget::customizedFont(const QFont& baseFont) const { // The customized font should be italic if the file is a symbolic link. QFont font(baseFont); font.setItalic(data().value("isLink").toBool()); return font; } int KFileItemListWidget::selectionLength(const QString& text) const { // Select the text without MIME-type extension int selectionLength = text.length(); // If item is a directory, use the whole text length for // selection (ignore all points) if(data().value("isDir").toBool()) { return selectionLength; } QMimeDatabase db; const QString extension = db.suffixForFileName(text); if (extension.isEmpty()) { // For an unknown extension just exclude the extension after // the last point. This does not work for multiple extensions like // *.tar.gz but usually this is anyhow a known extension. selectionLength = text.lastIndexOf(QLatin1Char('.')); // If no point could be found, use whole text length for selection. if (selectionLength < 1) { selectionLength = text.length(); } } else { selectionLength -= extension.length() + 1; } return selectionLength; } diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index 8c736ccfe..70014e1a7 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -1,2421 +1,2418 @@ /***************************************************************************** * Copyright (C) 2011 by Peter Penz * * Copyright (C) 2013 by Frank Reininghaus * * Copyright (C) 2013 by Emmanuel Pescosta * * * * 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 "kfileitemmodel.h" #include "dolphin_generalsettings.h" #include #include #include "dolphindebug.h" #include "private/kfileitemmodelsortalgorithm.h" #include "private/kfileitemmodeldirlister.h" #include #include #include #include -#include -#include - // #define KFILEITEMMODEL_DEBUG KFileItemModel::KFileItemModel(QObject* parent) : KItemModelBase("text", parent), m_dirLister(nullptr), m_sortDirsFirst(true), m_sortRole(NameRole), m_sortingProgressPercent(-1), m_roles(), m_itemData(), m_items(), m_filter(), m_filteredItems(), m_requestRole(), m_maximumUpdateIntervalTimer(nullptr), m_resortAllItemsTimer(nullptr), m_pendingItemsToInsert(), m_groups(), m_expandedDirs(), m_urlsToExpand() { m_collator.setNumericMode(true); loadSortingSettings(); m_dirLister = new KFileItemModelDirLister(this); m_dirLister->setDelayedMimeTypes(true); const QWidget* parentWidget = qobject_cast(parent); if (parentWidget) { m_dirLister->setMainWindow(parentWidget->window()); } connect(m_dirLister, &KFileItemModelDirLister::started, this, &KFileItemModel::directoryLoadingStarted); connect(m_dirLister, static_cast(&KFileItemModelDirLister::canceled), this, &KFileItemModel::slotCanceled); connect(m_dirLister, static_cast(&KFileItemModelDirLister::completed), this, &KFileItemModel::slotCompleted); connect(m_dirLister, &KFileItemModelDirLister::itemsAdded, this, &KFileItemModel::slotItemsAdded); connect(m_dirLister, &KFileItemModelDirLister::itemsDeleted, this, &KFileItemModel::slotItemsDeleted); connect(m_dirLister, &KFileItemModelDirLister::refreshItems, this, &KFileItemModel::slotRefreshItems); connect(m_dirLister, static_cast(&KFileItemModelDirLister::clear), this, &KFileItemModel::slotClear); connect(m_dirLister, &KFileItemModelDirLister::infoMessage, this, &KFileItemModel::infoMessage); connect(m_dirLister, &KFileItemModelDirLister::errorMessage, this, &KFileItemModel::errorMessage); connect(m_dirLister, &KFileItemModelDirLister::percent, this, &KFileItemModel::directoryLoadingProgress); connect(m_dirLister, static_cast(&KFileItemModelDirLister::redirection), this, &KFileItemModel::directoryRedirection); connect(m_dirLister, &KFileItemModelDirLister::urlIsFileError, this, &KFileItemModel::urlIsFileError); // Apply default roles that should be determined resetRoles(); m_requestRole[NameRole] = true; m_requestRole[IsDirRole] = true; m_requestRole[IsLinkRole] = true; m_roles.insert("text"); m_roles.insert("isDir"); m_roles.insert("isLink"); m_roles.insert("isHidden"); // For slow KIO-slaves like used for searching it makes sense to show results periodically even // before the completed() or canceled() signal has been emitted. m_maximumUpdateIntervalTimer = new QTimer(this); m_maximumUpdateIntervalTimer->setInterval(2000); m_maximumUpdateIntervalTimer->setSingleShot(true); connect(m_maximumUpdateIntervalTimer, &QTimer::timeout, this, &KFileItemModel::dispatchPendingItemsToInsert); // When changing the value of an item which represents the sort-role a resorting must be // triggered. Especially in combination with KFileItemModelRolesUpdater this might be done // for a lot of items within a quite small timeslot. To prevent expensive resortings the // resorting is postponed until the timer has been exceeded. m_resortAllItemsTimer = new QTimer(this); m_resortAllItemsTimer->setInterval(500); m_resortAllItemsTimer->setSingleShot(true); connect(m_resortAllItemsTimer, &QTimer::timeout, this, &KFileItemModel::resortAllItems); connect(GeneralSettings::self(), &GeneralSettings::sortingChoiceChanged, this, &KFileItemModel::slotSortingChoiceChanged); } KFileItemModel::~KFileItemModel() { qDeleteAll(m_itemData); qDeleteAll(m_filteredItems); qDeleteAll(m_pendingItemsToInsert); } void KFileItemModel::loadDirectory(const QUrl &url) { m_dirLister->openUrl(url); } void KFileItemModel::refreshDirectory(const QUrl &url) { // Refresh all expanded directories first (Bug 295300) QHashIterator expandedDirs(m_expandedDirs); while (expandedDirs.hasNext()) { expandedDirs.next(); m_dirLister->openUrl(expandedDirs.value(), KDirLister::Reload); } m_dirLister->openUrl(url, KDirLister::Reload); } QUrl KFileItemModel::directory() const { return m_dirLister->url(); } void KFileItemModel::cancelDirectoryLoading() { m_dirLister->stop(); } int KFileItemModel::count() const { return m_itemData.count(); } QHash KFileItemModel::data(int index) const { if (index >= 0 && index < count()) { ItemData* data = m_itemData.at(index); if (data->values.isEmpty()) { data->values = retrieveData(data->item, data->parent); } return data->values; } return QHash(); } bool KFileItemModel::setData(int index, const QHash& values) { if (index < 0 || index >= count()) { return false; } QHash currentValues = data(index); // Determine which roles have been changed QSet changedRoles; QHashIterator it(values); while (it.hasNext()) { it.next(); const QByteArray role = sharedValue(it.key()); const QVariant value = it.value(); if (currentValues[role] != value) { currentValues[role] = value; changedRoles.insert(role); } } if (changedRoles.isEmpty()) { return false; } m_itemData[index]->values = currentValues; if (changedRoles.contains("text")) { QUrl url = m_itemData[index]->item.url(); url = url.adjusted(QUrl::RemoveFilename); url.setPath(url.path() + currentValues["text"].toString()); m_itemData[index]->item.setUrl(url); } emitItemsChangedAndTriggerResorting(KItemRangeList() << KItemRange(index, 1), changedRoles); return true; } void KFileItemModel::setSortDirectoriesFirst(bool dirsFirst) { if (dirsFirst != m_sortDirsFirst) { m_sortDirsFirst = dirsFirst; resortAllItems(); } } bool KFileItemModel::sortDirectoriesFirst() const { return m_sortDirsFirst; } void KFileItemModel::setShowHiddenFiles(bool show) { m_dirLister->setShowingDotFiles(show); m_dirLister->emitChanges(); if (show) { dispatchPendingItemsToInsert(); } } bool KFileItemModel::showHiddenFiles() const { return m_dirLister->showingDotFiles(); } void KFileItemModel::setShowDirectoriesOnly(bool enabled) { m_dirLister->setDirOnlyMode(enabled); } bool KFileItemModel::showDirectoriesOnly() const { return m_dirLister->dirOnlyMode(); } QMimeData* KFileItemModel::createMimeData(const KItemSet& indexes) const { QMimeData* data = new QMimeData(); // The following code has been taken from KDirModel::mimeData() // (kdelibs/kio/kio/kdirmodel.cpp) // Copyright (C) 2006 David Faure QList urls; QList mostLocalUrls; bool canUseMostLocalUrls = true; const ItemData* lastAddedItem = nullptr; for (int index : indexes) { const ItemData* itemData = m_itemData.at(index); const ItemData* parent = itemData->parent; while (parent && parent != lastAddedItem) { parent = parent->parent; } if (parent && parent == lastAddedItem) { // A parent of 'itemData' has been added already. continue; } lastAddedItem = itemData; const KFileItem& item = itemData->item; if (!item.isNull()) { urls << item.url(); bool isLocal; mostLocalUrls << item.mostLocalUrl(isLocal); if (!isLocal) { canUseMostLocalUrls = false; } } } KUrlMimeData::setUrls(urls, mostLocalUrls, data); return data; } int KFileItemModel::indexForKeyboardSearch(const QString& text, int startFromIndex) const { startFromIndex = qMax(0, startFromIndex); for (int i = startFromIndex; i < count(); ++i) { if (fileItem(i).text().startsWith(text, Qt::CaseInsensitive)) { return i; } } for (int i = 0; i < startFromIndex; ++i) { if (fileItem(i).text().startsWith(text, Qt::CaseInsensitive)) { return i; } } return -1; } bool KFileItemModel::supportsDropping(int index) const { const KFileItem item = fileItem(index); return !item.isNull() && (item.isDir() || item.isDesktopFile()); } QString KFileItemModel::roleDescription(const QByteArray& role) const { static QHash description; if (description.isEmpty()) { int count = 0; const RoleInfoMap* map = rolesInfoMap(count); for (int i = 0; i < count; ++i) { description.insert(map[i].role, i18nc(map[i].roleTranslationContext, map[i].roleTranslation)); } } return description.value(role); } QList > KFileItemModel::groups() const { if (!m_itemData.isEmpty() && m_groups.isEmpty()) { #ifdef KFILEITEMMODEL_DEBUG QElapsedTimer timer; timer.start(); #endif switch (typeForRole(sortRole())) { case NameRole: m_groups = nameRoleGroups(); break; case SizeRole: m_groups = sizeRoleGroups(); break; case ModificationTimeRole: m_groups = timeRoleGroups([](const ItemData *item) { return item->item.time(KFileItem::ModificationTime); }); break; case CreationTimeRole: m_groups = timeRoleGroups([](const ItemData *item) { return item->item.time(KFileItem::CreationTime); }); break; case AccessTimeRole: m_groups = timeRoleGroups([](const ItemData *item) { return item->item.time(KFileItem::AccessTime); }); break; case DeletionTimeRole: m_groups = timeRoleGroups([](const ItemData *item) { return item->values.value("deletiontime").toDateTime(); }); break; case PermissionsRole: m_groups = permissionRoleGroups(); break; case RatingRole: m_groups = ratingRoleGroups(); break; default: m_groups = genericStringRoleGroups(sortRole()); break; } #ifdef KFILEITEMMODEL_DEBUG qCDebug(DolphinDebug) << "[TIME] Calculating groups for" << count() << "items:" << timer.elapsed(); #endif } return m_groups; } KFileItem KFileItemModel::fileItem(int index) const { if (index >= 0 && index < count()) { return m_itemData.at(index)->item; } return KFileItem(); } KFileItem KFileItemModel::fileItem(const QUrl &url) const { const int indexForUrl = index(url); if (indexForUrl >= 0) { return m_itemData.at(indexForUrl)->item; } return KFileItem(); } int KFileItemModel::index(const KFileItem& item) const { return index(item.url()); } int KFileItemModel::index(const QUrl& url) const { const QUrl urlToFind = url.adjusted(QUrl::StripTrailingSlash); const int itemCount = m_itemData.count(); int itemsInHash = m_items.count(); int index = m_items.value(urlToFind, -1); while (index < 0 && itemsInHash < itemCount) { // Not all URLs are stored yet in m_items. We grow m_items until either // urlToFind is found, or all URLs have been stored in m_items. // Note that we do not add the URLs to m_items one by one, but in // larger blocks. After each block, we check if urlToFind is in // m_items. We could in principle compare urlToFind with each URL while // we are going through m_itemData, but comparing two QUrls will, // unlike calling qHash for the URLs, trigger a parsing of the URLs // which costs both CPU cycles and memory. const int blockSize = 1000; const int currentBlockEnd = qMin(itemsInHash + blockSize, itemCount); for (int i = itemsInHash; i < currentBlockEnd; ++i) { const QUrl nextUrl = m_itemData.at(i)->item.url(); m_items.insert(nextUrl, i); } itemsInHash = currentBlockEnd; index = m_items.value(urlToFind, -1); } if (index < 0) { // The item could not be found, even though all items from m_itemData // should be in m_items now. We print some diagnostic information which // might help to find the cause of the problem, but only once. This // prevents that obtaining and printing the debugging information // wastes CPU cycles and floods the shell or .xsession-errors. static bool printDebugInfo = true; if (m_items.count() != m_itemData.count() && printDebugInfo) { printDebugInfo = false; qCWarning(DolphinDebug) << "The model is in an inconsistent state."; qCWarning(DolphinDebug) << "m_items.count() ==" << m_items.count(); qCWarning(DolphinDebug) << "m_itemData.count() ==" << m_itemData.count(); // Check if there are multiple items with the same URL. QMultiHash indexesForUrl; for (int i = 0; i < m_itemData.count(); ++i) { indexesForUrl.insert(m_itemData.at(i)->item.url(), i); } foreach (const QUrl& url, indexesForUrl.uniqueKeys()) { if (indexesForUrl.count(url) > 1) { qCWarning(DolphinDebug) << "Multiple items found with the URL" << url; auto it = indexesForUrl.find(url); while (it != indexesForUrl.end() && it.key() == url) { const ItemData* data = m_itemData.at(it.value()); qCWarning(DolphinDebug) << "index" << it.value() << ":" << data->item; if (data->parent) { qCWarning(DolphinDebug) << "parent" << data->parent->item; } ++it; } } } } } return index; } KFileItem KFileItemModel::rootItem() const { return m_dirLister->rootItem(); } void KFileItemModel::clear() { slotClear(); } void KFileItemModel::setRoles(const QSet& roles) { if (m_roles == roles) { return; } const QSet changedRoles = (roles - m_roles) + (m_roles - roles); m_roles = roles; if (count() > 0) { const bool supportedExpanding = m_requestRole[ExpandedParentsCountRole]; const bool willSupportExpanding = roles.contains("expandedParentsCount"); if (supportedExpanding && !willSupportExpanding) { // No expanding is supported anymore. Take care to delete all items that have an expansion level // that is not 0 (and hence are part of an expanded item). removeExpandedItems(); } } m_groups.clear(); resetRoles(); QSetIterator it(roles); while (it.hasNext()) { const QByteArray& role = it.next(); m_requestRole[typeForRole(role)] = true; } if (count() > 0) { // Update m_data with the changed requested roles const int maxIndex = count() - 1; for (int i = 0; i <= maxIndex; ++i) { m_itemData[i]->values = retrieveData(m_itemData.at(i)->item, m_itemData.at(i)->parent); } emit itemsChanged(KItemRangeList() << KItemRange(0, count()), changedRoles); } // Clear the 'values' of all filtered items. They will be re-populated with the // correct roles the next time 'values' will be accessed via data(int). QHash::iterator filteredIt = m_filteredItems.begin(); const QHash::iterator filteredEnd = m_filteredItems.end(); while (filteredIt != filteredEnd) { (*filteredIt)->values.clear(); ++filteredIt; } } QSet KFileItemModel::roles() const { return m_roles; } bool KFileItemModel::setExpanded(int index, bool expanded) { if (!isExpandable(index) || isExpanded(index) == expanded) { return false; } QHash values; values.insert(sharedValue("isExpanded"), expanded); if (!setData(index, values)) { return false; } const KFileItem item = m_itemData.at(index)->item; const QUrl url = item.url(); const QUrl targetUrl = item.targetUrl(); if (expanded) { m_expandedDirs.insert(targetUrl, url); m_dirLister->openUrl(url, KDirLister::Keep); const QVariantList previouslyExpandedChildren = m_itemData.at(index)->values.value("previouslyExpandedChildren").value(); foreach (const QVariant& var, previouslyExpandedChildren) { m_urlsToExpand.insert(var.toUrl()); } } else { // Note that there might be (indirect) children of the folder which is to be collapsed in // m_pendingItemsToInsert. To prevent that they will be inserted into the model later, // possibly without a parent, which might result in a crash, we insert all pending items // right now. All new items which would be without a parent will then be removed. dispatchPendingItemsToInsert(); // Check if the index of the collapsed folder has changed. If that is the case, then items // were inserted before the collapsed folder, and its index needs to be updated. if (m_itemData.at(index)->item != item) { index = this->index(item); } m_expandedDirs.remove(targetUrl); m_dirLister->stop(url); const int parentLevel = expandedParentsCount(index); const int itemCount = m_itemData.count(); const int firstChildIndex = index + 1; QVariantList expandedChildren; int childIndex = firstChildIndex; while (childIndex < itemCount && expandedParentsCount(childIndex) > parentLevel) { ItemData* itemData = m_itemData.at(childIndex); if (itemData->values.value("isExpanded").toBool()) { const QUrl targetUrl = itemData->item.targetUrl(); const QUrl url = itemData->item.url(); m_expandedDirs.remove(targetUrl); m_dirLister->stop(url); // TODO: try to unit-test this, see https://bugs.kde.org/show_bug.cgi?id=332102#c11 expandedChildren.append(targetUrl); } ++childIndex; } const int childrenCount = childIndex - firstChildIndex; removeFilteredChildren(KItemRangeList() << KItemRange(index, 1 + childrenCount)); removeItems(KItemRangeList() << KItemRange(firstChildIndex, childrenCount), DeleteItemData); m_itemData.at(index)->values.insert("previouslyExpandedChildren", expandedChildren); } return true; } bool KFileItemModel::isExpanded(int index) const { if (index >= 0 && index < count()) { return m_itemData.at(index)->values.value("isExpanded").toBool(); } return false; } bool KFileItemModel::isExpandable(int index) const { if (index >= 0 && index < count()) { // Call data (instead of accessing m_itemData directly) // to ensure that the value is initialized. return data(index).value("isExpandable").toBool(); } return false; } int KFileItemModel::expandedParentsCount(int index) const { if (index >= 0 && index < count()) { return expandedParentsCount(m_itemData.at(index)); } return 0; } QSet KFileItemModel::expandedDirectories() const { QSet result; const auto dirs = m_expandedDirs; for (const auto &dir : dirs) { result.insert(dir); } return result; } void KFileItemModel::restoreExpandedDirectories(const QSet &urls) { m_urlsToExpand = urls; } void KFileItemModel::expandParentDirectories(const QUrl &url) { // Assure that each sub-path of the URL that should be // expanded is added to m_urlsToExpand. KDirLister // does not care whether the parent-URL has already been // expanded. QUrl urlToExpand = m_dirLister->url(); const int pos = urlToExpand.path().length(); // first subdir can be empty, if m_dirLister->url().path() does not end with '/' // this happens if baseUrl is not root but a home directory, see FoldersPanel, // so using QString::SkipEmptyParts const QStringList subDirs = url.path().mid(pos).split(QDir::separator(), QString::SkipEmptyParts); for (int i = 0; i < subDirs.count() - 1; ++i) { QString path = urlToExpand.path(); if (!path.endsWith(QLatin1Char('/'))) { path.append(QLatin1Char('/')); } urlToExpand.setPath(path + subDirs.at(i)); m_urlsToExpand.insert(urlToExpand); } // KDirLister::open() must called at least once to trigger an initial // loading. The pending URLs that must be restored are handled // in slotCompleted(). QSetIterator it2(m_urlsToExpand); while (it2.hasNext()) { const int idx = index(it2.next()); if (idx >= 0 && !isExpanded(idx)) { setExpanded(idx, true); break; } } } void KFileItemModel::setNameFilter(const QString& nameFilter) { if (m_filter.pattern() != nameFilter) { dispatchPendingItemsToInsert(); m_filter.setPattern(nameFilter); applyFilters(); } } QString KFileItemModel::nameFilter() const { return m_filter.pattern(); } void KFileItemModel::setMimeTypeFilters(const QStringList& filters) { if (m_filter.mimeTypes() != filters) { dispatchPendingItemsToInsert(); m_filter.setMimeTypes(filters); applyFilters(); } } QStringList KFileItemModel::mimeTypeFilters() const { return m_filter.mimeTypes(); } void KFileItemModel::applyFilters() { // Check which shown items from m_itemData must get // hidden and hence moved to m_filteredItems. QVector newFilteredIndexes; const int itemCount = m_itemData.count(); for (int index = 0; index < itemCount; ++index) { ItemData* itemData = m_itemData.at(index); // Only filter non-expanded items as child items may never // exist without a parent item if (!itemData->values.value("isExpanded").toBool()) { const KFileItem item = itemData->item; if (!m_filter.matches(item)) { newFilteredIndexes.append(index); m_filteredItems.insert(item, itemData); } } } const KItemRangeList removedRanges = KItemRangeList::fromSortedContainer(newFilteredIndexes); removeItems(removedRanges, KeepItemData); // Check which hidden items from m_filteredItems should // get visible again and hence removed from m_filteredItems. QList newVisibleItems; QHash::iterator it = m_filteredItems.begin(); while (it != m_filteredItems.end()) { if (m_filter.matches(it.key())) { newVisibleItems.append(it.value()); it = m_filteredItems.erase(it); } else { ++it; } } insertItems(newVisibleItems); } void KFileItemModel::removeFilteredChildren(const KItemRangeList& itemRanges) { if (m_filteredItems.isEmpty() || !m_requestRole[ExpandedParentsCountRole]) { // There are either no filtered items, or it is not possible to expand // folders -> there cannot be any filtered children. return; } QSet parents; foreach (const KItemRange& range, itemRanges) { for (int index = range.index; index < range.index + range.count; ++index) { parents.insert(m_itemData.at(index)); } } QHash::iterator it = m_filteredItems.begin(); while (it != m_filteredItems.end()) { if (parents.contains(it.value()->parent)) { delete it.value(); it = m_filteredItems.erase(it); } else { ++it; } } } QList KFileItemModel::rolesInformation() { static QList rolesInfo; if (rolesInfo.isEmpty()) { int count = 0; const RoleInfoMap* map = rolesInfoMap(count); for (int i = 0; i < count; ++i) { if (map[i].roleType != NoRole) { RoleInfo info; info.role = map[i].role; info.translation = i18nc(map[i].roleTranslationContext, map[i].roleTranslation); if (map[i].groupTranslation) { info.group = i18nc(map[i].groupTranslationContext, map[i].groupTranslation); } else { // For top level roles, groupTranslation is 0. We must make sure that // info.group is an empty string then because the code that generates // menus tries to put the actions into sub menus otherwise. info.group = QString(); } info.requiresBaloo = map[i].requiresBaloo; info.requiresIndexer = map[i].requiresIndexer; rolesInfo.append(info); } } } return rolesInfo; } void KFileItemModel::onGroupedSortingChanged(bool current) { Q_UNUSED(current); m_groups.clear(); } void KFileItemModel::onSortRoleChanged(const QByteArray& current, const QByteArray& previous) { Q_UNUSED(previous); m_sortRole = typeForRole(current); if (!m_requestRole[m_sortRole]) { QSet newRoles = m_roles; newRoles << current; setRoles(newRoles); } resortAllItems(); } void KFileItemModel::onSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) { Q_UNUSED(current); Q_UNUSED(previous); resortAllItems(); } void KFileItemModel::loadSortingSettings() { using Choice = GeneralSettings::EnumSortingChoice; switch (GeneralSettings::sortingChoice()) { case Choice::NaturalSorting: m_naturalSorting = true; m_collator.setCaseSensitivity(Qt::CaseInsensitive); break; case Choice::CaseSensitiveSorting: m_naturalSorting = false; m_collator.setCaseSensitivity(Qt::CaseSensitive); break; case Choice::CaseInsensitiveSorting: m_naturalSorting = false; m_collator.setCaseSensitivity(Qt::CaseInsensitive); break; default: Q_UNREACHABLE(); } } void KFileItemModel::resortAllItems() { m_resortAllItemsTimer->stop(); const int itemCount = count(); if (itemCount <= 0) { return; } #ifdef KFILEITEMMODEL_DEBUG QElapsedTimer timer; timer.start(); qCDebug(DolphinDebug) << "==========================================================="; qCDebug(DolphinDebug) << "Resorting" << itemCount << "items"; #endif // Remember the order of the current URLs so // that it can be determined which indexes have // been moved because of the resorting. QList oldUrls; oldUrls.reserve(itemCount); foreach (const ItemData* itemData, m_itemData) { oldUrls.append(itemData->item.url()); } m_items.clear(); m_items.reserve(itemCount); // Resort the items sort(m_itemData.begin(), m_itemData.end()); for (int i = 0; i < itemCount; ++i) { m_items.insert(m_itemData.at(i)->item.url(), i); } // Determine the first index that has been moved. int firstMovedIndex = 0; while (firstMovedIndex < itemCount && firstMovedIndex == m_items.value(oldUrls.at(firstMovedIndex))) { ++firstMovedIndex; } const bool itemsHaveMoved = firstMovedIndex < itemCount; if (itemsHaveMoved) { m_groups.clear(); int lastMovedIndex = itemCount - 1; while (lastMovedIndex > firstMovedIndex && lastMovedIndex == m_items.value(oldUrls.at(lastMovedIndex))) { --lastMovedIndex; } Q_ASSERT(firstMovedIndex <= lastMovedIndex); // Create a list movedToIndexes, which has the property that // movedToIndexes[i] is the new index of the item with the old index // firstMovedIndex + i. const int movedItemsCount = lastMovedIndex - firstMovedIndex + 1; QList movedToIndexes; movedToIndexes.reserve(movedItemsCount); for (int i = firstMovedIndex; i <= lastMovedIndex; ++i) { const int newIndex = m_items.value(oldUrls.at(i)); movedToIndexes.append(newIndex); } emit itemsMoved(KItemRange(firstMovedIndex, movedItemsCount), movedToIndexes); } else if (groupedSorting()) { // The groups might have changed even if the order of the items has not. const QList > oldGroups = m_groups; m_groups.clear(); if (groups() != oldGroups) { emit groupsChanged(); } } #ifdef KFILEITEMMODEL_DEBUG qCDebug(DolphinDebug) << "[TIME] Resorting of" << itemCount << "items:" << timer.elapsed(); #endif } void KFileItemModel::slotCompleted() { dispatchPendingItemsToInsert(); if (!m_urlsToExpand.isEmpty()) { // Try to find a URL that can be expanded. // Note that the parent folder must be expanded before any of its subfolders become visible. // Therefore, some URLs in m_restoredExpandedUrls might not be visible yet // -> we expand the first visible URL we find in m_restoredExpandedUrls. foreach (const QUrl& url, m_urlsToExpand) { const int indexForUrl = index(url); if (indexForUrl >= 0) { m_urlsToExpand.remove(url); if (setExpanded(indexForUrl, true)) { // The dir lister has been triggered. This slot will be called // again after the directory has been expanded. return; } } } // None of the URLs in m_restoredExpandedUrls could be found in the model. This can happen // if these URLs have been deleted in the meantime. m_urlsToExpand.clear(); } emit directoryLoadingCompleted(); } void KFileItemModel::slotCanceled() { m_maximumUpdateIntervalTimer->stop(); dispatchPendingItemsToInsert(); emit directoryLoadingCanceled(); } void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemList& items) { Q_ASSERT(!items.isEmpty()); QUrl parentUrl; if (m_expandedDirs.contains(directoryUrl)) { parentUrl = m_expandedDirs.value(directoryUrl); } else { parentUrl = directoryUrl.adjusted(QUrl::StripTrailingSlash); } if (m_requestRole[ExpandedParentsCountRole]) { // If the expanding of items is enabled, the call // dirLister->openUrl(url, KDirLister::Keep) in KFileItemModel::setExpanded() // might result in emitting the same items twice due to the Keep-parameter. // This case happens if an item gets expanded, collapsed and expanded again // before the items could be loaded for the first expansion. if (index(items.first().url()) >= 0) { // The items are already part of the model. return; } if (directoryUrl != directory()) { // To be able to compare whether the new items may be inserted as children // of a parent item the pending items must be added to the model first. dispatchPendingItemsToInsert(); } // KDirLister keeps the children of items that got expanded once even if // they got collapsed again with KFileItemModel::setExpanded(false). So it must be // checked whether the parent for new items is still expanded. const int parentIndex = index(parentUrl); if (parentIndex >= 0 && !m_itemData[parentIndex]->values.value("isExpanded").toBool()) { // The parent is not expanded. return; } } QList itemDataList = createItemDataList(parentUrl, items); if (!m_filter.hasSetFilters()) { m_pendingItemsToInsert.append(itemDataList); } else { // The name or type filter is active. Hide filtered items // before inserting them into the model and remember // the filtered items in m_filteredItems. foreach (ItemData* itemData, itemDataList) { if (m_filter.matches(itemData->item)) { m_pendingItemsToInsert.append(itemData); } else { m_filteredItems.insert(itemData->item, itemData); } } } if (useMaximumUpdateInterval() && !m_maximumUpdateIntervalTimer->isActive()) { // Assure that items get dispatched if no completed() or canceled() signal is // emitted during the maximum update interval. m_maximumUpdateIntervalTimer->start(); } } void KFileItemModel::slotItemsDeleted(const KFileItemList& items) { dispatchPendingItemsToInsert(); QVector indexesToRemove; indexesToRemove.reserve(items.count()); foreach (const KFileItem& item, items) { const int indexForItem = index(item); if (indexForItem >= 0) { indexesToRemove.append(indexForItem); } else { // Probably the item has been filtered. QHash::iterator it = m_filteredItems.find(item); if (it != m_filteredItems.end()) { delete it.value(); m_filteredItems.erase(it); } } } std::sort(indexesToRemove.begin(), indexesToRemove.end()); if (m_requestRole[ExpandedParentsCountRole] && !m_expandedDirs.isEmpty()) { // Assure that removing a parent item also results in removing all children QVector indexesToRemoveWithChildren; indexesToRemoveWithChildren.reserve(m_itemData.count()); const int itemCount = m_itemData.count(); foreach (int index, indexesToRemove) { indexesToRemoveWithChildren.append(index); const int parentLevel = expandedParentsCount(index); int childIndex = index + 1; while (childIndex < itemCount && expandedParentsCount(childIndex) > parentLevel) { indexesToRemoveWithChildren.append(childIndex); ++childIndex; } } indexesToRemove = indexesToRemoveWithChildren; } const KItemRangeList itemRanges = KItemRangeList::fromSortedContainer(indexesToRemove); removeFilteredChildren(itemRanges); removeItems(itemRanges, DeleteItemData); } void KFileItemModel::slotRefreshItems(const QList >& items) { Q_ASSERT(!items.isEmpty()); #ifdef KFILEITEMMODEL_DEBUG qCDebug(DolphinDebug) << "Refreshing" << items.count() << "items"; #endif // Get the indexes of all items that have been refreshed QList indexes; indexes.reserve(items.count()); QSet changedRoles; QListIterator > it(items); while (it.hasNext()) { const QPair& itemPair = it.next(); const KFileItem& oldItem = itemPair.first; const KFileItem& newItem = itemPair.second; const int indexForItem = index(oldItem); if (indexForItem >= 0) { m_itemData[indexForItem]->item = newItem; // Keep old values as long as possible if they could not retrieved synchronously yet. // The update of the values will be done asynchronously by KFileItemModelRolesUpdater. QHashIterator it(retrieveData(newItem, m_itemData.at(indexForItem)->parent)); QHash& values = m_itemData[indexForItem]->values; while (it.hasNext()) { it.next(); const QByteArray& role = it.key(); if (values.value(role) != it.value()) { values.insert(role, it.value()); changedRoles.insert(role); } } m_items.remove(oldItem.url()); m_items.insert(newItem.url(), indexForItem); indexes.append(indexForItem); } else { // Check if 'oldItem' is one of the filtered items. QHash::iterator it = m_filteredItems.find(oldItem); if (it != m_filteredItems.end()) { ItemData* itemData = it.value(); itemData->item = newItem; // The data stored in 'values' might have changed. Therefore, we clear // 'values' and re-populate it the next time it is requested via data(int). itemData->values.clear(); m_filteredItems.erase(it); m_filteredItems.insert(newItem, itemData); } } } // If the changed items have been created recently, they might not be in m_items yet. // In that case, the list 'indexes' might be empty. if (indexes.isEmpty()) { return; } // Extract the item-ranges out of the changed indexes qSort(indexes); const KItemRangeList itemRangeList = KItemRangeList::fromSortedContainer(indexes); emitItemsChangedAndTriggerResorting(itemRangeList, changedRoles); } void KFileItemModel::slotClear() { #ifdef KFILEITEMMODEL_DEBUG qCDebug(DolphinDebug) << "Clearing all items"; #endif qDeleteAll(m_filteredItems); m_filteredItems.clear(); m_groups.clear(); m_maximumUpdateIntervalTimer->stop(); m_resortAllItemsTimer->stop(); qDeleteAll(m_pendingItemsToInsert); m_pendingItemsToInsert.clear(); const int removedCount = m_itemData.count(); if (removedCount > 0) { qDeleteAll(m_itemData); m_itemData.clear(); m_items.clear(); emit itemsRemoved(KItemRangeList() << KItemRange(0, removedCount)); } m_expandedDirs.clear(); } void KFileItemModel::slotSortingChoiceChanged() { loadSortingSettings(); resortAllItems(); } void KFileItemModel::dispatchPendingItemsToInsert() { if (!m_pendingItemsToInsert.isEmpty()) { insertItems(m_pendingItemsToInsert); m_pendingItemsToInsert.clear(); } } void KFileItemModel::insertItems(QList& newItems) { if (newItems.isEmpty()) { return; } #ifdef KFILEITEMMODEL_DEBUG QElapsedTimer timer; timer.start(); qCDebug(DolphinDebug) << "==========================================================="; qCDebug(DolphinDebug) << "Inserting" << newItems.count() << "items"; #endif m_groups.clear(); prepareItemsForSorting(newItems); if (m_sortRole == NameRole && m_naturalSorting) { // Natural sorting of items can be very slow. However, it becomes much // faster if the input sequence is already mostly sorted. Therefore, we // first sort 'newItems' according to the QStrings returned by // KFileItem::text() using QString::operator<(), which is quite fast. parallelMergeSort(newItems.begin(), newItems.end(), nameLessThan, QThread::idealThreadCount()); } sort(newItems.begin(), newItems.end()); #ifdef KFILEITEMMODEL_DEBUG qCDebug(DolphinDebug) << "[TIME] Sorting:" << timer.elapsed(); #endif KItemRangeList itemRanges; const int existingItemCount = m_itemData.count(); const int newItemCount = newItems.count(); const int totalItemCount = existingItemCount + newItemCount; if (existingItemCount == 0) { // Optimization for the common special case that there are no // items in the model yet. Happens, e.g., when entering a folder. m_itemData = newItems; itemRanges << KItemRange(0, newItemCount); } else { m_itemData.reserve(totalItemCount); for (int i = existingItemCount; i < totalItemCount; ++i) { m_itemData.append(0); } // We build the new list m_itemData in reverse order to minimize // the number of moves and guarantee O(N) complexity. int targetIndex = totalItemCount - 1; int sourceIndexExistingItems = existingItemCount - 1; int sourceIndexNewItems = newItemCount - 1; int rangeCount = 0; while (sourceIndexNewItems >= 0) { ItemData* newItem = newItems.at(sourceIndexNewItems); if (sourceIndexExistingItems >= 0 && lessThan(newItem, m_itemData.at(sourceIndexExistingItems), m_collator)) { // Move an existing item to its new position. If any new items // are behind it, push the item range to itemRanges. if (rangeCount > 0) { itemRanges << KItemRange(sourceIndexExistingItems + 1, rangeCount); rangeCount = 0; } m_itemData[targetIndex] = m_itemData.at(sourceIndexExistingItems); --sourceIndexExistingItems; } else { // Insert a new item into the list. ++rangeCount; m_itemData[targetIndex] = newItem; --sourceIndexNewItems; } --targetIndex; } // Push the final item range to itemRanges. if (rangeCount > 0) { itemRanges << KItemRange(sourceIndexExistingItems + 1, rangeCount); } // Note that itemRanges is still sorted in reverse order. std::reverse(itemRanges.begin(), itemRanges.end()); } // The indexes in m_items are not correct anymore. Therefore, we clear m_items. // It will be re-populated with the updated indices if index(const QUrl&) is called. m_items.clear(); emit itemsInserted(itemRanges); #ifdef KFILEITEMMODEL_DEBUG qCDebug(DolphinDebug) << "[TIME] Inserting of" << newItems.count() << "items:" << timer.elapsed(); #endif } void KFileItemModel::removeItems(const KItemRangeList& itemRanges, RemoveItemsBehavior behavior) { if (itemRanges.isEmpty()) { return; } m_groups.clear(); // Step 1: Remove the items from m_itemData, and free the ItemData. int removedItemsCount = 0; foreach (const KItemRange& range, itemRanges) { removedItemsCount += range.count; for (int index = range.index; index < range.index + range.count; ++index) { if (behavior == DeleteItemData) { delete m_itemData.at(index); } m_itemData[index] = 0; } } // Step 2: Remove the ItemData pointers from the list m_itemData. int target = itemRanges.at(0).index; int source = itemRanges.at(0).index + itemRanges.at(0).count; int nextRange = 1; const int oldItemDataCount = m_itemData.count(); while (source < oldItemDataCount) { m_itemData[target] = m_itemData[source]; ++target; ++source; if (nextRange < itemRanges.count() && source == itemRanges.at(nextRange).index) { // Skip the items in the next removed range. source += itemRanges.at(nextRange).count; ++nextRange; } } m_itemData.erase(m_itemData.end() - removedItemsCount, m_itemData.end()); // The indexes in m_items are not correct anymore. Therefore, we clear m_items. // It will be re-populated with the updated indices if index(const QUrl&) is called. m_items.clear(); emit itemsRemoved(itemRanges); } QList KFileItemModel::createItemDataList(const QUrl& parentUrl, const KFileItemList& items) const { if (m_sortRole == TypeRole) { // Try to resolve the MIME-types synchronously to prevent a reordering of // the items when sorting by type (per default MIME-types are resolved // asynchronously by KFileItemModelRolesUpdater). determineMimeTypes(items, 200); } const int parentIndex = index(parentUrl); ItemData* parentItem = parentIndex < 0 ? 0 : m_itemData.at(parentIndex); QList itemDataList; itemDataList.reserve(items.count()); foreach (const KFileItem& item, items) { ItemData* itemData = new ItemData(); itemData->item = item; itemData->parent = parentItem; itemDataList.append(itemData); } return itemDataList; } void KFileItemModel::prepareItemsForSorting(QList& itemDataList) { switch (m_sortRole) { case PermissionsRole: case OwnerRole: case GroupRole: case DestinationRole: case PathRole: case DeletionTimeRole: // These roles can be determined with retrieveData, and they have to be stored // in the QHash "values" for the sorting. foreach (ItemData* itemData, itemDataList) { if (itemData->values.isEmpty()) { itemData->values = retrieveData(itemData->item, itemData->parent); } } break; case TypeRole: // At least store the data including the file type for items with known MIME type. foreach (ItemData* itemData, itemDataList) { if (itemData->values.isEmpty()) { const KFileItem item = itemData->item; if (item.isDir() || item.isMimeTypeKnown()) { itemData->values = retrieveData(itemData->item, itemData->parent); } } } break; default: // The other roles are either resolved by KFileItemModelRolesUpdater // (this includes the SizeRole for directories), or they do not need // to be stored in the QHash "values" for sorting because the data can // be retrieved directly from the KFileItem (NameRole, SizeRole for files, // DateRole). break; } } int KFileItemModel::expandedParentsCount(const ItemData* data) { // The hash 'values' is only guaranteed to contain the key "expandedParentsCount" // if the corresponding item is expanded, and it is not a top-level item. const ItemData* parent = data->parent; if (parent) { if (parent->parent) { Q_ASSERT(parent->values.contains("expandedParentsCount")); return parent->values.value("expandedParentsCount").toInt() + 1; } else { return 1; } } else { return 0; } } void KFileItemModel::removeExpandedItems() { QVector indexesToRemove; const int maxIndex = m_itemData.count() - 1; for (int i = 0; i <= maxIndex; ++i) { const ItemData* itemData = m_itemData.at(i); if (itemData->parent) { indexesToRemove.append(i); } } removeItems(KItemRangeList::fromSortedContainer(indexesToRemove), DeleteItemData); m_expandedDirs.clear(); // Also remove all filtered items which have a parent. QHash::iterator it = m_filteredItems.begin(); const QHash::iterator end = m_filteredItems.end(); while (it != end) { if (it.value()->parent) { delete it.value(); it = m_filteredItems.erase(it); } else { ++it; } } } void KFileItemModel::emitItemsChangedAndTriggerResorting(const KItemRangeList& itemRanges, const QSet& changedRoles) { emit itemsChanged(itemRanges, changedRoles); // Trigger a resorting if necessary. Note that this can happen even if the sort // role has not changed at all because the file name can be used as a fallback. if (changedRoles.contains(sortRole()) || changedRoles.contains(roleForType(NameRole))) { foreach (const KItemRange& range, itemRanges) { bool needsResorting = false; const int first = range.index; const int last = range.index + range.count - 1; // Resorting the model is necessary if // (a) The first item in the range is "lessThan" its predecessor, // (b) the successor of the last item is "lessThan" the last item, or // (c) the internal order of the items in the range is incorrect. if (first > 0 && lessThan(m_itemData.at(first), m_itemData.at(first - 1), m_collator)) { needsResorting = true; } else if (last < count() - 1 && lessThan(m_itemData.at(last + 1), m_itemData.at(last), m_collator)) { needsResorting = true; } else { for (int index = first; index < last; ++index) { if (lessThan(m_itemData.at(index + 1), m_itemData.at(index), m_collator)) { needsResorting = true; break; } } } if (needsResorting) { m_resortAllItemsTimer->start(); return; } } } if (groupedSorting() && changedRoles.contains(sortRole())) { // The position is still correct, but the groups might have changed // if the changed item is either the first or the last item in a // group. // In principle, we could try to find out if the item really is the // first or last one in its group and then update the groups // (possibly with a delayed timer to make sure that we don't // re-calculate the groups very often if items are updated one by // one), but starting m_resortAllItemsTimer is easier. m_resortAllItemsTimer->start(); } } void KFileItemModel::resetRoles() { for (int i = 0; i < RolesCount; ++i) { m_requestRole[i] = false; } } KFileItemModel::RoleType KFileItemModel::typeForRole(const QByteArray& role) const { static QHash roles; if (roles.isEmpty()) { // Insert user visible roles that can be accessed with // KFileItemModel::roleInformation() int count = 0; const RoleInfoMap* map = rolesInfoMap(count); for (int i = 0; i < count; ++i) { roles.insert(map[i].role, map[i].roleType); } // Insert internal roles (take care to synchronize the implementation // with KFileItemModel::roleForType() in case if a change is done). roles.insert("isDir", IsDirRole); roles.insert("isLink", IsLinkRole); roles.insert("isHidden", IsHiddenRole); roles.insert("isExpanded", IsExpandedRole); roles.insert("isExpandable", IsExpandableRole); roles.insert("expandedParentsCount", ExpandedParentsCountRole); Q_ASSERT(roles.count() == RolesCount); } return roles.value(role, NoRole); } QByteArray KFileItemModel::roleForType(RoleType roleType) const { static QHash roles; if (roles.isEmpty()) { // Insert user visible roles that can be accessed with // KFileItemModel::roleInformation() int count = 0; const RoleInfoMap* map = rolesInfoMap(count); for (int i = 0; i < count; ++i) { roles.insert(map[i].roleType, map[i].role); } // Insert internal roles (take care to synchronize the implementation // with KFileItemModel::typeForRole() in case if a change is done). roles.insert(IsDirRole, "isDir"); roles.insert(IsLinkRole, "isLink"); roles.insert(IsHiddenRole, "isHidden"); roles.insert(IsExpandedRole, "isExpanded"); roles.insert(IsExpandableRole, "isExpandable"); roles.insert(ExpandedParentsCountRole, "expandedParentsCount"); Q_ASSERT(roles.count() == RolesCount); }; return roles.value(roleType); } QHash KFileItemModel::retrieveData(const KFileItem& item, const ItemData* parent) const { // It is important to insert only roles that are fast to retrieve. E.g. // KFileItem::iconName() can be very expensive if the MIME-type is unknown // and hence will be retrieved asynchronously by KFileItemModelRolesUpdater. QHash data; data.insert(sharedValue("url"), item.url()); const bool isDir = item.isDir(); if (m_requestRole[IsDirRole] && isDir) { data.insert(sharedValue("isDir"), true); } if (m_requestRole[IsLinkRole] && item.isLink()) { data.insert(sharedValue("isLink"), true); } if (m_requestRole[IsHiddenRole] && item.isHidden()) { data.insert(sharedValue("isHidden"), true); } if (m_requestRole[NameRole]) { data.insert(sharedValue("text"), item.text()); } if (m_requestRole[SizeRole] && !isDir) { data.insert(sharedValue("size"), item.size()); } if (m_requestRole[ModificationTimeRole]) { // Don't use KFileItem::timeString() as this is too expensive when // having several thousands of items. Instead the formatting of the // date-time will be done on-demand by the view when the date will be shown. const QDateTime dateTime = item.time(KFileItem::ModificationTime); data.insert(sharedValue("modificationtime"), dateTime); } if (m_requestRole[CreationTimeRole]) { // Don't use KFileItem::timeString() as this is too expensive when // having several thousands of items. Instead the formatting of the // date-time will be done on-demand by the view when the date will be shown. const QDateTime dateTime = item.time(KFileItem::CreationTime); data.insert(sharedValue("creationtime"), dateTime); } if (m_requestRole[AccessTimeRole]) { // Don't use KFileItem::timeString() as this is too expensive when // having several thousands of items. Instead the formatting of the // date-time will be done on-demand by the view when the date will be shown. const QDateTime dateTime = item.time(KFileItem::AccessTime); data.insert(sharedValue("accesstime"), dateTime); } if (m_requestRole[PermissionsRole]) { data.insert(sharedValue("permissions"), item.permissionsString()); } if (m_requestRole[OwnerRole]) { data.insert(sharedValue("owner"), item.user()); } if (m_requestRole[GroupRole]) { data.insert(sharedValue("group"), item.group()); } if (m_requestRole[DestinationRole]) { QString destination = item.linkDest(); if (destination.isEmpty()) { destination = QStringLiteral("-"); } data.insert(sharedValue("destination"), destination); } if (m_requestRole[PathRole]) { QString path; if (item.url().scheme() == QLatin1String("trash")) { path = item.entry().stringValue(KIO::UDSEntry::UDS_EXTRA); } else { // For performance reasons cache the home-path in a static QString // (see QDir::homePath() for more details) static QString homePath; if (homePath.isEmpty()) { homePath = QDir::homePath(); } path = item.localPath(); if (path.startsWith(homePath)) { path.replace(0, homePath.length(), QLatin1Char('~')); } } const int index = path.lastIndexOf(item.text()); path = path.mid(0, index - 1); data.insert(sharedValue("path"), path); } if (m_requestRole[DeletionTimeRole]) { QDateTime deletionTime; if (item.url().scheme() == QLatin1String("trash")) { deletionTime = QDateTime::fromString(item.entry().stringValue(KIO::UDSEntry::UDS_EXTRA + 1), Qt::ISODate); } data.insert(sharedValue("deletiontime"), deletionTime); } if (m_requestRole[IsExpandableRole] && isDir) { data.insert(sharedValue("isExpandable"), true); } if (m_requestRole[ExpandedParentsCountRole]) { if (parent) { const int level = expandedParentsCount(parent) + 1; data.insert(sharedValue("expandedParentsCount"), level); } } if (item.isMimeTypeKnown()) { data.insert(sharedValue("iconName"), item.iconName()); if (m_requestRole[TypeRole]) { data.insert(sharedValue("type"), item.mimeComment()); } } else if (m_requestRole[TypeRole] && isDir) { static const QString folderMimeType = item.mimeComment(); data.insert(sharedValue("type"), folderMimeType); } return data; } bool KFileItemModel::lessThan(const ItemData* a, const ItemData* b, const QCollator& collator) const { int result = 0; if (a->parent != b->parent) { const int expansionLevelA = expandedParentsCount(a); const int expansionLevelB = expandedParentsCount(b); // If b has a higher expansion level than a, check if a is a parent // of b, and make sure that both expansion levels are equal otherwise. for (int i = expansionLevelB; i > expansionLevelA; --i) { if (b->parent == a) { return true; } b = b->parent; } // If a has a higher expansion level than a, check if b is a parent // of a, and make sure that both expansion levels are equal otherwise. for (int i = expansionLevelA; i > expansionLevelB; --i) { if (a->parent == b) { return false; } a = a->parent; } Q_ASSERT(expandedParentsCount(a) == expandedParentsCount(b)); // Compare the last parents of a and b which are different. while (a->parent != b->parent) { a = a->parent; b = b->parent; } } if (m_sortDirsFirst || m_sortRole == SizeRole) { const bool isDirA = a->item.isDir(); const bool isDirB = b->item.isDir(); if (isDirA && !isDirB) { return true; } else if (!isDirA && isDirB) { return false; } } result = sortRoleCompare(a, b, collator); return (sortOrder() == Qt::AscendingOrder) ? result < 0 : result > 0; } /** * Helper class for KFileItemModel::sort(). */ class KFileItemModelLessThan { public: KFileItemModelLessThan(const KFileItemModel* model, const QCollator& collator) : m_model(model), m_collator(collator) { } KFileItemModelLessThan(const KFileItemModelLessThan& other) : m_model(other.m_model), m_collator() { m_collator.setCaseSensitivity(other.m_collator.caseSensitivity()); m_collator.setIgnorePunctuation(other.m_collator.ignorePunctuation()); m_collator.setLocale(other.m_collator.locale()); m_collator.setNumericMode(other.m_collator.numericMode()); } ~KFileItemModelLessThan() = default; //We do not delete m_model as the pointer was passed from outside ant it will be deleted elsewhere. KFileItemModelLessThan& operator=(const KFileItemModelLessThan& other) { m_model = other.m_model; m_collator = other.m_collator; return *this; } bool operator()(const KFileItemModel::ItemData* a, const KFileItemModel::ItemData* b) const { return m_model->lessThan(a, b, m_collator); } private: const KFileItemModel* m_model; QCollator m_collator; }; void KFileItemModel::sort(QList::iterator begin, QList::iterator end) const { KFileItemModelLessThan lessThan(this, m_collator); if (m_sortRole == NameRole) { // Sorting by name can be expensive, in particular if natural sorting is // enabled. Use all CPU cores to speed up the sorting process. static const int numberOfThreads = QThread::idealThreadCount(); parallelMergeSort(begin, end, lessThan, numberOfThreads); } else { // Sorting by other roles is quite fast. Use only one thread to prevent // problems caused by non-reentrant comparison functions, see // https://bugs.kde.org/show_bug.cgi?id=312679 mergeSort(begin, end, lessThan); } } int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const QCollator& collator) const { const KFileItem& itemA = a->item; const KFileItem& itemB = b->item; int result = 0; switch (m_sortRole) { case NameRole: // The name role is handled as default fallback after the switch break; case SizeRole: { if (itemA.isDir()) { // See "if (m_sortFoldersFirst || m_sortRole == SizeRole)" in KFileItemModel::lessThan(): Q_ASSERT(itemB.isDir()); const QVariant valueA = a->values.value("size"); const QVariant valueB = b->values.value("size"); if (valueA.isNull() && valueB.isNull()) { result = 0; } else if (valueA.isNull()) { result = -1; } else if (valueB.isNull()) { result = +1; } else { result = valueA.toInt() - valueB.toInt(); } } else { // See "if (m_sortFoldersFirst || m_sortRole == SizeRole)" in KFileItemModel::lessThan(): Q_ASSERT(!itemB.isDir()); const KIO::filesize_t sizeA = itemA.size(); const KIO::filesize_t sizeB = itemB.size(); if (sizeA > sizeB) { result = +1; } else if (sizeA < sizeB) { result = -1; } else { result = 0; } } break; } case ModificationTimeRole: { const QDateTime dateTimeA = itemA.time(KFileItem::ModificationTime); const QDateTime dateTimeB = itemB.time(KFileItem::ModificationTime); if (dateTimeA < dateTimeB) { result = -1; } else if (dateTimeA > dateTimeB) { result = +1; } break; } case CreationTimeRole: { const QDateTime dateTimeA = itemA.time(KFileItem::CreationTime); const QDateTime dateTimeB = itemB.time(KFileItem::CreationTime); if (dateTimeA < dateTimeB) { result = -1; } else if (dateTimeA > dateTimeB) { result = +1; } break; } case DeletionTimeRole: { const QDateTime dateTimeA = a->values.value("deletiontime").toDateTime(); const QDateTime dateTimeB = b->values.value("deletiontime").toDateTime(); if (dateTimeA < dateTimeB) { result = -1; } else if (dateTimeA > dateTimeB) { result = +1; } break; } case RatingRole: { result = a->values.value("rating").toInt() - b->values.value("rating").toInt(); break; } case ImageSizeRole: { // Alway use a natural comparing to interpret the numbers of a string like // "1600 x 1200" for having a correct sorting. result = collator.compare(a->values.value("imageSize").toString(), b->values.value("imageSize").toString()); break; } default: { const QByteArray role = roleForType(m_sortRole); result = QString::compare(a->values.value(role).toString(), b->values.value(role).toString()); break; } } if (result != 0) { // The current sort role was sufficient to define an order return result; } // Fallback #1: Compare the text of the items result = stringCompare(itemA.text(), itemB.text(), collator); if (result != 0) { return result; } // Fallback #2: KFileItem::text() may not be unique in case UDS_DISPLAY_NAME is used result = stringCompare(itemA.name(), itemB.name(), collator); if (result != 0) { return result; } // Fallback #3: It must be assured that the sort order is always unique even if two values have been // equal. In this case a comparison of the URL is done which is unique in all cases // within KDirLister. return QString::compare(itemA.url().url(), itemB.url().url(), Qt::CaseSensitive); } int KFileItemModel::stringCompare(const QString& a, const QString& b, const QCollator& collator) const { if (m_naturalSorting) { return collator.compare(a, b); } const int result = QString::compare(a, b, collator.caseSensitivity()); if (result != 0 || collator.caseSensitivity() == Qt::CaseSensitive) { // Only return the result, if the strings are not equal. If they are equal by a case insensitive // comparison, still a deterministic sort order is required. A case sensitive // comparison is done as fallback. return result; } return QString::compare(a, b, Qt::CaseSensitive); } bool KFileItemModel::useMaximumUpdateInterval() const { return !m_dirLister->url().isLocalFile(); } QList > KFileItemModel::nameRoleGroups() const { Q_ASSERT(!m_itemData.isEmpty()); const int maxIndex = count() - 1; QList > groups; QString groupValue; QChar firstChar; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; } const QString name = m_itemData.at(i)->item.text(); // Use the first character of the name as group indication QChar newFirstChar = name.at(0).toUpper(); if (newFirstChar == QLatin1Char('~') && name.length() > 1) { newFirstChar = name.at(1).toUpper(); } if (firstChar != newFirstChar) { QString newGroupValue; if (newFirstChar.isLetter()) { // Try to find a matching group in the range 'A' to 'Z'. static std::vector lettersAtoZ; lettersAtoZ.reserve('Z' - 'A' + 1); if (lettersAtoZ.empty()) { for (char c = 'A'; c <= 'Z'; ++c) { lettersAtoZ.push_back(QLatin1Char(c)); } } auto localeAwareLessThan = [this](QChar c1, QChar c2) -> bool { return m_collator.compare(c1, c2) < 0; }; std::vector::iterator it = std::lower_bound(lettersAtoZ.begin(), lettersAtoZ.end(), newFirstChar, localeAwareLessThan); if (it != lettersAtoZ.end()) { if (localeAwareLessThan(newFirstChar, *it) && it != lettersAtoZ.begin()) { // newFirstChar belongs to the group preceding *it. // Example: for an umlaut 'A' in the German locale, *it would be 'B' now. --it; } newGroupValue = *it; } else { newGroupValue = newFirstChar; } } else if (newFirstChar >= QLatin1Char('0') && newFirstChar <= QLatin1Char('9')) { // Apply group '0 - 9' for any name that starts with a digit newGroupValue = i18nc("@title:group Groups that start with a digit", "0 - 9"); } else { newGroupValue = i18nc("@title:group", "Others"); } if (newGroupValue != groupValue) { groupValue = newGroupValue; groups.append(QPair(i, newGroupValue)); } firstChar = newFirstChar; } } return groups; } QList > KFileItemModel::sizeRoleGroups() const { Q_ASSERT(!m_itemData.isEmpty()); const int maxIndex = count() - 1; QList > groups; QString groupValue; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; } const KFileItem& item = m_itemData.at(i)->item; const KIO::filesize_t fileSize = !item.isNull() ? item.size() : ~0U; QString newGroupValue; if (!item.isNull() && item.isDir()) { newGroupValue = i18nc("@title:group Size", "Folders"); } else if (fileSize < 5 * 1024 * 1024) { newGroupValue = i18nc("@title:group Size", "Small"); } else if (fileSize < 10 * 1024 * 1024) { newGroupValue = i18nc("@title:group Size", "Medium"); } else { newGroupValue = i18nc("@title:group Size", "Big"); } if (newGroupValue != groupValue) { groupValue = newGroupValue; groups.append(QPair(i, newGroupValue)); } } return groups; } QList > KFileItemModel::timeRoleGroups(std::function fileTimeCb) const { Q_ASSERT(!m_itemData.isEmpty()); const int maxIndex = count() - 1; QList > groups; const QDate currentDate = QDate::currentDate(); QDate previousFileDate; QString groupValue; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; } const QDateTime fileTime = fileTimeCb(m_itemData.at(i)); const QDate fileDate = fileTime.date(); if (fileDate == previousFileDate) { // The current item is in the same group as the previous item continue; } previousFileDate = fileDate; const int daysDistance = fileDate.daysTo(currentDate); QString newGroupValue; if (currentDate.year() == fileDate.year() && currentDate.month() == fileDate.month()) { switch (daysDistance / 7) { case 0: switch (daysDistance) { case 0: newGroupValue = i18nc("@title:group Date", "Today"); break; case 1: newGroupValue = i18nc("@title:group Date", "Yesterday"); break; default: newGroupValue = fileTime.toString( i18nc("@title:group Date: The week day name: dddd", "dddd")); newGroupValue = i18nc("Can be used to script translation of \"dddd\"" "with context @title:group Date", "%1", newGroupValue); } break; case 1: newGroupValue = i18nc("@title:group Date", "One Week Ago"); break; case 2: newGroupValue = i18nc("@title:group Date", "Two Weeks Ago"); break; case 3: newGroupValue = i18nc("@title:group Date", "Three Weeks Ago"); break; case 4: case 5: newGroupValue = i18nc("@title:group Date", "Earlier this Month"); break; default: Q_ASSERT(false); } } else { const QDate lastMonthDate = currentDate.addMonths(-1); if (lastMonthDate.year() == fileDate.year() && lastMonthDate.month() == fileDate.month()) { if (daysDistance == 1) { newGroupValue = fileTime.toString(i18nc("@title:group Date: " "MMMM is full month name in current locale, and yyyy is " "full year number", "'Yesterday' (MMMM, yyyy)")); newGroupValue = i18nc("Can be used to script translation of " "\"'Yesterday' (MMMM, yyyy)\" with context @title:group Date", "%1", newGroupValue); } else if (daysDistance <= 7) { newGroupValue = fileTime.toString(i18nc("@title:group Date: " "The week day name: dddd, MMMM is full month name " "in current locale, and yyyy is full year number", "dddd (MMMM, yyyy)")); newGroupValue = i18nc("Can be used to script translation of " "\"dddd (MMMM, yyyy)\" with context @title:group Date", "%1", newGroupValue); } else if (daysDistance <= 7 * 2) { newGroupValue = fileTime.toString(i18nc("@title:group Date: " "MMMM is full month name in current locale, and yyyy is " "full year number", "'One Week Ago' (MMMM, yyyy)")); newGroupValue = i18nc("Can be used to script translation of " "\"'One Week Ago' (MMMM, yyyy)\" with context @title:group Date", "%1", newGroupValue); } else if (daysDistance <= 7 * 3) { newGroupValue = fileTime.toString(i18nc("@title:group Date: " "MMMM is full month name in current locale, and yyyy is " "full year number", "'Two Weeks Ago' (MMMM, yyyy)")); newGroupValue = i18nc("Can be used to script translation of " "\"'Two Weeks Ago' (MMMM, yyyy)\" with context @title:group Date", "%1", newGroupValue); } else if (daysDistance <= 7 * 4) { newGroupValue = fileTime.toString(i18nc("@title:group Date: " "MMMM is full month name in current locale, and yyyy is " "full year number", "'Three Weeks Ago' (MMMM, yyyy)")); newGroupValue = i18nc("Can be used to script translation of " "\"'Three Weeks Ago' (MMMM, yyyy)\" with context @title:group Date", "%1", newGroupValue); } else { newGroupValue = fileTime.toString(i18nc("@title:group Date: " "MMMM is full month name in current locale, and yyyy is " "full year number", "'Earlier on' MMMM, yyyy")); newGroupValue = i18nc("Can be used to script translation of " "\"'Earlier on' MMMM, yyyy\" with context @title:group Date", "%1", newGroupValue); } } else { newGroupValue = fileTime.toString(i18nc("@title:group " "The month and year: MMMM is full month name in current locale, " "and yyyy is full year number", "MMMM, yyyy")); newGroupValue = i18nc("Can be used to script translation of " "\"MMMM, yyyy\" with context @title:group Date", "%1", newGroupValue); } } if (newGroupValue != groupValue) { groupValue = newGroupValue; groups.append(QPair(i, newGroupValue)); } } return groups; } QList > KFileItemModel::permissionRoleGroups() const { Q_ASSERT(!m_itemData.isEmpty()); const int maxIndex = count() - 1; QList > groups; QString permissionsString; QString groupValue; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; } const ItemData* itemData = m_itemData.at(i); const QString newPermissionsString = itemData->values.value("permissions").toString(); if (newPermissionsString == permissionsString) { continue; } permissionsString = newPermissionsString; const QFileInfo info(itemData->item.url().toLocalFile()); // Set user string QString user; if (info.permission(QFile::ReadUser)) { user = i18nc("@item:intext Access permission, concatenated", "Read, "); } if (info.permission(QFile::WriteUser)) { user += i18nc("@item:intext Access permission, concatenated", "Write, "); } if (info.permission(QFile::ExeUser)) { user += i18nc("@item:intext Access permission, concatenated", "Execute, "); } user = user.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : user.mid(0, user.count() - 2); // Set group string QString group; if (info.permission(QFile::ReadGroup)) { group = i18nc("@item:intext Access permission, concatenated", "Read, "); } if (info.permission(QFile::WriteGroup)) { group += i18nc("@item:intext Access permission, concatenated", "Write, "); } if (info.permission(QFile::ExeGroup)) { group += i18nc("@item:intext Access permission, concatenated", "Execute, "); } group = group.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : group.mid(0, group.count() - 2); // Set others string QString others; if (info.permission(QFile::ReadOther)) { others = i18nc("@item:intext Access permission, concatenated", "Read, "); } if (info.permission(QFile::WriteOther)) { others += i18nc("@item:intext Access permission, concatenated", "Write, "); } if (info.permission(QFile::ExeOther)) { others += i18nc("@item:intext Access permission, concatenated", "Execute, "); } others = others.isEmpty() ? i18nc("@item:intext Access permission, concatenated", "Forbidden") : others.mid(0, others.count() - 2); const QString newGroupValue = i18nc("@title:group Files and folders by permissions", "User: %1 | Group: %2 | Others: %3", user, group, others); if (newGroupValue != groupValue) { groupValue = newGroupValue; groups.append(QPair(i, newGroupValue)); } } return groups; } QList > KFileItemModel::ratingRoleGroups() const { Q_ASSERT(!m_itemData.isEmpty()); const int maxIndex = count() - 1; QList > groups; int groupValue = -1; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; } const int newGroupValue = m_itemData.at(i)->values.value("rating", 0).toInt(); if (newGroupValue != groupValue) { groupValue = newGroupValue; groups.append(QPair(i, newGroupValue)); } } return groups; } QList > KFileItemModel::genericStringRoleGroups(const QByteArray& role) const { Q_ASSERT(!m_itemData.isEmpty()); const int maxIndex = count() - 1; QList > groups; bool isFirstGroupValue = true; QString groupValue; for (int i = 0; i <= maxIndex; ++i) { if (isChildItem(i)) { continue; } const QString newGroupValue = m_itemData.at(i)->values.value(role).toString(); if (newGroupValue != groupValue || isFirstGroupValue) { groupValue = newGroupValue; groups.append(QPair(i, newGroupValue)); isFirstGroupValue = false; } } return groups; } void KFileItemModel::emitSortProgress(int resolvedCount) { // Be tolerant against a resolvedCount with a wrong range. // Although there should not be a case where KFileItemModelRolesUpdater // (= caller) provides a wrong range, it is important to emit // a useful progress information even if there is an unexpected // implementation issue. const int itemCount = count(); if (resolvedCount >= itemCount) { m_sortingProgressPercent = -1; if (m_resortAllItemsTimer->isActive()) { m_resortAllItemsTimer->stop(); resortAllItems(); } emit directorySortingProgress(100); } else if (itemCount > 0) { resolvedCount = qBound(0, resolvedCount, itemCount); const int progress = resolvedCount * 100 / itemCount; if (m_sortingProgressPercent != progress) { m_sortingProgressPercent = progress; emit directorySortingProgress(progress); } } } const KFileItemModel::RoleInfoMap* KFileItemModel::rolesInfoMap(int& count) { static const RoleInfoMap rolesInfoMap[] = { // | role | roleType | role translation | group translation | requires Baloo | requires indexer { nullptr, NoRole, nullptr, nullptr, nullptr, nullptr, false, false }, { "text", NameRole, I18N_NOOP2_NOSTRIP("@label", "Name"), nullptr, nullptr, false, false }, { "size", SizeRole, I18N_NOOP2_NOSTRIP("@label", "Size"), nullptr, nullptr, false, false }, { "modificationtime", ModificationTimeRole, I18N_NOOP2_NOSTRIP("@label", "Modified"), nullptr, nullptr, false, false }, { "creationtime", CreationTimeRole, I18N_NOOP2_NOSTRIP("@label", "Created"), nullptr, nullptr, false, false }, { "accesstime", AccessTimeRole, I18N_NOOP2_NOSTRIP("@label", "Accessed"), nullptr, nullptr, false, false }, { "type", TypeRole, I18N_NOOP2_NOSTRIP("@label", "Type"), nullptr, nullptr, false, false }, { "rating", RatingRole, I18N_NOOP2_NOSTRIP("@label", "Rating"), nullptr, nullptr, true, false }, { "tags", TagsRole, I18N_NOOP2_NOSTRIP("@label", "Tags"), nullptr, nullptr, true, false }, { "comment", CommentRole, I18N_NOOP2_NOSTRIP("@label", "Comment"), nullptr, nullptr, true, false }, { "title", TitleRole, I18N_NOOP2_NOSTRIP("@label", "Title"), I18N_NOOP2_NOSTRIP("@label", "Document"), true, true }, { "wordCount", WordCountRole, I18N_NOOP2_NOSTRIP("@label", "Word Count"), I18N_NOOP2_NOSTRIP("@label", "Document"), true, true }, { "lineCount", LineCountRole, I18N_NOOP2_NOSTRIP("@label", "Line Count"), I18N_NOOP2_NOSTRIP("@label", "Document"), true, true }, { "imageDateTime", ImageDateTimeRole, I18N_NOOP2_NOSTRIP("@label", "Date Photographed"), I18N_NOOP2_NOSTRIP("@label", "Image"), true, true }, { "imageSize", ImageSizeRole, I18N_NOOP2_NOSTRIP("@label", "Image Size"), I18N_NOOP2_NOSTRIP("@label", "Image"), true, true }, { "orientation", OrientationRole, I18N_NOOP2_NOSTRIP("@label", "Orientation"), I18N_NOOP2_NOSTRIP("@label", "Image"), true, true }, { "artist", ArtistRole, I18N_NOOP2_NOSTRIP("@label", "Artist"), I18N_NOOP2_NOSTRIP("@label", "Audio"), true, true }, { "genre", GenreRole, I18N_NOOP2_NOSTRIP("@label", "Genre"), I18N_NOOP2_NOSTRIP("@label", "Audio"), true, true }, { "album", AlbumRole, I18N_NOOP2_NOSTRIP("@label", "Album"), I18N_NOOP2_NOSTRIP("@label", "Audio"), true, true }, { "duration", DurationRole, I18N_NOOP2_NOSTRIP("@label", "Duration"), I18N_NOOP2_NOSTRIP("@label", "Audio"), true, true }, { "bitrate", BitrateRole, I18N_NOOP2_NOSTRIP("@label", "Bitrate"), I18N_NOOP2_NOSTRIP("@label", "Audio"), true, true }, { "track", TrackRole, I18N_NOOP2_NOSTRIP("@label", "Track"), I18N_NOOP2_NOSTRIP("@label", "Audio"), true, true }, { "releaseYear", ReleaseYearRole, I18N_NOOP2_NOSTRIP("@label", "Release Year"), I18N_NOOP2_NOSTRIP("@label", "Audio"), true, true }, { "path", PathRole, I18N_NOOP2_NOSTRIP("@label", "Path"), I18N_NOOP2_NOSTRIP("@label", "Other"), false, false }, { "deletiontime",DeletionTimeRole,I18N_NOOP2_NOSTRIP("@label", "Deletion Time"), I18N_NOOP2_NOSTRIP("@label", "Other"), false, false }, { "destination", DestinationRole, I18N_NOOP2_NOSTRIP("@label", "Link Destination"), I18N_NOOP2_NOSTRIP("@label", "Other"), false, false }, { "originUrl", OriginUrlRole, I18N_NOOP2_NOSTRIP("@label", "Downloaded From"), I18N_NOOP2_NOSTRIP("@label", "Other"), true, false }, { "permissions", PermissionsRole, I18N_NOOP2_NOSTRIP("@label", "Permissions"), I18N_NOOP2_NOSTRIP("@label", "Other"), false, false }, { "owner", OwnerRole, I18N_NOOP2_NOSTRIP("@label", "Owner"), I18N_NOOP2_NOSTRIP("@label", "Other"), false, false }, { "group", GroupRole, I18N_NOOP2_NOSTRIP("@label", "User Group"), I18N_NOOP2_NOSTRIP("@label", "Other"), false, false }, }; count = sizeof(rolesInfoMap) / sizeof(RoleInfoMap); return rolesInfoMap; } void KFileItemModel::determineMimeTypes(const KFileItemList& items, int timeout) { QElapsedTimer timer; timer.start(); foreach (const KFileItem& item, items) { // krazy:exclude=foreach // Only determine mime types for files here. For directories, // KFileItem::determineMimeType() reads the .directory file inside to // load the icon, but this is not necessary at all if we just need the // type. Some special code for setting the correct mime type for // directories is in retrieveData(). if (!item.isDir()) { item.determineMimeType(); } if (timer.elapsed() > timeout) { // Don't block the user interface, let the remaining items // be resolved asynchronously. return; } } } QByteArray KFileItemModel::sharedValue(const QByteArray& value) { static QSet pool; const QSet::const_iterator it = pool.constFind(value); if (it != pool.constEnd()) { return *it; } else { pool.insert(value); return value; } } bool KFileItemModel::isConsistent() const { // m_items may contain less items than m_itemData because m_items // is populated lazily, see KFileItemModel::index(const QUrl& url). if (m_items.count() > m_itemData.count()) { return false; } for (int i = 0, iMax = count(); i < iMax; ++i) { // Check if m_items and m_itemData are consistent. const KFileItem item = fileItem(i); if (item.isNull()) { qCWarning(DolphinDebug) << "Item" << i << "is null"; return false; } const int itemIndex = index(item); if (itemIndex != i) { qCWarning(DolphinDebug) << "Item" << i << "has a wrong index:" << itemIndex; return false; } // Check if the items are sorted correctly. if (i > 0 && !lessThan(m_itemData.at(i - 1), m_itemData.at(i), m_collator)) { qCWarning(DolphinDebug) << "The order of items" << i - 1 << "and" << i << "is wrong:" << fileItem(i - 1) << fileItem(i); return false; } // Check if all parent-child relationships are consistent. const ItemData* data = m_itemData.at(i); const ItemData* parent = data->parent; if (parent) { if (expandedParentsCount(data) != expandedParentsCount(parent) + 1) { qCWarning(DolphinDebug) << "expandedParentsCount is inconsistent for parent" << parent->item << "and child" << data->item; return false; } const int parentIndex = index(parent->item); if (parentIndex >= i) { qCWarning(DolphinDebug) << "Index" << parentIndex << "of parent" << parent->item << "is not smaller than index" << i << "of child" << data->item; return false; } } } return true; } diff --git a/src/kitemviews/kfileitemmodelrolesupdater.cpp b/src/kitemviews/kfileitemmodelrolesupdater.cpp index a08f1ae15..ddbed16a8 100644 --- a/src/kitemviews/kfileitemmodelrolesupdater.cpp +++ b/src/kitemviews/kfileitemmodelrolesupdater.cpp @@ -1,1202 +1,1198 @@ /*************************************************************************** * 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 "kfileitemmodelrolesupdater.h" #include "kfileitemmodel.h" #include #include #include -#include #include #include #include #include #include #include #include "private/kpixmapmodifier.h" #include "private/kdirectorycontentscounter.h" #include #include -#include #include #include -#include - #ifdef HAVE_BALOO #include "private/kbaloorolesprovider.h" #include #include #endif // #define KFILEITEMMODELROLESUPDATER_DEBUG namespace { // Maximum time in ms that the KFileItemModelRolesUpdater // may perform a blocking operation const int MaxBlockTimeout = 200; // If the number of items is smaller than ResolveAllItemsLimit, // the roles of all items will be resolved. const int ResolveAllItemsLimit = 500; // Not only the visible area, but up to ReadAheadPages before and after // this area will be resolved. const int ReadAheadPages = 5; } KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QObject* parent) : QObject(parent), m_state(Idle), m_previewChangedDuringPausing(false), m_iconSizeChangedDuringPausing(false), m_rolesChangedDuringPausing(false), m_previewShown(false), m_enlargeSmallPreviews(true), m_clearPreviews(false), m_finishedItems(), m_model(model), m_iconSize(), m_firstVisibleIndex(0), m_lastVisibleIndex(-1), m_maximumVisibleItems(50), m_roles(), m_resolvableRoles(), m_enabledPlugins(), m_pendingSortRoleItems(), m_pendingIndexes(), m_pendingPreviewItems(), m_previewJob(), m_recentlyChangedItemsTimer(nullptr), m_recentlyChangedItems(), m_changedItems(), m_directoryContentsCounter(nullptr) #ifdef HAVE_BALOO , m_balooFileMonitor(nullptr) #endif { Q_ASSERT(model); const KConfigGroup globalConfig(KSharedConfig::openConfig(), "PreviewSettings"); m_enabledPlugins = globalConfig.readEntry("Plugins", KIO::PreviewJob::defaultPlugins()); connect(m_model, &KFileItemModel::itemsInserted, this, &KFileItemModelRolesUpdater::slotItemsInserted); connect(m_model, &KFileItemModel::itemsRemoved, this, &KFileItemModelRolesUpdater::slotItemsRemoved); connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); connect(m_model, &KFileItemModel::itemsMoved, this, &KFileItemModelRolesUpdater::slotItemsMoved); connect(m_model, &KFileItemModel::sortRoleChanged, this, &KFileItemModelRolesUpdater::slotSortRoleChanged); // Use a timer to prevent that each call of slotItemsChanged() results in a synchronous // resolving of the roles. Postpone the resolving until no update has been done for 1 second. m_recentlyChangedItemsTimer = new QTimer(this); m_recentlyChangedItemsTimer->setInterval(1000); m_recentlyChangedItemsTimer->setSingleShot(true); connect(m_recentlyChangedItemsTimer, &QTimer::timeout, this, &KFileItemModelRolesUpdater::resolveRecentlyChangedItems); m_resolvableRoles.insert("size"); m_resolvableRoles.insert("type"); m_resolvableRoles.insert("isExpandable"); #ifdef HAVE_BALOO m_resolvableRoles += KBalooRolesProvider::instance().roles(); #endif m_directoryContentsCounter = new KDirectoryContentsCounter(m_model, this); connect(m_directoryContentsCounter, &KDirectoryContentsCounter::result, this, &KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived); auto plugins = KPluginLoader::instantiatePlugins(QStringLiteral("kf5/overlayicon"), nullptr, qApp); foreach (QObject *it, plugins) { auto plugin = qobject_cast(it); if (plugin) { m_overlayIconsPlugin.append(plugin); connect(plugin, &KOverlayIconPlugin::overlaysChanged, this, &KFileItemModelRolesUpdater::slotOverlaysChanged); } else { // not our/valid plugin, so delete the created object it->deleteLater(); } } } KFileItemModelRolesUpdater::~KFileItemModelRolesUpdater() { killPreviewJob(); } void KFileItemModelRolesUpdater::setIconSize(const QSize& size) { if (size != m_iconSize) { m_iconSize = size; if (m_state == Paused) { m_iconSizeChangedDuringPausing = true; } else if (m_previewShown) { // An icon size change requires the regenerating of // all previews m_finishedItems.clear(); startUpdating(); } } } QSize KFileItemModelRolesUpdater::iconSize() const { return m_iconSize; } void KFileItemModelRolesUpdater::setVisibleIndexRange(int index, int count) { if (index < 0) { index = 0; } if (count < 0) { count = 0; } if (index == m_firstVisibleIndex && count == m_lastVisibleIndex - m_firstVisibleIndex + 1) { // The range has not been changed return; } m_firstVisibleIndex = index; m_lastVisibleIndex = qMin(index + count - 1, m_model->count() - 1); startUpdating(); } void KFileItemModelRolesUpdater::setMaximumVisibleItems(int count) { m_maximumVisibleItems = count; } void KFileItemModelRolesUpdater::setPreviewsShown(bool show) { if (show == m_previewShown) { return; } m_previewShown = show; if (!show) { m_clearPreviews = true; } updateAllPreviews(); } bool KFileItemModelRolesUpdater::previewsShown() const { return m_previewShown; } void KFileItemModelRolesUpdater::setEnlargeSmallPreviews(bool enlarge) { if (enlarge != m_enlargeSmallPreviews) { m_enlargeSmallPreviews = enlarge; if (m_previewShown) { updateAllPreviews(); } } } bool KFileItemModelRolesUpdater::enlargeSmallPreviews() const { return m_enlargeSmallPreviews; } void KFileItemModelRolesUpdater::setEnabledPlugins(const QStringList& list) { if (m_enabledPlugins != list) { m_enabledPlugins = list; if (m_previewShown) { updateAllPreviews(); } } } void KFileItemModelRolesUpdater::setPaused(bool paused) { if (paused == (m_state == Paused)) { return; } if (paused) { m_state = Paused; killPreviewJob(); } else { const bool updatePreviews = (m_iconSizeChangedDuringPausing && m_previewShown) || m_previewChangedDuringPausing; const bool resolveAll = updatePreviews || m_rolesChangedDuringPausing; if (resolveAll) { m_finishedItems.clear(); } m_iconSizeChangedDuringPausing = false; m_previewChangedDuringPausing = false; m_rolesChangedDuringPausing = false; if (!m_pendingSortRoleItems.isEmpty()) { m_state = ResolvingSortRole; resolveNextSortRole(); } else { m_state = Idle; } startUpdating(); } } void KFileItemModelRolesUpdater::setRoles(const QSet& roles) { if (m_roles != roles) { m_roles = roles; #ifdef HAVE_BALOO // Check whether there is at least one role that must be resolved // with the help of Baloo. If this is the case, a (quite expensive) // resolving will be done in KFileItemModelRolesUpdater::rolesData() and // the role gets watched for changes. const KBalooRolesProvider& rolesProvider = KBalooRolesProvider::instance(); bool hasBalooRole = false; QSetIterator it(roles); while (it.hasNext()) { const QByteArray& role = it.next(); if (rolesProvider.roles().contains(role)) { hasBalooRole = true; break; } } if (hasBalooRole && m_balooConfig.fileIndexingEnabled() && !m_balooFileMonitor) { m_balooFileMonitor = new Baloo::FileMonitor(this); connect(m_balooFileMonitor, &Baloo::FileMonitor::fileMetaDataChanged, this, &KFileItemModelRolesUpdater::applyChangedBalooRoles); } else if (!hasBalooRole && m_balooFileMonitor) { delete m_balooFileMonitor; m_balooFileMonitor = nullptr; } #endif if (m_state == Paused) { m_rolesChangedDuringPausing = true; } else { startUpdating(); } } } QSet KFileItemModelRolesUpdater::roles() const { return m_roles; } bool KFileItemModelRolesUpdater::isPaused() const { return m_state == Paused; } QStringList KFileItemModelRolesUpdater::enabledPlugins() const { return m_enabledPlugins; } void KFileItemModelRolesUpdater::slotItemsInserted(const KItemRangeList& itemRanges) { QElapsedTimer timer; timer.start(); // Determine the sort role synchronously for as many items as possible. if (m_resolvableRoles.contains(m_model->sortRole())) { int insertedCount = 0; foreach (const KItemRange& range, itemRanges) { const int lastIndex = insertedCount + range.index + range.count - 1; for (int i = insertedCount + range.index; i <= lastIndex; ++i) { if (timer.elapsed() < MaxBlockTimeout) { applySortRole(i); } else { m_pendingSortRoleItems.insert(m_model->fileItem(i)); } } insertedCount += range.count; } applySortProgressToModel(); // If there are still items whose sort role is unknown, check if the // asynchronous determination of the sort role is already in progress, // and start it if that is not the case. if (!m_pendingSortRoleItems.isEmpty() && m_state != ResolvingSortRole) { killPreviewJob(); m_state = ResolvingSortRole; resolveNextSortRole(); } } startUpdating(); } void KFileItemModelRolesUpdater::slotItemsRemoved(const KItemRangeList& itemRanges) { Q_UNUSED(itemRanges); const bool allItemsRemoved = (m_model->count() == 0); #ifdef HAVE_BALOO if (m_balooFileMonitor) { // Don't let the FileWatcher watch for removed items if (allItemsRemoved) { m_balooFileMonitor->clear(); } else { QStringList newFileList; foreach (const QString& file, m_balooFileMonitor->files()) { if (m_model->index(QUrl::fromLocalFile(file)) >= 0) { newFileList.append(file); } } m_balooFileMonitor->setFiles(newFileList); } } #endif if (allItemsRemoved) { m_state = Idle; m_finishedItems.clear(); m_pendingSortRoleItems.clear(); m_pendingIndexes.clear(); m_pendingPreviewItems.clear(); m_recentlyChangedItems.clear(); m_recentlyChangedItemsTimer->stop(); m_changedItems.clear(); killPreviewJob(); } else { // Only remove the items from m_finishedItems. They will be removed // from the other sets later on. QSet::iterator it = m_finishedItems.begin(); while (it != m_finishedItems.end()) { if (m_model->index(*it) < 0) { it = m_finishedItems.erase(it); } else { ++it; } } // The visible items might have changed. startUpdating(); } } void KFileItemModelRolesUpdater::slotItemsMoved(const KItemRange& itemRange, QList movedToIndexes) { Q_UNUSED(itemRange); Q_UNUSED(movedToIndexes); // The visible items might have changed. startUpdating(); } void KFileItemModelRolesUpdater::slotItemsChanged(const KItemRangeList& itemRanges, const QSet& roles) { Q_UNUSED(roles); // Find out if slotItemsChanged() has been done recently. If that is the // case, resolving the roles is postponed until a timer has exceeded // to prevent expensive repeated updates if files are updated frequently. const bool itemsChangedRecently = m_recentlyChangedItemsTimer->isActive(); QSet& targetSet = itemsChangedRecently ? m_recentlyChangedItems : m_changedItems; foreach (const KItemRange& itemRange, itemRanges) { int index = itemRange.index; for (int count = itemRange.count; count > 0; --count) { const KFileItem item = m_model->fileItem(index); targetSet.insert(item); ++index; } } m_recentlyChangedItemsTimer->start(); if (!itemsChangedRecently) { updateChangedItems(); } } void KFileItemModelRolesUpdater::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous) { Q_UNUSED(current); Q_UNUSED(previous); if (m_resolvableRoles.contains(current)) { m_pendingSortRoleItems.clear(); m_finishedItems.clear(); const int count = m_model->count(); QElapsedTimer timer; timer.start(); // Determine the sort role synchronously for as many items as possible. for (int index = 0; index < count; ++index) { if (timer.elapsed() < MaxBlockTimeout) { applySortRole(index); } else { m_pendingSortRoleItems.insert(m_model->fileItem(index)); } } applySortProgressToModel(); if (!m_pendingSortRoleItems.isEmpty()) { // Trigger the asynchronous determination of the sort role. killPreviewJob(); m_state = ResolvingSortRole; resolveNextSortRole(); } } else { m_state = Idle; m_pendingSortRoleItems.clear(); applySortProgressToModel(); } } void KFileItemModelRolesUpdater::slotGotPreview(const KFileItem& item, const QPixmap& pixmap) { if (m_state != PreviewJobRunning) { return; } m_changedItems.remove(item); const int index = m_model->index(item); if (index < 0) { return; } QPixmap scaledPixmap = pixmap; const QString mimeType = item.mimetype(); const int slashIndex = mimeType.indexOf(QLatin1Char('/')); const bool isFontPreview = mimeType.right(slashIndex).contains(QLatin1String("font")); const bool isFolderPreview = item.isDir(); const bool isWindowsExePreview = mimeType == QLatin1String("application/x-ms-dos-executable") || mimeType == QLatin1String("application/x-msdownload"); if (!isFolderPreview && !isFontPreview && !isWindowsExePreview) { if (m_enlargeSmallPreviews) { KPixmapModifier::applyFrame(scaledPixmap, m_iconSize); } else { // Assure that small previews don't get enlarged. Instead they // should be shown centered within the frame. const QSize contentSize = KPixmapModifier::sizeInsideFrame(m_iconSize); const bool enlargingRequired = scaledPixmap.width() < contentSize.width() && scaledPixmap.height() < contentSize.height(); if (enlargingRequired) { QSize frameSize = scaledPixmap.size() / scaledPixmap.devicePixelRatio(); frameSize.scale(m_iconSize, Qt::KeepAspectRatio); QPixmap largeFrame(frameSize); largeFrame.fill(Qt::transparent); KPixmapModifier::applyFrame(largeFrame, frameSize); QPainter painter(&largeFrame); painter.drawPixmap((largeFrame.width() - scaledPixmap.width() / scaledPixmap.devicePixelRatio()) / 2, (largeFrame.height() - scaledPixmap.height() / scaledPixmap.devicePixelRatio()) / 2, scaledPixmap); scaledPixmap = largeFrame; } else { // The image must be shrinked as it is too large to fit into // the available icon size KPixmapModifier::applyFrame(scaledPixmap, m_iconSize); } } } else { KPixmapModifier::scale(scaledPixmap, m_iconSize); } QHash data = rolesData(item); const QStringList overlays = data["iconOverlays"].toStringList(); // Strangely KFileItem::overlays() returns empty string-values, so // we need to check first whether an overlay must be drawn at all. // It is more efficient to do it here, as KIconLoader::drawOverlays() // assumes that an overlay will be drawn and has some additional // setup time. foreach (const QString& overlay, overlays) { if (!overlay.isEmpty()) { // There is at least one overlay, draw all overlays above m_pixmap // and cancel the check KIconLoader::global()->drawOverlays(overlays, scaledPixmap, KIconLoader::Desktop); break; } } data.insert("iconPixmap", scaledPixmap); disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); m_model->setData(index, data); connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); m_finishedItems.insert(item); } void KFileItemModelRolesUpdater::slotPreviewFailed(const KFileItem& item) { if (m_state != PreviewJobRunning) { return; } m_changedItems.remove(item); const int index = m_model->index(item); if (index >= 0) { QHash data; data.insert("iconPixmap", QPixmap()); disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); m_model->setData(index, data); connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); applyResolvedRoles(index, ResolveAll); m_finishedItems.insert(item); } } void KFileItemModelRolesUpdater::slotPreviewJobFinished() { m_previewJob = nullptr; if (m_state != PreviewJobRunning) { return; } m_state = Idle; if (!m_pendingPreviewItems.isEmpty()) { startPreviewJob(); } else { if (!m_changedItems.isEmpty()) { updateChangedItems(); } } } void KFileItemModelRolesUpdater::resolveNextSortRole() { if (m_state != ResolvingSortRole) { return; } QSet::iterator it = m_pendingSortRoleItems.begin(); while (it != m_pendingSortRoleItems.end()) { const KFileItem item = *it; const int index = m_model->index(item); // Continue if the sort role has already been determined for the // item, and the item has not been changed recently. if (!m_changedItems.contains(item) && m_model->data(index).contains(m_model->sortRole())) { it = m_pendingSortRoleItems.erase(it); continue; } applySortRole(index); m_pendingSortRoleItems.erase(it); break; } if (!m_pendingSortRoleItems.isEmpty()) { applySortProgressToModel(); QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextSortRole); } else { m_state = Idle; // Prevent that we try to update the items twice. disconnect(m_model, &KFileItemModel::itemsMoved, this, &KFileItemModelRolesUpdater::slotItemsMoved); applySortProgressToModel(); connect(m_model, &KFileItemModel::itemsMoved, this, &KFileItemModelRolesUpdater::slotItemsMoved); startUpdating(); } } void KFileItemModelRolesUpdater::resolveNextPendingRoles() { if (m_state != ResolvingAllRoles) { return; } while (!m_pendingIndexes.isEmpty()) { const int index = m_pendingIndexes.takeFirst(); const KFileItem item = m_model->fileItem(index); if (m_finishedItems.contains(item)) { continue; } applyResolvedRoles(index, ResolveAll); m_finishedItems.insert(item); m_changedItems.remove(item); break; } if (!m_pendingIndexes.isEmpty()) { QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextPendingRoles); } else { m_state = Idle; if (m_clearPreviews) { // Only go through the list if there are items which might still have previews. if (m_finishedItems.count() != m_model->count()) { QHash data; data.insert("iconPixmap", QPixmap()); disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); for (int index = 0; index <= m_model->count(); ++index) { if (m_model->data(index).contains("iconPixmap")) { m_model->setData(index, data); } } connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); } m_clearPreviews = false; } if (!m_changedItems.isEmpty()) { updateChangedItems(); } } } void KFileItemModelRolesUpdater::resolveRecentlyChangedItems() { m_changedItems += m_recentlyChangedItems; m_recentlyChangedItems.clear(); updateChangedItems(); } void KFileItemModelRolesUpdater::applyChangedBalooRoles(const QString& file) { #ifdef HAVE_BALOO const KFileItem item = m_model->fileItem(QUrl::fromLocalFile(file)); if (item.isNull()) { // itemUrl is not in the model anymore, probably because // the corresponding file has been deleted in the meantime. return; } applyChangedBalooRolesForItem(item); #endif } void KFileItemModelRolesUpdater::applyChangedBalooRolesForItem(const KFileItem &item) { #ifdef HAVE_BALOO Baloo::File file(item.localPath()); file.load(); const KBalooRolesProvider& rolesProvider = KBalooRolesProvider::instance(); QHash data; foreach (const QByteArray& role, rolesProvider.roles()) { // Overwrite all the role values with an empty QVariant, because the roles // provider doesn't overwrite it when the property value list is empty. // See bug 322348 data.insert(role, QVariant()); } QHashIterator it(rolesProvider.roleValues(file, m_roles)); while (it.hasNext()) { it.next(); data.insert(it.key(), it.value()); } disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); const int index = m_model->index(item); m_model->setData(index, data); connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); #else #ifndef Q_CC_MSVC Q_UNUSED(item); #endif #endif } void KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived(const QString& path, int count) { const bool getSizeRole = m_roles.contains("size"); const bool getIsExpandableRole = m_roles.contains("isExpandable"); if (getSizeRole || getIsExpandableRole) { const int index = m_model->index(QUrl::fromLocalFile(path)); if (index >= 0) { QHash data; if (getSizeRole) { data.insert("size", count); } if (getIsExpandableRole) { data.insert("isExpandable", count > 0); } disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); m_model->setData(index, data); connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); } } } void KFileItemModelRolesUpdater::startUpdating() { if (m_state == Paused) { return; } if (m_finishedItems.count() == m_model->count()) { // All roles have been resolved already. m_state = Idle; return; } // Terminate all updates that are currently active. killPreviewJob(); m_pendingIndexes.clear(); QElapsedTimer timer; timer.start(); // Determine the icons for the visible items synchronously. updateVisibleIcons(); // A detailed update of the items in and near the visible area // only makes sense if sorting is finished. if (m_state == ResolvingSortRole) { return; } // Start the preview job or the asynchronous resolving of all roles. QList indexes = indexesToResolve(); if (m_previewShown) { m_pendingPreviewItems.clear(); m_pendingPreviewItems.reserve(indexes.count()); foreach (int index, indexes) { const KFileItem item = m_model->fileItem(index); if (!m_finishedItems.contains(item)) { m_pendingPreviewItems.append(item); } } startPreviewJob(); } else { m_pendingIndexes = indexes; // Trigger the asynchronous resolving of all roles. m_state = ResolvingAllRoles; QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextPendingRoles); } } void KFileItemModelRolesUpdater::updateVisibleIcons() { int lastVisibleIndex = m_lastVisibleIndex; if (lastVisibleIndex <= 0) { // Guess a reasonable value for the last visible index if the view // has not told us about the real value yet. lastVisibleIndex = qMin(m_firstVisibleIndex + m_maximumVisibleItems, m_model->count() - 1); if (lastVisibleIndex <= 0) { lastVisibleIndex = qMin(200, m_model->count() - 1); } } QElapsedTimer timer; timer.start(); // Try to determine the final icons for all visible items. int index; for (index = m_firstVisibleIndex; index <= lastVisibleIndex && timer.elapsed() < MaxBlockTimeout; ++index) { applyResolvedRoles(index, ResolveFast); } // KFileItemListView::initializeItemListWidget(KItemListWidget*) will load // preliminary icons (i.e., without mime type determination) for the // remaining items. } void KFileItemModelRolesUpdater::startPreviewJob() { m_state = PreviewJobRunning; if (m_pendingPreviewItems.isEmpty()) { QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::slotPreviewJobFinished); return; } // PreviewJob internally caches items always with the size of // 128 x 128 pixels or 256 x 256 pixels. A (slow) downscaling is done // by PreviewJob if a smaller size is requested. For images KFileItemModelRolesUpdater must // do a downscaling anyhow because of the frame, so in this case only the provided // cache sizes are requested. const QSize cacheSize = (m_iconSize.width() > 128) || (m_iconSize.height() > 128) ? QSize(256, 256) : QSize(128, 128); // KIO::filePreview() will request the MIME-type of all passed items, which (in the // worst case) might block the application for several seconds. To prevent such // a blocking, we only pass items with known mime type to the preview job. const int count = m_pendingPreviewItems.count(); KFileItemList itemSubSet; itemSubSet.reserve(count); if (m_pendingPreviewItems.first().isMimeTypeKnown()) { // Some mime types are known already, probably because they were // determined when loading the icons for the visible items. Start // a preview job for all items at the beginning of the list which // have a known mime type. do { itemSubSet.append(m_pendingPreviewItems.takeFirst()); } while (!m_pendingPreviewItems.isEmpty() && m_pendingPreviewItems.first().isMimeTypeKnown()); } else { // Determine mime types for MaxBlockTimeout ms, and start a preview // job for the corresponding items. QElapsedTimer timer; timer.start(); do { const KFileItem item = m_pendingPreviewItems.takeFirst(); item.determineMimeType(); itemSubSet.append(item); } while (!m_pendingPreviewItems.isEmpty() && timer.elapsed() < MaxBlockTimeout); } KIO::PreviewJob* job = new KIO::PreviewJob(itemSubSet, cacheSize, &m_enabledPlugins); job->setIgnoreMaximumSize(itemSubSet.first().isLocalFile()); if (job->uiDelegate()) { KJobWidgets::setWindow(job, qApp->activeWindow()); } connect(job, &KIO::PreviewJob::gotPreview, this, &KFileItemModelRolesUpdater::slotGotPreview); connect(job, &KIO::PreviewJob::failed, this, &KFileItemModelRolesUpdater::slotPreviewFailed); connect(job, &KIO::PreviewJob::finished, this, &KFileItemModelRolesUpdater::slotPreviewJobFinished); m_previewJob = job; } void KFileItemModelRolesUpdater::updateChangedItems() { if (m_state == Paused) { return; } if (m_changedItems.isEmpty()) { return; } m_finishedItems -= m_changedItems; if (m_resolvableRoles.contains(m_model->sortRole())) { m_pendingSortRoleItems += m_changedItems; if (m_state != ResolvingSortRole) { // Stop the preview job if necessary, and trigger the // asynchronous determination of the sort role. killPreviewJob(); m_state = ResolvingSortRole; QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextSortRole); } return; } QList visibleChangedIndexes; QList invisibleChangedIndexes; foreach (const KFileItem& item, m_changedItems) { const int index = m_model->index(item); if (index < 0) { m_changedItems.remove(item); continue; } if (index >= m_firstVisibleIndex && index <= m_lastVisibleIndex) { visibleChangedIndexes.append(index); } else { invisibleChangedIndexes.append(index); } } std::sort(visibleChangedIndexes.begin(), visibleChangedIndexes.end()); if (m_previewShown) { foreach (int index, visibleChangedIndexes) { m_pendingPreviewItems.append(m_model->fileItem(index)); } foreach (int index, invisibleChangedIndexes) { m_pendingPreviewItems.append(m_model->fileItem(index)); } if (!m_previewJob) { startPreviewJob(); } } else { const bool resolvingInProgress = !m_pendingIndexes.isEmpty(); m_pendingIndexes = visibleChangedIndexes + m_pendingIndexes + invisibleChangedIndexes; if (!resolvingInProgress) { // Trigger the asynchronous resolving of the changed roles. m_state = ResolvingAllRoles; QTimer::singleShot(0, this, &KFileItemModelRolesUpdater::resolveNextPendingRoles); } } } void KFileItemModelRolesUpdater::applySortRole(int index) { QHash data; const KFileItem item = m_model->fileItem(index); if (m_model->sortRole() == "type") { if (!item.isMimeTypeKnown()) { item.determineMimeType(); } data.insert("type", item.mimeComment()); } else if (m_model->sortRole() == "size" && item.isLocalFile() && item.isDir()) { const QString path = item.localPath(); data.insert("size", m_directoryContentsCounter->countDirectoryContentsSynchronously(path)); } else { // Probably the sort role is a baloo role - just determine all roles. data = rolesData(item); } disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); m_model->setData(index, data); connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); } void KFileItemModelRolesUpdater::applySortProgressToModel() { // Inform the model about the progress of the resolved items, // so that it can give an indication when the sorting has been finished. const int resolvedCount = m_model->count() - m_pendingSortRoleItems.count(); m_model->emitSortProgress(resolvedCount); } bool KFileItemModelRolesUpdater::applyResolvedRoles(int index, ResolveHint hint) { const KFileItem item = m_model->fileItem(index); const bool resolveAll = (hint == ResolveAll); bool iconChanged = false; if (!item.isMimeTypeKnown() || !item.isFinalIconKnown()) { item.determineMimeType(); iconChanged = true; } else if (!m_model->data(index).contains("iconName")) { iconChanged = true; } if (iconChanged || resolveAll || m_clearPreviews) { if (index < 0) { return false; } QHash data; if (resolveAll) { data = rolesData(item); } data.insert("iconName", item.iconName()); if (m_clearPreviews) { data.insert("iconPixmap", QPixmap()); } disconnect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); m_model->setData(index, data); connect(m_model, &KFileItemModel::itemsChanged, this, &KFileItemModelRolesUpdater::slotItemsChanged); return true; } return false; } QHash KFileItemModelRolesUpdater::rolesData(const KFileItem& item) { QHash data; const bool getSizeRole = m_roles.contains("size"); const bool getIsExpandableRole = m_roles.contains("isExpandable"); if ((getSizeRole || getIsExpandableRole) && item.isDir()) { if (item.isLocalFile()) { // Tell m_directoryContentsCounter that we want to count the items // inside the directory. The result will be received in slotDirectoryContentsCountReceived. const QString path = item.localPath(); m_directoryContentsCounter->addDirectory(path); } else if (getSizeRole) { data.insert("size", -1); // -1 indicates an unknown number of items } } if (m_roles.contains("type")) { data.insert("type", item.mimeComment()); } QStringList overlays = item.overlays(); foreach(KOverlayIconPlugin *it, m_overlayIconsPlugin) { overlays.append(it->getOverlays(item.url())); } data.insert("iconOverlays", overlays); #ifdef HAVE_BALOO if (m_balooFileMonitor) { m_balooFileMonitor->addFile(item.localPath()); applyChangedBalooRolesForItem(item); } #endif return data; } void KFileItemModelRolesUpdater::slotOverlaysChanged(const QUrl& url, const QStringList &) { const KFileItem item = m_model->fileItem(url); if (item.isNull()) { return; } const int index = m_model->index(item); QHash data = m_model->data(index); QStringList overlays = item.overlays(); foreach (KOverlayIconPlugin *it, m_overlayIconsPlugin) { overlays.append(it->getOverlays(url)); } data.insert("iconOverlays", overlays); m_model->setData(index, data); } void KFileItemModelRolesUpdater::updateAllPreviews() { if (m_state == Paused) { m_previewChangedDuringPausing = true; } else { m_finishedItems.clear(); startUpdating(); } } void KFileItemModelRolesUpdater::killPreviewJob() { if (m_previewJob) { disconnect(m_previewJob, &KIO::PreviewJob::gotPreview, this, &KFileItemModelRolesUpdater::slotGotPreview); disconnect(m_previewJob, &KIO::PreviewJob::failed, this, &KFileItemModelRolesUpdater::slotPreviewFailed); disconnect(m_previewJob, &KIO::PreviewJob::finished, this, &KFileItemModelRolesUpdater::slotPreviewJobFinished); m_previewJob->kill(); m_previewJob = nullptr; m_pendingPreviewItems.clear(); } } QList KFileItemModelRolesUpdater::indexesToResolve() const { const int count = m_model->count(); QList result; result.reserve(ResolveAllItemsLimit); // Add visible items. for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) { result.append(i); } // We need a reasonable upper limit for number of items to resolve after // and before the visible range. m_maximumVisibleItems can be quite large // when using Compace View. const int readAheadItems = qMin(ReadAheadPages * m_maximumVisibleItems, ResolveAllItemsLimit / 2); // Add items after the visible range. const int endExtendedVisibleRange = qMin(m_lastVisibleIndex + readAheadItems, count - 1); for (int i = m_lastVisibleIndex + 1; i <= endExtendedVisibleRange; ++i) { result.append(i); } // Add items before the visible range in reverse order. const int beginExtendedVisibleRange = qMax(0, m_firstVisibleIndex - readAheadItems); for (int i = m_firstVisibleIndex - 1; i >= beginExtendedVisibleRange; --i) { result.append(i); } // Add items on the last page. const int beginLastPage = qMax(qMin(endExtendedVisibleRange + 1, count - 1), count - m_maximumVisibleItems); for (int i = beginLastPage; i < count; ++i) { result.append(i); } // Add items on the first page. const int endFirstPage = qMin(qMax(beginExtendedVisibleRange - 1, 0), m_maximumVisibleItems); for (int i = 0; i <= endFirstPage; ++i) { result.append(i); } // Continue adding items until ResolveAllItemsLimit is reached. int remainingItems = ResolveAllItemsLimit - result.count(); for (int i = endExtendedVisibleRange + 1; i < beginLastPage && remainingItems > 0; ++i) { result.append(i); --remainingItems; } for (int i = beginExtendedVisibleRange - 1; i > endFirstPage && remainingItems > 0; --i) { result.append(i); --remainingItems; } return result; } diff --git a/src/kitemviews/kitemlistcontainer.cpp b/src/kitemviews/kitemlistcontainer.cpp index c1eae53bd..cdc7baa35 100644 --- a/src/kitemviews/kitemlistcontainer.cpp +++ b/src/kitemviews/kitemlistcontainer.cpp @@ -1,409 +1,407 @@ /*************************************************************************** * Copyright (C) 2011 by Peter Penz * * * * Based on the Itemviews NG project from Trolltech Labs: * * http://qt.gitorious.org/qt-labs/itemviews-ng * * * * 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 "kitemlistcontainer.h" #include "kitemlistcontroller.h" #include "kitemlistview.h" -#include "kitemmodelbase.h" #include "private/kitemlistsmoothscroller.h" #include #include #include #include -#include #include /** * Replaces the default viewport of KItemListContainer by a * non-scrollable viewport. The scrolling is done in an optimized * way by KItemListView internally. */ class KItemListContainerViewport : public QGraphicsView { Q_OBJECT public: KItemListContainerViewport(QGraphicsScene* scene, QWidget* parent); protected: void wheelEvent(QWheelEvent* event) override; }; KItemListContainerViewport::KItemListContainerViewport(QGraphicsScene* scene, QWidget* parent) : QGraphicsView(scene, parent) { setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setViewportMargins(0, 0, 0, 0); setFrameShape(QFrame::NoFrame); } void KItemListContainerViewport::wheelEvent(QWheelEvent* event) { // Assure that the wheel-event gets forwarded to the parent // and not handled at all by QGraphicsView. event->ignore(); } KItemListContainer::KItemListContainer(KItemListController* controller, QWidget* parent) : QAbstractScrollArea(parent), m_controller(controller), m_horizontalSmoothScroller(nullptr), m_verticalSmoothScroller(nullptr) { Q_ASSERT(controller); controller->setParent(this); QGraphicsView* graphicsView = new KItemListContainerViewport(new QGraphicsScene(this), this); setViewport(graphicsView); m_horizontalSmoothScroller = new KItemListSmoothScroller(horizontalScrollBar(), this); m_verticalSmoothScroller = new KItemListSmoothScroller(verticalScrollBar(), this); if (controller->model()) { slotModelChanged(controller->model(), nullptr); } if (controller->view()) { slotViewChanged(controller->view(), nullptr); } connect(controller, &KItemListController::modelChanged, this, &KItemListContainer::slotModelChanged); connect(controller, &KItemListController::viewChanged, this, &KItemListContainer::slotViewChanged); } KItemListContainer::~KItemListContainer() { // Don't rely on the QObject-order to delete the controller, otherwise // the QGraphicsScene might get deleted before the view. delete m_controller; m_controller = nullptr; } KItemListController* KItemListContainer::controller() const { return m_controller; } void KItemListContainer::setEnabledFrame(bool enable) { QGraphicsView* graphicsView = qobject_cast(viewport()); if (enable) { setFrameShape(QFrame::StyledPanel); graphicsView->setPalette(palette()); graphicsView->viewport()->setAutoFillBackground(true); } else { setFrameShape(QFrame::NoFrame); // Make the background of the container transparent and apply the window-text color // to the text color, so that enough contrast is given for all color // schemes QPalette p = graphicsView->palette(); p.setColor(QPalette::Active, QPalette::Text, p.color(QPalette::Active, QPalette::WindowText)); p.setColor(QPalette::Inactive, QPalette::Text, p.color(QPalette::Inactive, QPalette::WindowText)); p.setColor(QPalette::Disabled, QPalette::Text, p.color(QPalette::Disabled, QPalette::WindowText)); graphicsView->setPalette(p); graphicsView->viewport()->setAutoFillBackground(false); } } bool KItemListContainer::enabledFrame() const { const QGraphicsView* graphicsView = qobject_cast(viewport()); return graphicsView->autoFillBackground(); } void KItemListContainer::keyPressEvent(QKeyEvent* event) { // TODO: We should find a better way to handle the key press events in the view. // The reasons why we need this hack are: // 1. Without reimplementing keyPressEvent() here, the event would not reach the QGraphicsView. // 2. By default, the KItemListView does not have the keyboard focus in the QGraphicsScene, so // simply sending the event to the QGraphicsView which is the KItemListContainer's viewport // does not work. KItemListView* view = m_controller->view(); if (view) { QApplication::sendEvent(view, event); } } void KItemListContainer::showEvent(QShowEvent* event) { QAbstractScrollArea::showEvent(event); updateGeometries(); } void KItemListContainer::resizeEvent(QResizeEvent* event) { QAbstractScrollArea::resizeEvent(event); updateGeometries(); } void KItemListContainer::scrollContentsBy(int dx, int dy) { m_horizontalSmoothScroller->scrollContentsBy(dx); m_verticalSmoothScroller->scrollContentsBy(dy); } void KItemListContainer::wheelEvent(QWheelEvent* event) { if (event->modifiers().testFlag(Qt::ControlModifier)) { event->ignore(); return; } KItemListView* view = m_controller->view(); if (!view) { event->ignore(); return; } const bool scrollHorizontally = (event->orientation() == Qt::Horizontal) || (event->orientation() == Qt::Vertical && !verticalScrollBar()->isVisible()); KItemListSmoothScroller* smoothScroller = scrollHorizontally ? m_horizontalSmoothScroller : m_verticalSmoothScroller; smoothScroller->handleWheelEvent(event); } void KItemListContainer::slotScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous) { Q_UNUSED(previous); updateSmoothScrollers(current); } void KItemListContainer::slotModelChanged(KItemModelBase* current, KItemModelBase* previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListContainer::slotViewChanged(KItemListView* current, KItemListView* previous) { QGraphicsScene* scene = static_cast(viewport())->scene(); if (previous) { scene->removeItem(previous); disconnect(previous, &KItemListView::scrollOrientationChanged, this, &KItemListContainer::slotScrollOrientationChanged); disconnect(previous, &KItemListView::scrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar); disconnect(previous, &KItemListView::maximumScrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar); disconnect(previous, &KItemListView::itemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar); disconnect(previous, &KItemListView::maximumItemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar); disconnect(previous, &KItemListView::scrollTo, this, &KItemListContainer::scrollTo); m_horizontalSmoothScroller->setTargetObject(nullptr); m_verticalSmoothScroller->setTargetObject(nullptr); } if (current) { scene->addItem(current); connect(current, &KItemListView::scrollOrientationChanged, this, &KItemListContainer::slotScrollOrientationChanged); connect(current, &KItemListView::scrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar); connect(current, &KItemListView::maximumScrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar); connect(current, &KItemListView::itemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar); connect(current, &KItemListView::maximumItemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar); connect(current, &KItemListView::scrollTo, this, &KItemListContainer::scrollTo); m_horizontalSmoothScroller->setTargetObject(current); m_verticalSmoothScroller->setTargetObject(current); updateSmoothScrollers(current->scrollOrientation()); } } void KItemListContainer::scrollTo(qreal offset) { const KItemListView* view = m_controller->view(); if (view) { if (view->scrollOrientation() == Qt::Vertical) { m_verticalSmoothScroller->scrollTo(offset); } else { m_horizontalSmoothScroller->scrollTo(offset); } } } void KItemListContainer::updateScrollOffsetScrollBar() { const KItemListView* view = m_controller->view(); if (!view) { return; } KItemListSmoothScroller* smoothScroller = nullptr; QScrollBar* scrollOffsetScrollBar = nullptr; int singleStep = 0; int pageStep = 0; int maximum = 0; if (view->scrollOrientation() == Qt::Vertical) { smoothScroller = m_verticalSmoothScroller; scrollOffsetScrollBar = verticalScrollBar(); singleStep = view->itemSizeHint().height(); // We cannot use view->size().height() because this height might // include the header widget, which is not part of the scrolled area. pageStep = view->verticalPageStep(); // However, the total height of the view must be considered for the // maximum value of the scroll bar. Note that the view's scrollOffset() // refers to the offset of the top part of the view, which might be // hidden behind the header. maximum = qMax(0, int(view->maximumScrollOffset() - view->size().height())); } else { smoothScroller = m_horizontalSmoothScroller; scrollOffsetScrollBar = horizontalScrollBar(); singleStep = view->itemSize().width(); pageStep = view->size().width(); maximum = qMax(0, int(view->maximumScrollOffset() - view->size().width())); } const int value = view->scrollOffset(); if (smoothScroller->requestScrollBarUpdate(maximum)) { const bool updatePolicy = (scrollOffsetScrollBar->maximum() > 0 && maximum == 0) || horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOn; scrollOffsetScrollBar->setSingleStep(singleStep); scrollOffsetScrollBar->setPageStep(pageStep); scrollOffsetScrollBar->setMinimum(0); scrollOffsetScrollBar->setMaximum(maximum); scrollOffsetScrollBar->setValue(value); if (updatePolicy) { // Prevent a potential endless layout loop (see bug #293318). updateScrollOffsetScrollBarPolicy(); } } } void KItemListContainer::updateItemOffsetScrollBar() { const KItemListView* view = m_controller->view(); if (!view) { return; } KItemListSmoothScroller* smoothScroller = nullptr; QScrollBar* itemOffsetScrollBar = nullptr; int singleStep = 0; int pageStep = 0; if (view->scrollOrientation() == Qt::Vertical) { smoothScroller = m_horizontalSmoothScroller; itemOffsetScrollBar = horizontalScrollBar(); singleStep = view->size().width() / 10; pageStep = view->size().width(); } else { smoothScroller = m_verticalSmoothScroller; itemOffsetScrollBar = verticalScrollBar(); singleStep = view->size().height() / 10; pageStep = view->size().height(); } const int value = view->itemOffset(); const int maximum = qMax(0, int(view->maximumItemOffset()) - pageStep); if (smoothScroller->requestScrollBarUpdate(maximum)) { itemOffsetScrollBar->setSingleStep(singleStep); itemOffsetScrollBar->setPageStep(pageStep); itemOffsetScrollBar->setMinimum(0); itemOffsetScrollBar->setMaximum(maximum); itemOffsetScrollBar->setValue(value); } } void KItemListContainer::updateGeometries() { QRect rect = geometry(); int extra = frameWidth() * 2; QStyleOption option; option.initFrom(this); int scrollbarSpacing = 0; if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &option, this)) { scrollbarSpacing = style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing, &option, this); } const int widthDec = verticalScrollBar()->isVisible() ? extra + scrollbarSpacing + style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this) : extra; const int heightDec = horizontalScrollBar()->isVisible() ? extra + scrollbarSpacing + style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this) : extra; const QRectF newGeometry(0, 0, rect.width() - widthDec, rect.height() - heightDec); if (m_controller->view()->geometry() != newGeometry) { m_controller->view()->setGeometry(newGeometry); // Get the real geometry of the view again since the scrollbars // visibilities and the view geometry may have changed in re-layout. static_cast(viewport())->scene()->setSceneRect(m_controller->view()->geometry()); static_cast(viewport())->viewport()->setGeometry(m_controller->view()->geometry().toRect()); updateScrollOffsetScrollBar(); updateItemOffsetScrollBar(); } } void KItemListContainer::updateSmoothScrollers(Qt::Orientation orientation) { if (orientation == Qt::Vertical) { m_verticalSmoothScroller->setPropertyName("scrollOffset"); m_horizontalSmoothScroller->setPropertyName("itemOffset"); } else { m_horizontalSmoothScroller->setPropertyName("scrollOffset"); m_verticalSmoothScroller->setPropertyName("itemOffset"); } } void KItemListContainer::updateScrollOffsetScrollBarPolicy() { const KItemListView* view = m_controller->view(); Q_ASSERT(view); const bool vertical = (view->scrollOrientation() == Qt::Vertical); QStyleOption option; option.initFrom(this); const int scrollBarInc = style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this); QSizeF newViewSize = m_controller->view()->size(); if (vertical) { newViewSize.rwidth() += scrollBarInc; } else { newViewSize.rheight() += scrollBarInc; } const Qt::ScrollBarPolicy policy = view->scrollBarRequired(newViewSize) ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAsNeeded; if (vertical) { setVerticalScrollBarPolicy(policy); } else { setHorizontalScrollBarPolicy(policy); } } #include "kitemlistcontainer.moc" diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp index 7af8781b4..c8e109a05 100644 --- a/src/kitemviews/kitemlistcontroller.cpp +++ b/src/kitemviews/kitemlistcontroller.cpp @@ -1,1358 +1,1357 @@ /*************************************************************************** * Copyright (C) 2011 by Peter Penz * * Copyright (C) 2012 by Frank Reininghaus * * * * Based on the Itemviews NG project from Trolltech Labs: * * http://qt.gitorious.org/qt-labs/itemviews-ng * * * * 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 "kitemlistcontroller.h" #include "kitemlistview.h" #include "kitemlistselectionmanager.h" #include "private/kitemlistrubberband.h" #include "private/kitemlistkeyboardsearchmanager.h" #include #include -#include #include #include #include #include #include #include #include KItemListController::KItemListController(KItemModelBase* model, KItemListView* view, QObject* parent) : QObject(parent), m_singleClickActivationEnforced(false), m_selectionTogglePressed(false), m_clearSelectionIfItemsAreNotDragged(false), m_selectionBehavior(NoSelection), m_autoActivationBehavior(ActivationAndExpansion), m_mouseDoubleClickAction(ActivateItemOnly), m_model(nullptr), m_view(nullptr), m_selectionManager(new KItemListSelectionManager(this)), m_keyboardManager(new KItemListKeyboardSearchManager(this)), m_pressedIndex(-1), m_pressedMousePos(), m_autoActivationTimer(nullptr), m_oldSelection(), m_keyboardAnchorIndex(-1), m_keyboardAnchorPos(0) { connect(m_keyboardManager, &KItemListKeyboardSearchManager::changeCurrentItem, this, &KItemListController::slotChangeCurrentItem); connect(m_selectionManager, &KItemListSelectionManager::currentChanged, m_keyboardManager, &KItemListKeyboardSearchManager::slotCurrentChanged); m_autoActivationTimer = new QTimer(this); m_autoActivationTimer->setSingleShot(true); m_autoActivationTimer->setInterval(-1); connect(m_autoActivationTimer, &QTimer::timeout, this, &KItemListController::slotAutoActivationTimeout); setModel(model); setView(view); } KItemListController::~KItemListController() { setView(nullptr); Q_ASSERT(!m_view); setModel(nullptr); Q_ASSERT(!m_model); } void KItemListController::setModel(KItemModelBase* model) { if (m_model == model) { return; } KItemModelBase* oldModel = m_model; if (oldModel) { oldModel->deleteLater(); } m_model = model; if (m_model) { m_model->setParent(this); } if (m_view) { m_view->setModel(m_model); } m_selectionManager->setModel(m_model); emit modelChanged(m_model, oldModel); } KItemModelBase* KItemListController::model() const { return m_model; } KItemListSelectionManager* KItemListController::selectionManager() const { return m_selectionManager; } void KItemListController::setView(KItemListView* view) { if (m_view == view) { return; } KItemListView* oldView = m_view; if (oldView) { disconnect(oldView, &KItemListView::scrollOffsetChanged, this, &KItemListController::slotViewScrollOffsetChanged); oldView->deleteLater(); } m_view = view; if (m_view) { m_view->setParent(this); m_view->setController(this); m_view->setModel(m_model); connect(m_view, &KItemListView::scrollOffsetChanged, this, &KItemListController::slotViewScrollOffsetChanged); updateExtendedSelectionRegion(); } emit viewChanged(m_view, oldView); } KItemListView* KItemListController::view() const { return m_view; } void KItemListController::setSelectionBehavior(SelectionBehavior behavior) { m_selectionBehavior = behavior; updateExtendedSelectionRegion(); } KItemListController::SelectionBehavior KItemListController::selectionBehavior() const { return m_selectionBehavior; } void KItemListController::setAutoActivationBehavior(AutoActivationBehavior behavior) { m_autoActivationBehavior = behavior; } KItemListController::AutoActivationBehavior KItemListController::autoActivationBehavior() const { return m_autoActivationBehavior; } void KItemListController::setMouseDoubleClickAction(MouseDoubleClickAction action) { m_mouseDoubleClickAction = action; } KItemListController::MouseDoubleClickAction KItemListController::mouseDoubleClickAction() const { return m_mouseDoubleClickAction; } int KItemListController::indexCloseToMousePressedPosition() const { QHashIterator it(m_view->m_visibleGroups); while (it.hasNext()) { it.next(); KItemListGroupHeader *groupHeader = it.value(); const QPointF mappedToGroup = groupHeader->mapFromItem(nullptr, m_pressedMousePos); if (groupHeader->contains(mappedToGroup)) { return it.key()->index(); } } return -1; } void KItemListController::setAutoActivationDelay(int delay) { m_autoActivationTimer->setInterval(delay); } int KItemListController::autoActivationDelay() const { return m_autoActivationTimer->interval(); } void KItemListController::setSingleClickActivationEnforced(bool singleClick) { m_singleClickActivationEnforced = singleClick; } bool KItemListController::singleClickActivationEnforced() const { return m_singleClickActivationEnforced; } bool KItemListController::showEvent(QShowEvent* event) { Q_UNUSED(event); return false; } bool KItemListController::hideEvent(QHideEvent* event) { Q_UNUSED(event); return false; } bool KItemListController::keyPressEvent(QKeyEvent* event) { int index = m_selectionManager->currentItem(); int key = event->key(); // Handle the expanding/collapsing of items if (m_view->supportsItemExpanding() && m_model->isExpandable(index)) { if (key == Qt::Key_Right) { if (m_model->setExpanded(index, true)) { return true; } } else if (key == Qt::Key_Left) { if (m_model->setExpanded(index, false)) { return true; } } } const bool shiftPressed = event->modifiers() & Qt::ShiftModifier; const bool controlPressed = event->modifiers() & Qt::ControlModifier; const bool shiftOrControlPressed = shiftPressed || controlPressed; const int itemCount = m_model->count(); // For horizontal scroll orientation, transform // the arrow keys to simplify the event handling. if (m_view->scrollOrientation() == Qt::Horizontal) { switch (key) { case Qt::Key_Up: key = Qt::Key_Left; break; case Qt::Key_Down: key = Qt::Key_Right; break; case Qt::Key_Left: key = Qt::Key_Up; break; case Qt::Key_Right: key = Qt::Key_Down; break; default: break; } } const bool selectSingleItem = m_selectionBehavior != NoSelection && itemCount == 1 && (key == Qt::Key_Home || key == Qt::Key_End || key == Qt::Key_Up || key == Qt::Key_Down || key == Qt::Key_Left || key == Qt::Key_Right); if (selectSingleItem) { const int current = m_selectionManager->currentItem(); m_selectionManager->setSelected(current); return true; } switch (key) { case Qt::Key_Home: index = 0; m_keyboardAnchorIndex = index; m_keyboardAnchorPos = keyboardAnchorPos(index); break; case Qt::Key_End: index = itemCount - 1; m_keyboardAnchorIndex = index; m_keyboardAnchorPos = keyboardAnchorPos(index); break; case Qt::Key_Left: if (index > 0) { const int expandedParentsCount = m_model->expandedParentsCount(index); if (expandedParentsCount == 0) { --index; } else { // Go to the parent of the current item. do { --index; } while (index > 0 && m_model->expandedParentsCount(index) == expandedParentsCount); } m_keyboardAnchorIndex = index; m_keyboardAnchorPos = keyboardAnchorPos(index); } break; case Qt::Key_Right: if (index < itemCount - 1) { ++index; m_keyboardAnchorIndex = index; m_keyboardAnchorPos = keyboardAnchorPos(index); } break; case Qt::Key_Up: updateKeyboardAnchor(); index = previousRowIndex(index); break; case Qt::Key_Down: updateKeyboardAnchor(); index = nextRowIndex(index); break; case Qt::Key_PageUp: if (m_view->scrollOrientation() == Qt::Horizontal) { // The new current index should correspond to the first item in the current column. int newIndex = qMax(index - 1, 0); while (newIndex != index && m_view->itemRect(newIndex).topLeft().y() < m_view->itemRect(index).topLeft().y()) { index = newIndex; newIndex = qMax(index - 1, 0); } m_keyboardAnchorIndex = index; m_keyboardAnchorPos = keyboardAnchorPos(index); } else { const qreal currentItemBottom = m_view->itemRect(index).bottomLeft().y(); const qreal height = m_view->geometry().height(); // The new current item should be the first item in the current // column whose itemRect's top coordinate is larger than targetY. const qreal targetY = currentItemBottom - height; updateKeyboardAnchor(); int newIndex = previousRowIndex(index); do { index = newIndex; updateKeyboardAnchor(); newIndex = previousRowIndex(index); } while (m_view->itemRect(newIndex).topLeft().y() > targetY && newIndex != index); } break; case Qt::Key_PageDown: if (m_view->scrollOrientation() == Qt::Horizontal) { // The new current index should correspond to the last item in the current column. int newIndex = qMin(index + 1, m_model->count() - 1); while (newIndex != index && m_view->itemRect(newIndex).topLeft().y() > m_view->itemRect(index).topLeft().y()) { index = newIndex; newIndex = qMin(index + 1, m_model->count() - 1); } m_keyboardAnchorIndex = index; m_keyboardAnchorPos = keyboardAnchorPos(index); } else { const qreal currentItemTop = m_view->itemRect(index).topLeft().y(); const qreal height = m_view->geometry().height(); // The new current item should be the last item in the current // column whose itemRect's bottom coordinate is smaller than targetY. const qreal targetY = currentItemTop + height; updateKeyboardAnchor(); int newIndex = nextRowIndex(index); do { index = newIndex; updateKeyboardAnchor(); newIndex = nextRowIndex(index); } while (m_view->itemRect(newIndex).bottomLeft().y() < targetY && newIndex != index); } break; case Qt::Key_Enter: case Qt::Key_Return: { const KItemSet selectedItems = m_selectionManager->selectedItems(); if (selectedItems.count() >= 2) { emit itemsActivated(selectedItems); } else if (selectedItems.count() == 1) { emit itemActivated(selectedItems.first()); } else { emit itemActivated(index); } break; } case Qt::Key_Menu: { // Emit the signal itemContextMenuRequested() in case if at least one // item is selected. Otherwise the signal viewContextMenuRequested() will be emitted. const KItemSet selectedItems = m_selectionManager->selectedItems(); int index = -1; if (selectedItems.count() >= 2) { const int currentItemIndex = m_selectionManager->currentItem(); index = selectedItems.contains(currentItemIndex) ? currentItemIndex : selectedItems.first(); } else if (selectedItems.count() == 1) { index = selectedItems.first(); } if (index >= 0) { const QRectF contextRect = m_view->itemContextRect(index); const QPointF pos(m_view->scene()->views().first()->mapToGlobal(contextRect.bottomRight().toPoint())); emit itemContextMenuRequested(index, pos); } else { emit viewContextMenuRequested(QCursor::pos()); } break; } case Qt::Key_Escape: if (m_selectionBehavior != SingleSelection) { m_selectionManager->clearSelection(); } m_keyboardManager->cancelSearch(); emit escapePressed(); break; case Qt::Key_Space: if (m_selectionBehavior == MultiSelection) { if (controlPressed) { // Toggle the selection state of the current item. m_selectionManager->endAnchoredSelection(); m_selectionManager->setSelected(index, 1, KItemListSelectionManager::Toggle); m_selectionManager->beginAnchoredSelection(index); break; } else { // Select the current item if it is not selected yet. const int current = m_selectionManager->currentItem(); if (!m_selectionManager->isSelected(current)) { m_selectionManager->setSelected(current); break; } } } // Fall through to the default case and add the Space to the current search string. default: m_keyboardManager->addKeys(event->text()); // Make sure unconsumed events get propagated up the chain. #302329 event->ignore(); return false; } if (m_selectionManager->currentItem() != index) { switch (m_selectionBehavior) { case NoSelection: m_selectionManager->setCurrentItem(index); break; case SingleSelection: m_selectionManager->setCurrentItem(index); m_selectionManager->clearSelection(); m_selectionManager->setSelected(index, 1); break; case MultiSelection: if (controlPressed) { m_selectionManager->endAnchoredSelection(); } m_selectionManager->setCurrentItem(index); if (!shiftOrControlPressed) { m_selectionManager->clearSelection(); m_selectionManager->setSelected(index, 1); } if (!shiftPressed) { m_selectionManager->beginAnchoredSelection(index); } break; } m_view->scrollToItem(index); } return true; } void KItemListController::slotChangeCurrentItem(const QString& text, bool searchFromNextItem) { if (!m_model || m_model->count() == 0) { return; } const int currentIndex = m_selectionManager->currentItem(); int index; if (searchFromNextItem) { index = m_model->indexForKeyboardSearch(text, (currentIndex + 1) % m_model->count()); } else { index = m_model->indexForKeyboardSearch(text, currentIndex); } if (index >= 0) { m_selectionManager->setCurrentItem(index); if (m_selectionBehavior != NoSelection) { m_selectionManager->clearSelection(); m_selectionManager->setSelected(index, 1); m_selectionManager->beginAnchoredSelection(index); } m_view->scrollToItem(index); } } void KItemListController::slotAutoActivationTimeout() { if (!m_model || !m_view) { return; } const int index = m_autoActivationTimer->property("index").toInt(); if (index < 0 || index >= m_model->count()) { return; } /* m_view->isUnderMouse() fixes a bug in the Folder-View-Panel and in the * Places-Panel. * * Bug: When you drag a file onto a Folder-View-Item or a Places-Item and * then move away before the auto-activation timeout triggers, than the * item still becomes activated/expanded. * * See Bug 293200 and 305783 */ if (m_model->supportsDropping(index) && m_view->isUnderMouse()) { if (m_view->supportsItemExpanding() && m_model->isExpandable(index)) { const bool expanded = m_model->isExpanded(index); m_model->setExpanded(index, !expanded); } else if (m_autoActivationBehavior != ExpansionOnly) { emit itemActivated(index); } } } bool KItemListController::inputMethodEvent(QInputMethodEvent* event) { Q_UNUSED(event); return false; } bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform) { if (!m_view) { return false; } m_pressedMousePos = transform.map(event->pos()); m_pressedIndex = m_view->itemAt(m_pressedMousePos); emit mouseButtonPressed(m_pressedIndex, event->buttons()); if (event->buttons() & (Qt::BackButton | Qt::ForwardButton)) { // Do not select items when clicking the back/forward buttons, see // https://bugs.kde.org/show_bug.cgi?id=327412. return true; } if (m_view->isAboveExpansionToggle(m_pressedIndex, m_pressedMousePos)) { m_selectionManager->endAnchoredSelection(); m_selectionManager->setCurrentItem(m_pressedIndex); m_selectionManager->beginAnchoredSelection(m_pressedIndex); return true; } m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos); if (m_selectionTogglePressed) { m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle); // The previous anchored selection has been finished already in // KItemListSelectionManager::setSelected(). We can safely change // the current item and start a new anchored selection now. m_selectionManager->setCurrentItem(m_pressedIndex); m_selectionManager->beginAnchoredSelection(m_pressedIndex); return true; } const bool shiftPressed = event->modifiers() & Qt::ShiftModifier; const bool controlPressed = event->modifiers() & Qt::ControlModifier; // The previous selection is cleared if either // 1. The selection mode is SingleSelection, or // 2. the selection mode is MultiSelection, and *none* of the following conditions are met: // a) Shift or Control are pressed. // b) The clicked item is selected already. In that case, the user might want to: // - start dragging multiple items, or // - open the context menu and perform an action for all selected items. const bool shiftOrControlPressed = shiftPressed || controlPressed; const bool pressedItemAlreadySelected = m_pressedIndex >= 0 && m_selectionManager->isSelected(m_pressedIndex); const bool clearSelection = m_selectionBehavior == SingleSelection || (!shiftOrControlPressed && !pressedItemAlreadySelected); if (clearSelection) { m_selectionManager->clearSelection(); } else if (pressedItemAlreadySelected && !shiftOrControlPressed && (event->buttons() & Qt::LeftButton)) { // The user might want to start dragging multiple items, but if he clicks the item // in order to trigger it instead, the other selected items must be deselected. // However, we do not know yet what the user is going to do. // -> remember that the user pressed an item which had been selected already and // clear the selection in mouseReleaseEvent(), unless the items are dragged. m_clearSelectionIfItemsAreNotDragged = true; if (m_selectionManager->selectedItems().count() == 1 && m_view->isAboveText(m_pressedIndex, m_pressedMousePos)) { emit selectedItemTextPressed(m_pressedIndex); } } if (!shiftPressed) { // Finish the anchored selection before the current index is changed m_selectionManager->endAnchoredSelection(); } if (m_pressedIndex >= 0) { m_selectionManager->setCurrentItem(m_pressedIndex); switch (m_selectionBehavior) { case NoSelection: break; case SingleSelection: m_selectionManager->setSelected(m_pressedIndex); break; case MultiSelection: if (controlPressed && !shiftPressed) { m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle); m_selectionManager->beginAnchoredSelection(m_pressedIndex); } else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) { // Select the pressed item and start a new anchored selection m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select); m_selectionManager->beginAnchoredSelection(m_pressedIndex); } break; default: Q_ASSERT(false); break; } if (event->buttons() & Qt::RightButton) { emit itemContextMenuRequested(m_pressedIndex, event->screenPos()); } return true; } if (event->buttons() & Qt::RightButton) { const QRectF headerBounds = m_view->headerBoundaries(); if (headerBounds.contains(event->pos())) { emit headerContextMenuRequested(event->screenPos()); } else { emit viewContextMenuRequested(event->screenPos()); } return true; } if (m_selectionBehavior == MultiSelection) { QPointF startPos = m_pressedMousePos; if (m_view->scrollOrientation() == Qt::Vertical) { startPos.ry() += m_view->scrollOffset(); if (m_view->itemSize().width() < 0) { // Use a special rubberband for views that have only one column and // expand the rubberband to use the whole width of the view. startPos.setX(0); } } else { startPos.rx() += m_view->scrollOffset(); } m_oldSelection = m_selectionManager->selectedItems(); KItemListRubberBand* rubberBand = m_view->rubberBand(); rubberBand->setStartPosition(startPos); rubberBand->setEndPosition(startPos); rubberBand->setActive(true); connect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged); m_view->setAutoScroll(true); } return false; } bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform) { if (!m_view) { return false; } if (m_pressedIndex >= 0) { // Check whether a dragging should be started if (event->buttons() & Qt::LeftButton) { const QPointF pos = transform.map(event->pos()); if ((pos - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) { if (!m_selectionManager->isSelected(m_pressedIndex)) { // Always assure that the dragged item gets selected. Usually this is already // done on the mouse-press event, but when using the selection-toggle on a // selected item the dragged item is not selected yet. m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle); } else { // A selected item has been clicked to drag all selected items // -> the selection should not be cleared when the mouse button is released. m_clearSelectionIfItemsAreNotDragged = false; } startDragging(); } } } else { KItemListRubberBand* rubberBand = m_view->rubberBand(); if (rubberBand->isActive()) { QPointF endPos = transform.map(event->pos()); // Update the current item. const int newCurrent = m_view->itemAt(endPos); if (newCurrent >= 0) { // It's expected that the new current index is also the new anchor (bug 163451). m_selectionManager->endAnchoredSelection(); m_selectionManager->setCurrentItem(newCurrent); m_selectionManager->beginAnchoredSelection(newCurrent); } if (m_view->scrollOrientation() == Qt::Vertical) { endPos.ry() += m_view->scrollOffset(); if (m_view->itemSize().width() < 0) { // Use a special rubberband for views that have only one column and // expand the rubberband to use the whole width of the view. endPos.setX(m_view->size().width()); } } else { endPos.rx() += m_view->scrollOffset(); } rubberBand->setEndPosition(endPos); } } return false; } bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform) { if (!m_view) { return false; } emit mouseButtonReleased(m_pressedIndex, event->buttons()); const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos); if (isAboveSelectionToggle) { m_selectionTogglePressed = false; return true; } if (!isAboveSelectionToggle && m_selectionTogglePressed) { m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle); m_selectionTogglePressed = false; return true; } const bool shiftOrControlPressed = event->modifiers() & Qt::ShiftModifier || event->modifiers() & Qt::ControlModifier; KItemListRubberBand* rubberBand = m_view->rubberBand(); if (rubberBand->isActive()) { disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged); rubberBand->setActive(false); m_oldSelection.clear(); m_view->setAutoScroll(false); } const QPointF pos = transform.map(event->pos()); const int index = m_view->itemAt(pos); if (index >= 0 && index == m_pressedIndex) { // The release event is done above the same item as the press event if (m_clearSelectionIfItemsAreNotDragged) { // A selected item has been clicked, but no drag operation has been started // -> clear the rest of the selection. m_selectionManager->clearSelection(); m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select); m_selectionManager->beginAnchoredSelection(m_pressedIndex); } if (event->button() & Qt::LeftButton) { bool emitItemActivated = true; if (m_view->isAboveExpansionToggle(index, pos)) { const bool expanded = m_model->isExpanded(index); m_model->setExpanded(index, !expanded); emit itemExpansionToggleClicked(index); emitItemActivated = false; } else if (shiftOrControlPressed) { // The mouse click should only update the selection, not trigger the item emitItemActivated = false; } else if (!(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced)) { emitItemActivated = false; } if (emitItemActivated) { emit itemActivated(index); } } else if (event->button() & Qt::MidButton) { emit itemMiddleClicked(index); } } m_pressedMousePos = QPointF(); m_pressedIndex = -1; m_clearSelectionIfItemsAreNotDragged = false; return false; } bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform) { const QPointF pos = transform.map(event->pos()); const int index = m_view->itemAt(pos); // Expand item if desired - See Bug 295573 if (m_mouseDoubleClickAction != ActivateItemOnly) { if (m_view && m_model && m_view->supportsItemExpanding() && m_model->isExpandable(index)) { const bool expanded = m_model->isExpanded(index); m_model->setExpanded(index, !expanded); } } if (event->button() & Qt::RightButton) { m_selectionManager->clearSelection(); if (index >= 0) { m_selectionManager->setSelected(index); emit itemContextMenuRequested(index, event->screenPos()); } else { const QRectF headerBounds = m_view->headerBoundaries(); if (headerBounds.contains(event->pos())) { emit headerContextMenuRequested(event->screenPos()); } else { emit viewContextMenuRequested(event->screenPos()); } } return true; } bool emitItemActivated = !(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced) && (event->button() & Qt::LeftButton) && index >= 0 && index < m_model->count(); if (emitItemActivated) { emit itemActivated(index); } return false; } bool KItemListController::dragEnterEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform) { Q_UNUSED(event); Q_UNUSED(transform); DragAndDropHelper::clearUrlListMatchesUrlCache(); return false; } bool KItemListController::dragLeaveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform) { Q_UNUSED(event); Q_UNUSED(transform); m_autoActivationTimer->stop(); m_view->setAutoScroll(false); m_view->hideDropIndicator(); KItemListWidget* widget = hoveredWidget(); if (widget) { widget->setHovered(false); emit itemUnhovered(widget->index()); } return false; } bool KItemListController::dragMoveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform) { if (!m_model || !m_view) { return false; } QUrl hoveredDir = m_model->directory(); KItemListWidget* oldHoveredWidget = hoveredWidget(); const QPointF pos = transform.map(event->pos()); KItemListWidget* newHoveredWidget = widgetForPos(pos); if (oldHoveredWidget != newHoveredWidget) { m_autoActivationTimer->stop(); if (oldHoveredWidget) { oldHoveredWidget->setHovered(false); emit itemUnhovered(oldHoveredWidget->index()); } } if (newHoveredWidget) { bool droppingBetweenItems = false; if (m_model->sortRole().isEmpty()) { // The model supports inserting items between other items. droppingBetweenItems = (m_view->showDropIndicator(pos) >= 0); } const int index = newHoveredWidget->index(); if (m_model->isDir(index)) { hoveredDir = m_model->url(index); } if (!droppingBetweenItems) { if (m_model->supportsDropping(index)) { // Something has been dragged on an item. m_view->hideDropIndicator(); if (!newHoveredWidget->isHovered()) { newHoveredWidget->setHovered(true); emit itemHovered(index); } if (!m_autoActivationTimer->isActive() && m_autoActivationTimer->interval() >= 0) { m_autoActivationTimer->setProperty("index", index); m_autoActivationTimer->start(); } } } else { m_autoActivationTimer->stop(); if (newHoveredWidget && newHoveredWidget->isHovered()) { newHoveredWidget->setHovered(false); emit itemUnhovered(index); } } } else { m_view->hideDropIndicator(); } event->setAccepted(!DragAndDropHelper::urlListMatchesUrl(event->mimeData()->urls(), hoveredDir)); return false; } bool KItemListController::dropEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform) { if (!m_view) { return false; } m_autoActivationTimer->stop(); m_view->setAutoScroll(false); const QPointF pos = transform.map(event->pos()); int dropAboveIndex = -1; if (m_model->sortRole().isEmpty()) { // The model supports inserting of items between other items. dropAboveIndex = m_view->showDropIndicator(pos); } if (dropAboveIndex >= 0) { // Something has been dropped between two items. m_view->hideDropIndicator(); emit aboveItemDropEvent(dropAboveIndex, event); } else if (!event->mimeData()->hasFormat(m_model->blacklistItemDropEventMimeType())) { // Something has been dropped on an item or on an empty part of the view. emit itemDropEvent(m_view->itemAt(pos), event); } QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropEnd); QAccessible::updateAccessibility(&accessibilityEvent); return true; } bool KItemListController::hoverEnterEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform) { Q_UNUSED(event); Q_UNUSED(transform); return false; } bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform) { Q_UNUSED(transform); if (!m_model || !m_view) { return false; } KItemListWidget* oldHoveredWidget = hoveredWidget(); const QPointF pos = transform.map(event->pos()); KItemListWidget* newHoveredWidget = widgetForPos(pos); if (oldHoveredWidget != newHoveredWidget) { if (oldHoveredWidget) { oldHoveredWidget->setHovered(false); emit itemUnhovered(oldHoveredWidget->index()); } if (newHoveredWidget) { newHoveredWidget->setHovered(true); const QPointF mappedPos = newHoveredWidget->mapFromItem(m_view, pos); newHoveredWidget->setHoverPosition(mappedPos); emit itemHovered(newHoveredWidget->index()); } } else if (oldHoveredWidget) { const QPointF mappedPos = oldHoveredWidget->mapFromItem(m_view, pos); oldHoveredWidget->setHoverPosition(mappedPos); } return false; } bool KItemListController::hoverLeaveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform) { 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; } bool KItemListController::wheelEvent(QGraphicsSceneWheelEvent* event, const QTransform& transform) { Q_UNUSED(event); Q_UNUSED(transform); return false; } bool KItemListController::resizeEvent(QGraphicsSceneResizeEvent* event, const QTransform& transform) { Q_UNUSED(event); Q_UNUSED(transform); return false; } bool KItemListController::processEvent(QEvent* event, const QTransform& transform) { if (!event) { return false; } switch (event->type()) { case QEvent::KeyPress: return keyPressEvent(static_cast(event)); case QEvent::InputMethod: return inputMethodEvent(static_cast(event)); case QEvent::GraphicsSceneMousePress: return mousePressEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneMouseMove: return mouseMoveEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneMouseRelease: return mouseReleaseEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneMouseDoubleClick: return mouseDoubleClickEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneWheel: return wheelEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneDragEnter: return dragEnterEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneDragLeave: return dragLeaveEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneDragMove: return dragMoveEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneDrop: return dropEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneHoverEnter: return hoverEnterEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneHoverMove: return hoverMoveEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneHoverLeave: return hoverLeaveEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneResize: return resizeEvent(static_cast(event), transform); default: break; } return false; } void KItemListController::slotViewScrollOffsetChanged(qreal current, qreal previous) { if (!m_view) { return; } KItemListRubberBand* rubberBand = m_view->rubberBand(); if (rubberBand->isActive()) { const qreal diff = current - previous; // TODO: Ideally just QCursor::pos() should be used as // new end-position but it seems there is no easy way // to have something like QWidget::mapFromGlobal() for QGraphicsWidget // (... or I just missed an easy way to do the mapping) QPointF endPos = rubberBand->endPosition(); if (m_view->scrollOrientation() == Qt::Vertical) { endPos.ry() += diff; } else { endPos.rx() += diff; } rubberBand->setEndPosition(endPos); } } void KItemListController::slotRubberBandChanged() { if (!m_view || !m_model || m_model->count() <= 0) { return; } const KItemListRubberBand* rubberBand = m_view->rubberBand(); const QPointF startPos = rubberBand->startPosition(); const QPointF endPos = rubberBand->endPosition(); QRectF rubberBandRect = QRectF(startPos, endPos).normalized(); const bool scrollVertical = (m_view->scrollOrientation() == Qt::Vertical); if (scrollVertical) { rubberBandRect.translate(0, -m_view->scrollOffset()); } else { rubberBandRect.translate(-m_view->scrollOffset(), 0); } if (!m_oldSelection.isEmpty()) { // Clear the old selection that was available before the rubberband has // been activated in case if no Shift- or Control-key are pressed const bool shiftOrControlPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier || QApplication::keyboardModifiers() & Qt::ControlModifier; if (!shiftOrControlPressed) { m_oldSelection.clear(); } } KItemSet selectedItems; // Select all visible items that intersect with the rubberband foreach (const KItemListWidget* widget, m_view->visibleItemListWidgets()) { const int index = widget->index(); const QRectF widgetRect = m_view->itemRect(index); if (widgetRect.intersects(rubberBandRect)) { const QRectF iconRect = widget->iconRect().translated(widgetRect.topLeft()); const QRectF textRect = widget->textRect().translated(widgetRect.topLeft()); if (iconRect.intersects(rubberBandRect) || textRect.intersects(rubberBandRect)) { selectedItems.insert(index); } } } // Select all invisible items that intersect with the rubberband. Instead of // iterating all items only the area which might be touched by the rubberband // will be checked. const bool increaseIndex = scrollVertical ? startPos.y() > endPos.y(): startPos.x() > endPos.x(); int index = increaseIndex ? m_view->lastVisibleIndex() + 1 : m_view->firstVisibleIndex() - 1; bool selectionFinished = false; do { const QRectF widgetRect = m_view->itemRect(index); if (widgetRect.intersects(rubberBandRect)) { selectedItems.insert(index); } if (increaseIndex) { ++index; selectionFinished = (index >= m_model->count()) || ( scrollVertical && widgetRect.top() > rubberBandRect.bottom()) || (!scrollVertical && widgetRect.left() > rubberBandRect.right()); } else { --index; selectionFinished = (index < 0) || ( scrollVertical && widgetRect.bottom() < rubberBandRect.top()) || (!scrollVertical && widgetRect.right() < rubberBandRect.left()); } } while (!selectionFinished); if (QApplication::keyboardModifiers() & Qt::ControlModifier) { // If Control is pressed, the selection state of all items in the rubberband is toggled. // Therefore, the new selection contains: // 1. All previously selected items which are not inside the rubberband, and // 2. all items inside the rubberband which have not been selected previously. m_selectionManager->setSelectedItems(m_oldSelection ^ selectedItems); } else { m_selectionManager->setSelectedItems(selectedItems + m_oldSelection); } } void KItemListController::startDragging() { if (!m_view || !m_model) { return; } const KItemSet selectedItems = m_selectionManager->selectedItems(); if (selectedItems.isEmpty()) { return; } QMimeData* data = m_model->createMimeData(selectedItems); if (!data) { return; } // The created drag object will be owned and deleted // by QApplication::activeWindow(). QDrag* drag = new QDrag(QApplication::activeWindow()); drag->setMimeData(data); const QPixmap pixmap = m_view->createDragPixmap(selectedItems); drag->setPixmap(pixmap); const QPoint hotSpot((pixmap.width() / pixmap.devicePixelRatio()) / 2, 0); drag->setHotSpot(hotSpot); drag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::CopyAction); QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropStart); QAccessible::updateAccessibility(&accessibilityEvent); } KItemListWidget* KItemListController::hoveredWidget() const { Q_ASSERT(m_view); foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) { if (widget->isHovered()) { return widget; } } return nullptr; } KItemListWidget* KItemListController::widgetForPos(const QPointF& pos) const { Q_ASSERT(m_view); foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) { const QPointF mappedPos = widget->mapFromItem(m_view, pos); const bool hovered = widget->contains(mappedPos) && !widget->expansionToggleRect().contains(mappedPos); if (hovered) { return widget; } } return nullptr; } void KItemListController::updateKeyboardAnchor() { const bool validAnchor = m_keyboardAnchorIndex >= 0 && m_keyboardAnchorIndex < m_model->count() && keyboardAnchorPos(m_keyboardAnchorIndex) == m_keyboardAnchorPos; if (!validAnchor) { const int index = m_selectionManager->currentItem(); m_keyboardAnchorIndex = index; m_keyboardAnchorPos = keyboardAnchorPos(index); } } int KItemListController::nextRowIndex(int index) const { if (m_keyboardAnchorIndex < 0) { return index; } const int maxIndex = m_model->count() - 1; if (index == maxIndex) { return index; } // Calculate the index of the last column inside the row of the current index int lastColumnIndex = index; while (keyboardAnchorPos(lastColumnIndex + 1) > keyboardAnchorPos(lastColumnIndex)) { ++lastColumnIndex; if (lastColumnIndex >= maxIndex) { return index; } } // Based on the last column index go to the next row and calculate the nearest index // that is below the current index int nextRowIndex = lastColumnIndex + 1; int searchIndex = nextRowIndex; qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(nextRowIndex)); while (searchIndex < maxIndex && keyboardAnchorPos(searchIndex + 1) > keyboardAnchorPos(searchIndex)) { ++searchIndex; const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex)); if (searchDiff < minDiff) { minDiff = searchDiff; nextRowIndex = searchIndex; } } return nextRowIndex; } int KItemListController::previousRowIndex(int index) const { if (m_keyboardAnchorIndex < 0 || index == 0) { return index; } // Calculate the index of the first column inside the row of the current index int firstColumnIndex = index; while (keyboardAnchorPos(firstColumnIndex - 1) < keyboardAnchorPos(firstColumnIndex)) { --firstColumnIndex; if (firstColumnIndex <= 0) { return index; } } // Based on the first column index go to the previous row and calculate the nearest index // that is above the current index int previousRowIndex = firstColumnIndex - 1; int searchIndex = previousRowIndex; qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(previousRowIndex)); while (searchIndex > 0 && keyboardAnchorPos(searchIndex - 1) < keyboardAnchorPos(searchIndex)) { --searchIndex; const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex)); if (searchDiff < minDiff) { minDiff = searchDiff; previousRowIndex = searchIndex; } } return previousRowIndex; } qreal KItemListController::keyboardAnchorPos(int index) const { const QRectF itemRect = m_view->itemRect(index); if (!itemRect.isEmpty()) { return (m_view->scrollOrientation() == Qt::Vertical) ? itemRect.x() : itemRect.y(); } return 0; } void KItemListController::updateExtendedSelectionRegion() { if (m_view) { const bool extend = (m_selectionBehavior != MultiSelection); KItemListStyleOption option = m_view->styleOption(); if (option.extendedSelectionRegion != extend) { option.extendedSelectionRegion = extend; m_view->setStyleOption(option); } } } diff --git a/src/kitemviews/kitemlistselectionmanager.cpp b/src/kitemviews/kitemlistselectionmanager.cpp index 5d7b08eeb..efc256e1e 100644 --- a/src/kitemviews/kitemlistselectionmanager.cpp +++ b/src/kitemviews/kitemlistselectionmanager.cpp @@ -1,398 +1,396 @@ /*************************************************************************** * Copyright (C) 2011 by Peter Penz * * Copyright (C) 2011 by Frank Reininghaus * * * * Based on the Itemviews NG project from Trolltech Labs: * * http://qt.gitorious.org/qt-labs/itemviews-ng * * * * 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 "kitemlistselectionmanager.h" -#include "kitemmodelbase.h" - KItemListSelectionManager::KItemListSelectionManager(QObject* parent) : QObject(parent), m_currentItem(-1), m_anchorItem(-1), m_selectedItems(), m_isAnchoredSelectionActive(false), m_model(nullptr) { } KItemListSelectionManager::~KItemListSelectionManager() { } void KItemListSelectionManager::setCurrentItem(int current) { const int previous = m_currentItem; const KItemSet previousSelection = selectedItems(); if (m_model && current >= 0 && current < m_model->count()) { m_currentItem = current; } else { m_currentItem = -1; } if (m_currentItem != previous) { emit currentChanged(m_currentItem, previous); if (m_isAnchoredSelectionActive) { const KItemSet selection = selectedItems(); if (selection != previousSelection) { emit selectionChanged(selection, previousSelection); } } } } int KItemListSelectionManager::currentItem() const { return m_currentItem; } void KItemListSelectionManager::setSelectedItems(const KItemSet& items) { if (m_selectedItems != items) { const KItemSet previous = m_selectedItems; m_selectedItems = items; emit selectionChanged(m_selectedItems, previous); } } KItemSet KItemListSelectionManager::selectedItems() const { KItemSet selectedItems = m_selectedItems; if (m_isAnchoredSelectionActive && m_anchorItem != m_currentItem) { Q_ASSERT(m_anchorItem >= 0); Q_ASSERT(m_currentItem >= 0); const int from = qMin(m_anchorItem, m_currentItem); const int to = qMax(m_anchorItem, m_currentItem); for (int index = from; index <= to; ++index) { selectedItems.insert(index); } } return selectedItems; } bool KItemListSelectionManager::isSelected(int index) const { if (m_selectedItems.contains(index)) { return true; } if (m_isAnchoredSelectionActive && m_anchorItem != m_currentItem) { Q_ASSERT(m_anchorItem >= 0); Q_ASSERT(m_currentItem >= 0); const int from = qMin(m_anchorItem, m_currentItem); const int to = qMax(m_anchorItem, m_currentItem); if (from <= index && index <= to) { return true; } } return false; } bool KItemListSelectionManager::hasSelection() const { return !m_selectedItems.isEmpty() || (m_isAnchoredSelectionActive && m_anchorItem != m_currentItem); } void KItemListSelectionManager::setSelected(int index, int count, SelectionMode mode) { if (index < 0 || count < 1 || !m_model || index >= m_model->count()) { return; } endAnchoredSelection(); const KItemSet previous = 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; } const KItemSet selection = selectedItems(); if (selection != previous) { emit selectionChanged(selection, previous); } } void KItemListSelectionManager::clearSelection() { const KItemSet previous = selectedItems(); if (!previous.isEmpty()) { m_selectedItems.clear(); m_isAnchoredSelectionActive = false; emit selectionChanged(KItemSet(), previous); } } void KItemListSelectionManager::beginAnchoredSelection(int anchor) { if (anchor >= 0 && m_model && anchor < m_model->count()) { m_isAnchoredSelectionActive = true; m_anchorItem = anchor; } } void KItemListSelectionManager::endAnchoredSelection() { if (m_isAnchoredSelectionActive && (m_anchorItem != m_currentItem)) { Q_ASSERT(m_anchorItem >= 0); Q_ASSERT(m_currentItem >= 0); const int from = qMin(m_anchorItem, m_currentItem); const int to = qMax(m_anchorItem, m_currentItem); for (int index = from; index <= to; ++index) { m_selectedItems.insert(index); } } m_isAnchoredSelectionActive = false; } bool KItemListSelectionManager::isAnchoredSelectionActive() const { return m_isAnchoredSelectionActive; } KItemModelBase* KItemListSelectionManager::model() const { return m_model; } void KItemListSelectionManager::setModel(KItemModelBase* model) { m_model = model; if (model && model->count() > 0) { m_currentItem = 0; } } void KItemListSelectionManager::itemsInserted(const KItemRangeList& itemRanges) { // Store the current selection (needed in the selectionChanged() signal) const KItemSet previousSelection = selectedItems(); // Update the current item if (m_currentItem < 0) { setCurrentItem(0); } else { const int previousCurrent = m_currentItem; int inc = 0; foreach (const KItemRange& itemRange, itemRanges) { if (m_currentItem < itemRange.index) { break; } inc += itemRange.count; } // Calling setCurrentItem would trigger the selectionChanged signal, but we want to // emit it only once in this function -> change the current item manually and emit currentChanged m_currentItem += inc; emit currentChanged(m_currentItem, previousCurrent); } // Update the anchor item if (m_anchorItem < 0) { m_anchorItem = 0; } else { int inc = 0; foreach (const KItemRange& itemRange, itemRanges) { if (m_anchorItem < itemRange.index) { break; } inc += itemRange.count; } m_anchorItem += inc; } // Update the selections if (!m_selectedItems.isEmpty()) { const KItemSet previous = m_selectedItems; m_selectedItems.clear(); for (int index: previous) { int inc = 0; foreach (const KItemRange& itemRange, itemRanges) { if (index < itemRange.index) { break; } inc += itemRange.count; } m_selectedItems.insert(index + inc); } } const KItemSet selection = selectedItems(); if (selection != previousSelection) { emit selectionChanged(selection, previousSelection); } } void KItemListSelectionManager::itemsRemoved(const KItemRangeList& itemRanges) { // Store the current selection (needed in the selectionChanged() signal) const KItemSet previousSelection = selectedItems(); const int previousCurrent = m_currentItem; // Update the current item m_currentItem = indexAfterRangesRemoving(m_currentItem, itemRanges, DiscardRemovedIndex); if (m_currentItem != previousCurrent) { emit currentChanged(m_currentItem, previousCurrent); if (m_currentItem < 0) { // Calling setCurrentItem() would trigger the selectionChanged signal, but we want to // emit it only once in this function -> change the current item manually and emit currentChanged m_currentItem = indexAfterRangesRemoving(previousCurrent, itemRanges, AdjustRemovedIndex); emit currentChanged(m_currentItem, -1); } } // Update the anchor item if (m_anchorItem >= 0) { m_anchorItem = indexAfterRangesRemoving(m_anchorItem, itemRanges, DiscardRemovedIndex); if (m_anchorItem < 0) { m_isAnchoredSelectionActive = false; } } // Update the selections and the anchor item if (!m_selectedItems.isEmpty()) { const KItemSet previous = m_selectedItems; m_selectedItems.clear(); for (int oldIndex : previous) { const int index = indexAfterRangesRemoving(oldIndex, itemRanges, DiscardRemovedIndex); if (index >= 0) { m_selectedItems.insert(index); } } } const KItemSet selection = selectedItems(); if (selection != previousSelection) { emit selectionChanged(selection, previousSelection); } Q_ASSERT(m_currentItem < m_model->count()); Q_ASSERT(m_anchorItem < m_model->count()); } void KItemListSelectionManager::itemsMoved(const KItemRange& itemRange, const QList& movedToIndexes) { // Store the current selection (needed in the selectionChanged() signal) const KItemSet previousSelection = selectedItems(); // endAnchoredSelection() adds all items between m_currentItem and // m_anchorItem to m_selectedItems. They can then be moved // individually later in this function. endAnchoredSelection(); // Update the current item if (m_currentItem >= itemRange.index && m_currentItem < itemRange.index + itemRange.count) { const int previousCurrentItem = m_currentItem; const int newCurrentItem = movedToIndexes.at(previousCurrentItem - itemRange.index); // Calling setCurrentItem would trigger the selectionChanged signal, but we want to // emit it only once in this function -> change the current item manually and emit currentChanged m_currentItem = newCurrentItem; emit currentChanged(newCurrentItem, previousCurrentItem); } // Start a new anchored selection. beginAnchoredSelection(m_currentItem); // Update the selections if (!m_selectedItems.isEmpty()) { const KItemSet previous = m_selectedItems; m_selectedItems.clear(); for (int index : previous) { if (index >= itemRange.index && index < itemRange.index + itemRange.count) { m_selectedItems.insert(movedToIndexes.at(index - itemRange.index)); } else { m_selectedItems.insert(index); } } } const KItemSet selection = selectedItems(); if (selection != previousSelection) { emit selectionChanged(selection, previousSelection); } } int KItemListSelectionManager::indexAfterRangesRemoving(int index, const KItemRangeList& itemRanges, const RangesRemovingBehaviour behaviour) const { int dec = 0; foreach (const KItemRange& itemRange, itemRanges) { if (index < itemRange.index) { break; } dec += itemRange.count; const int firstIndexAfterRange = itemRange.index + itemRange.count; if (index < firstIndexAfterRange) { // The index is part of the removed range if (behaviour == DiscardRemovedIndex) { return -1; } else { // Use the first item after the range as new index index = firstIndexAfterRange; break; } } } return qBound(-1, index - dec, m_model->count() - 1); } diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index dbeb571a4..f457b00ac 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -1,2745 +1,2740 @@ /*************************************************************************** * Copyright (C) 2011 by Peter Penz * * * * Based on the Itemviews NG project from Trolltech Labs: * * http://qt.gitorious.org/qt-labs/itemviews-ng * * * * 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 "kitemlistview.h" #include "dolphindebug.h" #include "kitemlistcontainer.h" #include "kitemlistcontroller.h" #include "kitemlistheader.h" #include "kitemlistselectionmanager.h" #include "kstandarditemlistwidget.h" #include "private/kitemlistheaderwidget.h" #include "private/kitemlistrubberband.h" #include "private/kitemlistsizehintresolver.h" #include "private/kitemlistviewlayouter.h" -#include "private/kitemlistviewanimation.h" #include #include -#include -#include #include #include #include -#include - #include "kitemlistviewaccessible.h" namespace { // Time in ms until reaching the autoscroll margin triggers // an initial autoscrolling const int InitialAutoScrollDelay = 700; // Delay in ms for triggering the next autoscroll const int RepeatingAutoScrollDelay = 1000 / 60; } #ifndef QT_NO_ACCESSIBILITY QAccessibleInterface* accessibleInterfaceFactory(const QString& key, QObject* object) { Q_UNUSED(key) if (KItemListContainer* container = qobject_cast(object)) { return new KItemListContainerAccessible(container); } else if (KItemListView* view = qobject_cast(object)) { return new KItemListViewAccessible(view); } return nullptr; } #endif KItemListView::KItemListView(QGraphicsWidget* parent) : QGraphicsWidget(parent), m_enabledSelectionToggles(false), m_grouped(false), m_supportsItemExpanding(false), m_editingRole(false), m_activeTransactions(0), m_endTransactionAnimationHint(Animation), m_itemSize(), m_controller(nullptr), m_model(nullptr), m_visibleRoles(), m_widgetCreator(nullptr), m_groupHeaderCreator(nullptr), m_styleOption(), m_visibleItems(), m_visibleGroups(), m_visibleCells(), m_sizeHintResolver(nullptr), m_layouter(nullptr), m_animation(nullptr), m_layoutTimer(nullptr), m_oldScrollOffset(0), m_oldMaximumScrollOffset(0), m_oldItemOffset(0), m_oldMaximumItemOffset(0), m_skipAutoScrollForRubberBand(false), m_rubberBand(nullptr), m_mousePos(), m_autoScrollIncrement(0), m_autoScrollTimer(nullptr), m_header(nullptr), m_headerWidget(nullptr), m_dropIndicator() { setAcceptHoverEvents(true); m_sizeHintResolver = new KItemListSizeHintResolver(this); m_layouter = new KItemListViewLayouter(m_sizeHintResolver, this); m_animation = new KItemListViewAnimation(this); connect(m_animation, &KItemListViewAnimation::finished, this, &KItemListView::slotAnimationFinished); m_layoutTimer = new QTimer(this); m_layoutTimer->setInterval(300); m_layoutTimer->setSingleShot(true); connect(m_layoutTimer, &QTimer::timeout, this, &KItemListView::slotLayoutTimerFinished); m_rubberBand = new KItemListRubberBand(this); connect(m_rubberBand, &KItemListRubberBand::activationChanged, this, &KItemListView::slotRubberBandActivationChanged); m_headerWidget = new KItemListHeaderWidget(this); m_headerWidget->setVisible(false); m_header = new KItemListHeader(this); #ifndef QT_NO_ACCESSIBILITY QAccessible::installFactory(accessibleInterfaceFactory); #endif } KItemListView::~KItemListView() { // The group headers are children of the widgets created by // widgetCreator(). So it is mandatory to delete the group headers // first. delete m_groupHeaderCreator; m_groupHeaderCreator = nullptr; delete m_widgetCreator; m_widgetCreator = nullptr; delete m_sizeHintResolver; m_sizeHintResolver = nullptr; } void KItemListView::setScrollOffset(qreal offset) { if (offset < 0) { offset = 0; } const qreal previousOffset = m_layouter->scrollOffset(); if (offset == previousOffset) { return; } m_layouter->setScrollOffset(offset); m_animation->setScrollOffset(offset); // Don't check whether the m_layoutTimer is active: Changing the // scroll offset must always trigger a synchronous layout, otherwise // the smooth-scrolling might get jerky. doLayout(NoAnimation); onScrollOffsetChanged(offset, previousOffset); } qreal KItemListView::scrollOffset() const { return m_layouter->scrollOffset(); } qreal KItemListView::maximumScrollOffset() const { return m_layouter->maximumScrollOffset(); } void KItemListView::setItemOffset(qreal offset) { if (m_layouter->itemOffset() == offset) { return; } m_layouter->setItemOffset(offset); if (m_headerWidget->isVisible()) { m_headerWidget->setOffset(offset); } // Don't check whether the m_layoutTimer is active: Changing the // item offset must always trigger a synchronous layout, otherwise // the smooth-scrolling might get jerky. doLayout(NoAnimation); } qreal KItemListView::itemOffset() const { return m_layouter->itemOffset(); } qreal KItemListView::maximumItemOffset() const { return m_layouter->maximumItemOffset(); } int KItemListView::maximumVisibleItems() const { return m_layouter->maximumVisibleItems(); } void KItemListView::setVisibleRoles(const QList& roles) { const QList previousRoles = m_visibleRoles; m_visibleRoles = roles; onVisibleRolesChanged(roles, previousRoles); m_sizeHintResolver->clearCache(); m_layouter->markAsDirty(); if (m_itemSize.isEmpty()) { m_headerWidget->setColumns(roles); updatePreferredColumnWidths(); if (!m_headerWidget->automaticColumnResizing()) { // The column-width of new roles are still 0. Apply the preferred // column-width as default with. foreach (const QByteArray& role, m_visibleRoles) { if (m_headerWidget->columnWidth(role) == 0) { const qreal width = m_headerWidget->preferredColumnWidth(role); m_headerWidget->setColumnWidth(role, width); } } applyColumnWidthsFromHeader(); } } const bool alternateBackgroundsChanged = m_itemSize.isEmpty() && ((roles.count() > 1 && previousRoles.count() <= 1) || (roles.count() <= 1 && previousRoles.count() > 1)); QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); KItemListWidget* widget = it.value(); widget->setVisibleRoles(roles); if (alternateBackgroundsChanged) { updateAlternateBackgroundForWidget(widget); } } doLayout(NoAnimation); } QList KItemListView::visibleRoles() const { return m_visibleRoles; } void KItemListView::setAutoScroll(bool enabled) { if (enabled && !m_autoScrollTimer) { m_autoScrollTimer = new QTimer(this); m_autoScrollTimer->setSingleShot(true); connect(m_autoScrollTimer, &QTimer::timeout, this, &KItemListView::triggerAutoScrolling); m_autoScrollTimer->start(InitialAutoScrollDelay); } else if (!enabled && m_autoScrollTimer) { delete m_autoScrollTimer; m_autoScrollTimer = nullptr; } } bool KItemListView::autoScroll() const { return m_autoScrollTimer != nullptr; } void KItemListView::setEnabledSelectionToggles(bool enabled) { if (m_enabledSelectionToggles != enabled) { m_enabledSelectionToggles = enabled; QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); it.value()->setEnabledSelectionToggle(enabled); } } } bool KItemListView::enabledSelectionToggles() const { return m_enabledSelectionToggles; } KItemListController* KItemListView::controller() const { return m_controller; } KItemModelBase* KItemListView::model() const { return m_model; } void KItemListView::setWidgetCreator(KItemListWidgetCreatorBase* widgetCreator) { delete m_widgetCreator; m_widgetCreator = widgetCreator; } KItemListWidgetCreatorBase* KItemListView::widgetCreator() const { if (!m_widgetCreator) { m_widgetCreator = defaultWidgetCreator(); } return m_widgetCreator; } void KItemListView::setGroupHeaderCreator(KItemListGroupHeaderCreatorBase* groupHeaderCreator) { delete m_groupHeaderCreator; m_groupHeaderCreator = groupHeaderCreator; } KItemListGroupHeaderCreatorBase* KItemListView::groupHeaderCreator() const { if (!m_groupHeaderCreator) { m_groupHeaderCreator = defaultGroupHeaderCreator(); } return m_groupHeaderCreator; } QSizeF KItemListView::itemSize() const { return m_itemSize; } QSizeF KItemListView::itemSizeHint() const { return m_sizeHintResolver->minSizeHint(); } const KItemListStyleOption& KItemListView::styleOption() const { return m_styleOption; } void KItemListView::setGeometry(const QRectF& rect) { QGraphicsWidget::setGeometry(rect); if (!m_model) { return; } const QSizeF newSize = rect.size(); if (m_itemSize.isEmpty()) { m_headerWidget->resize(rect.width(), m_headerWidget->size().height()); if (m_headerWidget->automaticColumnResizing()) { applyAutomaticColumnWidths(); } else { const qreal requiredWidth = columnWidthsSum(); const QSizeF dynamicItemSize(qMax(newSize.width(), requiredWidth), m_itemSize.height()); m_layouter->setItemSize(dynamicItemSize); } // Triggering a synchronous layout is fine from a performance point of view, // as with dynamic item sizes no moving animation must be done. m_layouter->setSize(newSize); doLayout(NoAnimation); } else { const bool animate = !changesItemGridLayout(newSize, m_layouter->itemSize(), m_layouter->itemMargin()); m_layouter->setSize(newSize); if (animate) { // Trigger an asynchronous relayout with m_layoutTimer to prevent // performance bottlenecks. If the timer is exceeded, an animated layout // will be triggered. if (!m_layoutTimer->isActive()) { m_layoutTimer->start(); } } else { m_layoutTimer->stop(); doLayout(NoAnimation); } } } qreal KItemListView::verticalPageStep() const { qreal headerHeight = 0; if (m_headerWidget->isVisible()) { headerHeight = m_headerWidget->size().height(); } return size().height() - headerHeight; } int KItemListView::itemAt(const QPointF& pos) const { QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); const KItemListWidget* widget = it.value(); const QPointF mappedPos = widget->mapFromItem(this, pos); if (widget->contains(mappedPos)) { return it.key(); } } return -1; } bool KItemListView::isAboveSelectionToggle(int index, const QPointF& pos) const { if (!m_enabledSelectionToggles) { return false; } const KItemListWidget* widget = m_visibleItems.value(index); if (widget) { const QRectF selectionToggleRect = widget->selectionToggleRect(); if (!selectionToggleRect.isEmpty()) { const QPointF mappedPos = widget->mapFromItem(this, pos); return selectionToggleRect.contains(mappedPos); } } return false; } bool KItemListView::isAboveExpansionToggle(int index, const QPointF& pos) const { const KItemListWidget* widget = m_visibleItems.value(index); if (widget) { const QRectF expansionToggleRect = widget->expansionToggleRect(); if (!expansionToggleRect.isEmpty()) { const QPointF mappedPos = widget->mapFromItem(this, pos); return expansionToggleRect.contains(mappedPos); } } return false; } bool KItemListView::isAboveText(int index, const QPointF &pos) const { const KItemListWidget* widget = m_visibleItems.value(index); if (widget) { const QRectF &textRect = widget->textRect(); if (!textRect.isEmpty()) { const QPointF mappedPos = widget->mapFromItem(this, pos); return textRect.contains(mappedPos); } } return false; } int KItemListView::firstVisibleIndex() const { return m_layouter->firstVisibleIndex(); } int KItemListView::lastVisibleIndex() const { return m_layouter->lastVisibleIndex(); } void KItemListView::calculateItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint) const { widgetCreator()->calculateItemSizeHints(logicalHeightHints, logicalWidthHint, this); } void KItemListView::setSupportsItemExpanding(bool supportsExpanding) { if (m_supportsItemExpanding != supportsExpanding) { m_supportsItemExpanding = supportsExpanding; updateSiblingsInformation(); onSupportsItemExpandingChanged(supportsExpanding); } } bool KItemListView::supportsItemExpanding() const { return m_supportsItemExpanding; } QRectF KItemListView::itemRect(int index) const { return m_layouter->itemRect(index); } QRectF KItemListView::itemContextRect(int index) const { QRectF contextRect; const KItemListWidget* widget = m_visibleItems.value(index); if (widget) { contextRect = widget->iconRect() | widget->textRect(); contextRect.translate(itemRect(index).topLeft()); } return contextRect; } void KItemListView::scrollToItem(int index) { QRectF viewGeometry = geometry(); if (m_headerWidget->isVisible()) { const qreal headerHeight = m_headerWidget->size().height(); viewGeometry.adjust(0, headerHeight, 0, 0); } QRectF currentRect = itemRect(index); // Fix for Bug 311099 - View the underscore when using Ctrl + PagDown currentRect.adjust(-m_styleOption.horizontalMargin, -m_styleOption.verticalMargin, m_styleOption.horizontalMargin, m_styleOption.verticalMargin); if (!viewGeometry.contains(currentRect)) { qreal newOffset = scrollOffset(); if (scrollOrientation() == Qt::Vertical) { if (currentRect.top() < viewGeometry.top()) { newOffset += currentRect.top() - viewGeometry.top(); } else if (currentRect.bottom() > viewGeometry.bottom()) { newOffset += currentRect.bottom() - viewGeometry.bottom(); } } else { if (currentRect.left() < viewGeometry.left()) { newOffset += currentRect.left() - viewGeometry.left(); } else if (currentRect.right() > viewGeometry.right()) { newOffset += currentRect.right() - viewGeometry.right(); } } if (newOffset != scrollOffset()) { emit scrollTo(newOffset); } } } void KItemListView::beginTransaction() { ++m_activeTransactions; if (m_activeTransactions == 1) { onTransactionBegin(); } } void KItemListView::endTransaction() { --m_activeTransactions; if (m_activeTransactions < 0) { m_activeTransactions = 0; qCWarning(DolphinDebug) << "Mismatch between beginTransaction()/endTransaction()"; } if (m_activeTransactions == 0) { onTransactionEnd(); doLayout(m_endTransactionAnimationHint); m_endTransactionAnimationHint = Animation; } } bool KItemListView::isTransactionActive() const { return m_activeTransactions > 0; } void KItemListView::setHeaderVisible(bool visible) { if (visible && !m_headerWidget->isVisible()) { QStyleOptionHeader option; const QSize headerSize = style()->sizeFromContents(QStyle::CT_HeaderSection, &option, QSize()); m_headerWidget->setPos(0, 0); m_headerWidget->resize(size().width(), headerSize.height()); m_headerWidget->setModel(m_model); m_headerWidget->setColumns(m_visibleRoles); m_headerWidget->setZValue(1); connect(m_headerWidget, &KItemListHeaderWidget::columnWidthChanged, this, &KItemListView::slotHeaderColumnWidthChanged); connect(m_headerWidget, &KItemListHeaderWidget::columnMoved, this, &KItemListView::slotHeaderColumnMoved); connect(m_headerWidget, &KItemListHeaderWidget::sortOrderChanged, this, &KItemListView::sortOrderChanged); connect(m_headerWidget, &KItemListHeaderWidget::sortRoleChanged, this, &KItemListView::sortRoleChanged); m_layouter->setHeaderHeight(headerSize.height()); m_headerWidget->setVisible(true); } else if (!visible && m_headerWidget->isVisible()) { disconnect(m_headerWidget, &KItemListHeaderWidget::columnWidthChanged, this, &KItemListView::slotHeaderColumnWidthChanged); disconnect(m_headerWidget, &KItemListHeaderWidget::columnMoved, this, &KItemListView::slotHeaderColumnMoved); disconnect(m_headerWidget, &KItemListHeaderWidget::sortOrderChanged, this, &KItemListView::sortOrderChanged); disconnect(m_headerWidget, &KItemListHeaderWidget::sortRoleChanged, this, &KItemListView::sortRoleChanged); m_layouter->setHeaderHeight(0); m_headerWidget->setVisible(false); } } bool KItemListView::isHeaderVisible() const { return m_headerWidget->isVisible(); } KItemListHeader* KItemListView::header() const { return m_header; } QPixmap KItemListView::createDragPixmap(const KItemSet& indexes) const { QPixmap pixmap; if (indexes.count() == 1) { KItemListWidget* item = m_visibleItems.value(indexes.first()); QGraphicsView* graphicsView = scene()->views()[0]; if (item && graphicsView) { pixmap = item->createDragPixmap(nullptr, graphicsView); } } else { // TODO: Not implemented yet. Probably extend the interface // from KItemListWidget::createDragPixmap() to return a pixmap // that can be used for multiple indexes. } return pixmap; } void KItemListView::editRole(int index, const QByteArray& role) { KStandardItemListWidget* widget = qobject_cast(m_visibleItems.value(index)); if (!widget || m_editingRole) { return; } m_editingRole = true; widget->setEditedRole(role); connect(widget, &KItemListWidget::roleEditingCanceled, this, &KItemListView::slotRoleEditingCanceled); connect(widget, &KItemListWidget::roleEditingFinished, this, &KItemListView::slotRoleEditingFinished); connect(this, &KItemListView::scrollOffsetChanged, widget, &KStandardItemListWidget::finishRoleEditing); } void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { QGraphicsWidget::paint(painter, option, widget); if (m_rubberBand->isActive()) { QRectF rubberBandRect = QRectF(m_rubberBand->startPosition(), m_rubberBand->endPosition()).normalized(); const QPointF topLeft = rubberBandRect.topLeft(); if (scrollOrientation() == Qt::Vertical) { rubberBandRect.moveTo(topLeft.x(), topLeft.y() - scrollOffset()); } else { rubberBandRect.moveTo(topLeft.x() - scrollOffset(), topLeft.y()); } QStyleOptionRubberBand opt; opt.initFrom(widget); opt.shape = QRubberBand::Rectangle; opt.opaque = false; opt.rect = rubberBandRect.toRect(); style()->drawControl(QStyle::CE_RubberBand, &opt, painter); } if (!m_dropIndicator.isEmpty()) { const QRectF r = m_dropIndicator.toRect(); QColor color = palette().brush(QPalette::Normal, QPalette::Highlight).color(); painter->setPen(color); // TODO: The following implementation works only for a vertical scroll-orientation // and assumes a height of the m_draggingInsertIndicator of 1. Q_ASSERT(r.height() == 1); painter->drawLine(r.left() + 1, r.top(), r.right() - 1, r.top()); color.setAlpha(128); painter->setPen(color); painter->drawRect(r.left(), r.top() - 1, r.width() - 1, 2); } } QVariant KItemListView::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == QGraphicsItem::ItemSceneHasChanged && scene()) { if (!scene()->views().isEmpty()) { m_styleOption.palette = scene()->views().at(0)->palette(); } } return QGraphicsItem::itemChange(change, value); } void KItemListView::setItemSize(const QSizeF& size) { const QSizeF previousSize = m_itemSize; if (size == previousSize) { return; } // Skip animations when the number of rows or columns // are changed in the grid layout. Although the animation // engine can handle this usecase, it looks obtrusive. const bool animate = !changesItemGridLayout(m_layouter->size(), size, m_layouter->itemMargin()); const bool alternateBackgroundsChanged = (m_visibleRoles.count() > 1) && (( m_itemSize.isEmpty() && !size.isEmpty()) || (!m_itemSize.isEmpty() && size.isEmpty())); m_itemSize = size; if (alternateBackgroundsChanged) { // For an empty item size alternate backgrounds are drawn if more than // one role is shown. Assure that the backgrounds for visible items are // updated when changing the size in this context. updateAlternateBackgrounds(); } if (size.isEmpty()) { if (m_headerWidget->automaticColumnResizing()) { updatePreferredColumnWidths(); } else { // Only apply the changed height and respect the header widths // set by the user const qreal currentWidth = m_layouter->itemSize().width(); const QSizeF newSize(currentWidth, size.height()); m_layouter->setItemSize(newSize); } } else { m_layouter->setItemSize(size); } m_sizeHintResolver->clearCache(); doLayout(animate ? Animation : NoAnimation); onItemSizeChanged(size, previousSize); } void KItemListView::setStyleOption(const KItemListStyleOption& option) { const KItemListStyleOption previousOption = m_styleOption; m_styleOption = option; bool animate = true; const QSizeF margin(option.horizontalMargin, option.verticalMargin); if (margin != m_layouter->itemMargin()) { // Skip animations when the number of rows or columns // are changed in the grid layout. Although the animation // engine can handle this usecase, it looks obtrusive. animate = !changesItemGridLayout(m_layouter->size(), m_layouter->itemSize(), margin); m_layouter->setItemMargin(margin); } if (m_grouped) { updateGroupHeaderHeight(); } if (animate && (previousOption.maxTextLines != option.maxTextLines || previousOption.maxTextWidth != option.maxTextWidth)) { // Animating a change of the maximum text size just results in expensive // temporary eliding and clipping operations and does not look good visually. animate = false; } QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); it.value()->setStyleOption(option); } m_sizeHintResolver->clearCache(); m_layouter->markAsDirty(); doLayout(animate ? Animation : NoAnimation); if (m_itemSize.isEmpty()) { updatePreferredColumnWidths(); } onStyleOptionChanged(option, previousOption); } void KItemListView::setScrollOrientation(Qt::Orientation orientation) { const Qt::Orientation previousOrientation = m_layouter->scrollOrientation(); if (orientation == previousOrientation) { return; } m_layouter->setScrollOrientation(orientation); m_animation->setScrollOrientation(orientation); m_sizeHintResolver->clearCache(); if (m_grouped) { QMutableHashIterator it (m_visibleGroups); while (it.hasNext()) { it.next(); it.value()->setScrollOrientation(orientation); } updateGroupHeaderHeight(); } doLayout(NoAnimation); onScrollOrientationChanged(orientation, previousOrientation); emit scrollOrientationChanged(orientation, previousOrientation); } Qt::Orientation KItemListView::scrollOrientation() const { return m_layouter->scrollOrientation(); } KItemListWidgetCreatorBase* KItemListView::defaultWidgetCreator() const { return nullptr; } KItemListGroupHeaderCreatorBase* KItemListView::defaultGroupHeaderCreator() const { return nullptr; } void KItemListView::initializeItemListWidget(KItemListWidget* item) { Q_UNUSED(item); } bool KItemListView::itemSizeHintUpdateRequired(const QSet& changedRoles) const { Q_UNUSED(changedRoles); return true; } void KItemListView::onControllerChanged(KItemListController* current, KItemListController* previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListView::onScrollOffsetChanged(qreal current, qreal previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListView::onVisibleRolesChanged(const QList& current, const QList& previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListView::onSupportsItemExpandingChanged(bool supportsExpanding) { Q_UNUSED(supportsExpanding); } void KItemListView::onTransactionBegin() { } void KItemListView::onTransactionEnd() { } bool KItemListView::event(QEvent* event) { switch (event->type()) { case QEvent::PaletteChange: updatePalette(); break; case QEvent::FontChange: updateFont(); break; default: // Forward all other events to the controller and handle them there if (!m_editingRole && m_controller && m_controller->processEvent(event, transform())) { event->accept(); return true; } } return QGraphicsWidget::event(event); } void KItemListView::mousePressEvent(QGraphicsSceneMouseEvent* event) { m_mousePos = transform().map(event->pos()); event->accept(); } void KItemListView::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { QGraphicsWidget::mouseMoveEvent(event); m_mousePos = transform().map(event->pos()); if (m_autoScrollTimer && !m_autoScrollTimer->isActive()) { m_autoScrollTimer->start(InitialAutoScrollDelay); } } void KItemListView::dragEnterEvent(QGraphicsSceneDragDropEvent* event) { event->setAccepted(true); setAutoScroll(true); } void KItemListView::dragMoveEvent(QGraphicsSceneDragDropEvent* event) { QGraphicsWidget::dragMoveEvent(event); m_mousePos = transform().map(event->pos()); if (m_autoScrollTimer && !m_autoScrollTimer->isActive()) { m_autoScrollTimer->start(InitialAutoScrollDelay); } } void KItemListView::dragLeaveEvent(QGraphicsSceneDragDropEvent* event) { QGraphicsWidget::dragLeaveEvent(event); setAutoScroll(false); } void KItemListView::dropEvent(QGraphicsSceneDragDropEvent* event) { QGraphicsWidget::dropEvent(event); setAutoScroll(false); } QList KItemListView::visibleItemListWidgets() const { return m_visibleItems.values(); } void KItemListView::updateFont() { if (scene() && !scene()->views().isEmpty()) { KItemListStyleOption option = styleOption(); option.font = scene()->views().first()->font(); option.fontMetrics = QFontMetrics(option.font); setStyleOption(option); } } void KItemListView::updatePalette() { if (scene() && !scene()->views().isEmpty()) { KItemListStyleOption option = styleOption(); option.palette = scene()->views().first()->palette(); setStyleOption(option); } } void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) { if (m_itemSize.isEmpty()) { updatePreferredColumnWidths(itemRanges); } const bool hasMultipleRanges = (itemRanges.count() > 1); if (hasMultipleRanges) { beginTransaction(); } m_layouter->markAsDirty(); m_sizeHintResolver->itemsInserted(itemRanges); int previouslyInsertedCount = 0; foreach (const KItemRange& range, itemRanges) { // 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 previously inserted items. const int index = range.index + previouslyInsertedCount; const int count = range.count; if (index < 0 || count <= 0) { qCWarning(DolphinDebug) << "Invalid item range (index:" << index << ", count:" << count << ")"; continue; } previouslyInsertedCount += count; // Determine which visible items must be moved QList itemsToMove; QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); const int visibleItemIndex = it.key(); if (visibleItemIndex >= index) { itemsToMove.append(visibleItemIndex); } } // Update the indexes of all KItemListWidget instances that are located // after the inserted items. It is important to adjust the indexes in the order // from the highest index to the lowest index to prevent overlaps when setting the new index. qSort(itemsToMove); for (int i = itemsToMove.count() - 1; i >= 0; --i) { KItemListWidget* widget = m_visibleItems.value(itemsToMove[i]); Q_ASSERT(widget); const int newIndex = widget->index() + count; if (hasMultipleRanges) { setWidgetIndex(widget, newIndex); } else { // Try to animate the moving of the item moveWidgetToIndex(widget, newIndex); } } if (m_model->count() == count && m_activeTransactions == 0) { // Check whether a scrollbar is required to show the inserted items. In this case // the size of the layouter will be decreased before calling doLayout(): This prevents // an unnecessary temporary animation due to the geometry change of the inserted scrollbar. const bool verticalScrollOrientation = (scrollOrientation() == Qt::Vertical); const bool decreaseLayouterSize = ( verticalScrollOrientation && maximumScrollOffset() > size().height()) || (!verticalScrollOrientation && maximumScrollOffset() > size().width()); if (decreaseLayouterSize) { const int scrollBarExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent); int scrollbarSpacing = 0; if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) { scrollbarSpacing = style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing); } QSizeF layouterSize = m_layouter->size(); if (verticalScrollOrientation) { layouterSize.rwidth() -= scrollBarExtent + scrollbarSpacing; } else { layouterSize.rheight() -= scrollBarExtent + scrollbarSpacing; } m_layouter->setSize(layouterSize); } } if (!hasMultipleRanges) { doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, count); updateSiblingsInformation(); } } if (m_controller) { m_controller->selectionManager()->itemsInserted(itemRanges); } if (hasMultipleRanges) { m_endTransactionAnimationHint = NoAnimation; endTransaction(); updateSiblingsInformation(); } if (m_grouped && (hasMultipleRanges || itemRanges.first().count < m_model->count())) { // In case if items of the same group have been inserted before an item that // currently represents the first item of the group, the group header of // this item must be removed. updateVisibleGroupHeaders(); } if (useAlternateBackgrounds()) { updateAlternateBackgrounds(); } } void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) { if (m_itemSize.isEmpty()) { // Don't pass the item-range: The preferred column-widths of // all items must be adjusted when removing items. updatePreferredColumnWidths(); } const bool hasMultipleRanges = (itemRanges.count() > 1); if (hasMultipleRanges) { beginTransaction(); } m_layouter->markAsDirty(); m_sizeHintResolver->itemsRemoved(itemRanges); for (int i = itemRanges.count() - 1; i >= 0; --i) { const KItemRange& range = itemRanges[i]; const int index = range.index; const int count = range.count; if (index < 0 || count <= 0) { qCWarning(DolphinDebug) << "Invalid item range (index:" << index << ", count:" << count << ")"; continue; } const int firstRemovedIndex = index; const int lastRemovedIndex = index + count - 1; // Remember which items have to be moved because they are behind the removed range. QVector itemsToMove; // Remove all KItemListWidget instances that got deleted foreach (KItemListWidget* widget, m_visibleItems) { const int i = widget->index(); if (i < firstRemovedIndex) { continue; } else if (i > lastRemovedIndex) { itemsToMove.append(i); continue; } m_animation->stop(widget); // Stopping the animation might lead to recycling the widget if // it is invisible (see slotAnimationFinished()). // Check again whether it is still visible: if (!m_visibleItems.contains(i)) { continue; } if (m_model->count() == 0 || hasMultipleRanges || !animateChangedItemCount(count)) { // Remove the widget without animation recycleWidget(widget); } else { // Animate the removing of the items. Special case: When removing an item there // is no valid model index available anymore. For the // remove-animation the item gets removed from m_visibleItems but the widget // will stay alive until the animation has been finished and will // be recycled (deleted) in KItemListView::slotAnimationFinished(). m_visibleItems.remove(i); widget->setIndex(-1); m_animation->start(widget, KItemListViewAnimation::DeleteAnimation); } } // Update the indexes of all KItemListWidget instances that are located // after the deleted items. It is important to update them in ascending // order to prevent overlaps when setting the new index. std::sort(itemsToMove.begin(), itemsToMove.end()); foreach (int i, itemsToMove) { KItemListWidget* widget = m_visibleItems.value(i); Q_ASSERT(widget); const int newIndex = i - count; if (hasMultipleRanges) { setWidgetIndex(widget, newIndex); } else { // Try to animate the moving of the item moveWidgetToIndex(widget, newIndex); } } if (!hasMultipleRanges) { // The decrease-layout-size optimization in KItemListView::slotItemsInserted() // assumes an updated geometry. If items are removed during an active transaction, // the transaction will be temporary deactivated so that doLayout() triggers a // geometry update if necessary. const int activeTransactions = m_activeTransactions; m_activeTransactions = 0; doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, -count); m_activeTransactions = activeTransactions; updateSiblingsInformation(); } } if (m_controller) { m_controller->selectionManager()->itemsRemoved(itemRanges); } if (hasMultipleRanges) { m_endTransactionAnimationHint = NoAnimation; endTransaction(); updateSiblingsInformation(); } if (m_grouped && (hasMultipleRanges || m_model->count() > 0)) { // In case if the first item of a group has been removed, the group header // must be applied to the next visible item. updateVisibleGroupHeaders(); } if (useAlternateBackgrounds()) { updateAlternateBackgrounds(); } } void KItemListView::slotItemsMoved(const KItemRange& itemRange, const QList& movedToIndexes) { m_sizeHintResolver->itemsMoved(itemRange, movedToIndexes); m_layouter->markAsDirty(); if (m_controller) { m_controller->selectionManager()->itemsMoved(itemRange, movedToIndexes); } const int firstVisibleMovedIndex = qMax(firstVisibleIndex(), itemRange.index); const int lastVisibleMovedIndex = qMin(lastVisibleIndex(), itemRange.index + itemRange.count - 1); for (int index = firstVisibleMovedIndex; index <= lastVisibleMovedIndex; ++index) { KItemListWidget* widget = m_visibleItems.value(index); if (widget) { updateWidgetProperties(widget, index); initializeItemListWidget(widget); } } doLayout(NoAnimation); updateSiblingsInformation(); } void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, const QSet& roles) { const bool updateSizeHints = itemSizeHintUpdateRequired(roles); if (updateSizeHints && m_itemSize.isEmpty()) { updatePreferredColumnWidths(itemRanges); } foreach (const KItemRange& itemRange, itemRanges) { const int index = itemRange.index; const int count = itemRange.count; if (updateSizeHints) { m_sizeHintResolver->itemsChanged(index, count, roles); m_layouter->markAsDirty(); if (!m_layoutTimer->isActive()) { m_layoutTimer->start(); } } // Apply the changed roles to the visible item-widgets const int lastIndex = index + count - 1; for (int i = index; i <= lastIndex; ++i) { KItemListWidget* widget = m_visibleItems.value(i); if (widget) { widget->setData(m_model->data(i), roles); } } if (m_grouped && roles.contains(m_model->sortRole())) { // The sort-role has been changed which might result // in modified group headers updateVisibleGroupHeaders(); doLayout(NoAnimation); } QAccessibleTableModelChangeEvent ev(this, QAccessibleTableModelChangeEvent::DataChanged); ev.setFirstRow(itemRange.index); ev.setLastRow(itemRange.index + itemRange.count); QAccessible::updateAccessibility(&ev); } } void KItemListView::slotGroupsChanged() { updateVisibleGroupHeaders(); doLayout(NoAnimation); updateSiblingsInformation(); } void KItemListView::slotGroupedSortingChanged(bool current) { m_grouped = current; m_layouter->markAsDirty(); if (m_grouped) { updateGroupHeaderHeight(); } else { // Clear all visible headers. Note that the QHashIterator takes a copy of // m_visibleGroups. Therefore, it remains valid even if items are removed // from m_visibleGroups in recycleGroupHeaderForWidget(). QHashIterator it(m_visibleGroups); while (it.hasNext()) { it.next(); recycleGroupHeaderForWidget(it.key()); } Q_ASSERT(m_visibleGroups.isEmpty()); } if (useAlternateBackgrounds()) { // Changing the group mode requires to update the alternate backgrounds // as with the enabled group mode the altering is done on base of the first // group item. updateAlternateBackgrounds(); } updateSiblingsInformation(); doLayout(NoAnimation); } void KItemListView::slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) { Q_UNUSED(current); Q_UNUSED(previous); if (m_grouped) { updateVisibleGroupHeaders(); doLayout(NoAnimation); } } void KItemListView::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous) { Q_UNUSED(current); Q_UNUSED(previous); if (m_grouped) { updateVisibleGroupHeaders(); doLayout(NoAnimation); } } void KItemListView::slotCurrentChanged(int current, int previous) { Q_UNUSED(previous); // In SingleSelection mode (e.g., in the Places Panel), the current item is // always the selected item. It is not necessary to highlight the current item then. if (m_controller->selectionBehavior() != KItemListController::SingleSelection) { KItemListWidget* previousWidget = m_visibleItems.value(previous, 0); if (previousWidget) { previousWidget->setCurrent(false); } KItemListWidget* currentWidget = m_visibleItems.value(current, 0); if (currentWidget) { currentWidget->setCurrent(true); } } QAccessibleEvent ev(this, QAccessible::Focus); ev.setChild(current); QAccessible::updateAccessibility(&ev); } void KItemListView::slotSelectionChanged(const KItemSet& current, const KItemSet& previous) { Q_UNUSED(previous); QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); const int index = it.key(); KItemListWidget* widget = it.value(); widget->setSelected(current.contains(index)); } } void KItemListView::slotAnimationFinished(QGraphicsWidget* widget, KItemListViewAnimation::AnimationType type) { KItemListWidget* itemListWidget = qobject_cast(widget); Q_ASSERT(itemListWidget); switch (type) { case KItemListViewAnimation::DeleteAnimation: { // As we recycle the widget in this case it is important to assure that no // other animation has been started. This is a convention in KItemListView and // not a requirement defined by KItemListViewAnimation. Q_ASSERT(!m_animation->isStarted(itemListWidget)); // All KItemListWidgets that are animated by the DeleteAnimation are not maintained // by m_visibleWidgets and must be deleted manually after the animation has // been finished. recycleGroupHeaderForWidget(itemListWidget); widgetCreator()->recycle(itemListWidget); break; } case KItemListViewAnimation::CreateAnimation: case KItemListViewAnimation::MovingAnimation: case KItemListViewAnimation::ResizeAnimation: { const int index = itemListWidget->index(); const bool invisible = (index < m_layouter->firstVisibleIndex()) || (index > m_layouter->lastVisibleIndex()); if (invisible && !m_animation->isStarted(itemListWidget)) { recycleWidget(itemListWidget); } break; } default: break; } } void KItemListView::slotLayoutTimerFinished() { m_layouter->setSize(geometry().size()); doLayout(Animation); } void KItemListView::slotRubberBandPosChanged() { update(); } void KItemListView::slotRubberBandActivationChanged(bool active) { if (active) { connect(m_rubberBand, &KItemListRubberBand::startPositionChanged, this, &KItemListView::slotRubberBandPosChanged); connect(m_rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListView::slotRubberBandPosChanged); m_skipAutoScrollForRubberBand = true; } else { disconnect(m_rubberBand, &KItemListRubberBand::startPositionChanged, this, &KItemListView::slotRubberBandPosChanged); disconnect(m_rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListView::slotRubberBandPosChanged); m_skipAutoScrollForRubberBand = false; } update(); } void KItemListView::slotHeaderColumnWidthChanged(const QByteArray& role, qreal currentWidth, qreal previousWidth) { Q_UNUSED(role); Q_UNUSED(currentWidth); Q_UNUSED(previousWidth); m_headerWidget->setAutomaticColumnResizing(false); applyColumnWidthsFromHeader(); doLayout(NoAnimation); } void KItemListView::slotHeaderColumnMoved(const QByteArray& role, int currentIndex, int previousIndex) { Q_ASSERT(m_visibleRoles[previousIndex] == role); const QList previous = m_visibleRoles; QList current = m_visibleRoles; current.removeAt(previousIndex); current.insert(currentIndex, role); setVisibleRoles(current); emit visibleRolesChanged(current, previous); } void KItemListView::triggerAutoScrolling() { if (!m_autoScrollTimer) { return; } int pos = 0; int visibleSize = 0; if (scrollOrientation() == Qt::Vertical) { pos = m_mousePos.y(); visibleSize = size().height(); } else { pos = m_mousePos.x(); visibleSize = size().width(); } if (m_autoScrollTimer->interval() == InitialAutoScrollDelay) { m_autoScrollIncrement = 0; } m_autoScrollIncrement = calculateAutoScrollingIncrement(pos, visibleSize, m_autoScrollIncrement); if (m_autoScrollIncrement == 0) { // The mouse position is not above an autoscroll margin (the autoscroll timer // will be restarted in mouseMoveEvent()) m_autoScrollTimer->stop(); return; } if (m_rubberBand->isActive() && m_skipAutoScrollForRubberBand) { // If a rubberband selection is ongoing the autoscrolling may only get triggered // if the direction of the rubberband is similar to the autoscroll direction. This // prevents that starting to create a rubberband within the autoscroll margins starts // an autoscrolling. const qreal minDiff = 4; // Ignore any autoscrolling if the rubberband is very small const qreal diff = (scrollOrientation() == Qt::Vertical) ? m_rubberBand->endPosition().y() - m_rubberBand->startPosition().y() : m_rubberBand->endPosition().x() - m_rubberBand->startPosition().x(); if (qAbs(diff) < minDiff || (m_autoScrollIncrement < 0 && diff > 0) || (m_autoScrollIncrement > 0 && diff < 0)) { // The rubberband direction is different from the scroll direction (e.g. the rubberband has // been moved up although the autoscroll direction might be down) m_autoScrollTimer->stop(); return; } } // As soon as the autoscrolling has been triggered at least once despite having an active rubberband, // the autoscrolling may not get skipped anymore until a new rubberband is created m_skipAutoScrollForRubberBand = false; const qreal maxVisibleOffset = qMax(qreal(0), maximumScrollOffset() - visibleSize); const qreal newScrollOffset = qMin(scrollOffset() + m_autoScrollIncrement, maxVisibleOffset); setScrollOffset(newScrollOffset); // Trigger the autoscroll timer which will periodically call // triggerAutoScrolling() m_autoScrollTimer->start(RepeatingAutoScrollDelay); } void KItemListView::slotGeometryOfGroupHeaderParentChanged() { KItemListWidget* widget = qobject_cast(sender()); Q_ASSERT(widget); KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget); Q_ASSERT(groupHeader); updateGroupHeaderLayout(widget); } void KItemListView::slotRoleEditingCanceled(int index, const QByteArray& role, const QVariant& value) { disconnectRoleEditingSignals(index); emit roleEditingCanceled(index, role, value); m_editingRole = false; } void KItemListView::slotRoleEditingFinished(int index, const QByteArray& role, const QVariant& value) { disconnectRoleEditingSignals(index); emit roleEditingFinished(index, role, value); m_editingRole = false; } void KItemListView::setController(KItemListController* controller) { if (m_controller != controller) { KItemListController* previous = m_controller; if (previous) { KItemListSelectionManager* selectionManager = previous->selectionManager(); disconnect(selectionManager, &KItemListSelectionManager::currentChanged, this, &KItemListView::slotCurrentChanged); disconnect(selectionManager, &KItemListSelectionManager::selectionChanged, this, &KItemListView::slotSelectionChanged); } m_controller = controller; if (controller) { KItemListSelectionManager* selectionManager = controller->selectionManager(); connect(selectionManager, &KItemListSelectionManager::currentChanged, this, &KItemListView::slotCurrentChanged); connect(selectionManager, &KItemListSelectionManager::selectionChanged, this, &KItemListView::slotSelectionChanged); } onControllerChanged(controller, previous); } } void KItemListView::setModel(KItemModelBase* model) { if (m_model == model) { return; } KItemModelBase* previous = m_model; if (m_model) { disconnect(m_model, &KItemModelBase::itemsChanged, this, &KItemListView::slotItemsChanged); disconnect(m_model, &KItemModelBase::itemsInserted, this, &KItemListView::slotItemsInserted); disconnect(m_model, &KItemModelBase::itemsRemoved, this, &KItemListView::slotItemsRemoved); disconnect(m_model, &KItemModelBase::itemsMoved, this, &KItemListView::slotItemsMoved); disconnect(m_model, &KItemModelBase::groupsChanged, this, &KItemListView::slotGroupsChanged); disconnect(m_model, &KItemModelBase::groupedSortingChanged, this, &KItemListView::slotGroupedSortingChanged); disconnect(m_model, &KItemModelBase::sortOrderChanged, this, &KItemListView::slotSortOrderChanged); disconnect(m_model, &KItemModelBase::sortRoleChanged, this, &KItemListView::slotSortRoleChanged); m_sizeHintResolver->itemsRemoved(KItemRangeList() << KItemRange(0, m_model->count())); } m_model = model; m_layouter->setModel(model); m_grouped = model->groupedSorting(); if (m_model) { connect(m_model, &KItemModelBase::itemsChanged, this, &KItemListView::slotItemsChanged); connect(m_model, &KItemModelBase::itemsInserted, this, &KItemListView::slotItemsInserted); connect(m_model, &KItemModelBase::itemsRemoved, this, &KItemListView::slotItemsRemoved); connect(m_model, &KItemModelBase::itemsMoved, this, &KItemListView::slotItemsMoved); connect(m_model, &KItemModelBase::groupsChanged, this, &KItemListView::slotGroupsChanged); connect(m_model, &KItemModelBase::groupedSortingChanged, this, &KItemListView::slotGroupedSortingChanged); connect(m_model, &KItemModelBase::sortOrderChanged, this, &KItemListView::slotSortOrderChanged); connect(m_model, &KItemModelBase::sortRoleChanged, this, &KItemListView::slotSortRoleChanged); const int itemCount = m_model->count(); if (itemCount > 0) { slotItemsInserted(KItemRangeList() << KItemRange(0, itemCount)); } } onModelChanged(model, previous); } KItemListRubberBand* KItemListView::rubberBand() const { return m_rubberBand; } void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int changedCount) { if (m_layoutTimer->isActive()) { m_layoutTimer->stop(); } if (m_activeTransactions > 0) { if (hint == NoAnimation) { // As soon as at least one property change should be done without animation, // the whole transaction will be marked as not animated. m_endTransactionAnimationHint = NoAnimation; } return; } if (!m_model || m_model->count() < 0) { return; } int firstVisibleIndex = m_layouter->firstVisibleIndex(); if (firstVisibleIndex < 0) { emitOffsetChanges(); return; } // Do a sanity check of the scroll-offset property: When properties of the itemlist-view have been changed // it might be possible that the maximum offset got changed too. Assure that the full visible range // is still shown if the maximum offset got decreased. const qreal visibleOffsetRange = (scrollOrientation() == Qt::Horizontal) ? size().width() : size().height(); const qreal maxOffsetToShowFullRange = maximumScrollOffset() - visibleOffsetRange; if (scrollOffset() > maxOffsetToShowFullRange) { m_layouter->setScrollOffset(qMax(qreal(0), maxOffsetToShowFullRange)); firstVisibleIndex = m_layouter->firstVisibleIndex(); } const int lastVisibleIndex = m_layouter->lastVisibleIndex(); int firstSibblingIndex = -1; int lastSibblingIndex = -1; const bool supportsExpanding = supportsItemExpanding(); QList reusableItems = recycleInvisibleItems(firstVisibleIndex, lastVisibleIndex, hint); // Assure that for each visible item a KItemListWidget is available. KItemListWidget // instances from invisible items are reused. If no reusable items are // found then new KItemListWidget instances get created. const bool animate = (hint == Animation); for (int i = firstVisibleIndex; i <= lastVisibleIndex; ++i) { bool applyNewPos = true; bool wasHidden = false; const QRectF itemBounds = m_layouter->itemRect(i); const QPointF newPos = itemBounds.topLeft(); KItemListWidget* widget = m_visibleItems.value(i); if (!widget) { wasHidden = true; if (!reusableItems.isEmpty()) { // Reuse a KItemListWidget instance from an invisible item const int oldIndex = reusableItems.takeLast(); widget = m_visibleItems.value(oldIndex); setWidgetIndex(widget, i); updateWidgetProperties(widget, i); initializeItemListWidget(widget); } else { // No reusable KItemListWidget instance is available, create a new one widget = createWidget(i); } widget->resize(itemBounds.size()); if (animate && changedCount < 0) { // Items have been deleted. if (i >= changedIndex) { // The item is located behind the removed range. Move the // created item to the imaginary old position outside the // view. It will get animated to the new position later. const int previousIndex = i - changedCount; const QRectF itemRect = m_layouter->itemRect(previousIndex); if (itemRect.isEmpty()) { const QPointF invisibleOldPos = (scrollOrientation() == Qt::Vertical) ? QPointF(0, size().height()) : QPointF(size().width(), 0); widget->setPos(invisibleOldPos); } else { widget->setPos(itemRect.topLeft()); } applyNewPos = false; } } if (supportsExpanding && changedCount == 0) { if (firstSibblingIndex < 0) { firstSibblingIndex = i; } lastSibblingIndex = i; } } if (animate) { if (m_animation->isStarted(widget, KItemListViewAnimation::MovingAnimation)) { m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos); applyNewPos = false; } const bool itemsRemoved = (changedCount < 0); const bool itemsInserted = (changedCount > 0); if (itemsRemoved && (i >= changedIndex)) { // The item is located after the removed items. Animate the moving of the position. applyNewPos = !moveWidget(widget, newPos); } else if (itemsInserted && i >= changedIndex) { // The item is located after the first inserted item if (i <= changedIndex + changedCount - 1) { // The item is an inserted item. Animate the appearing of the item. // For performance reasons no animation is done when changedCount is equal // to all available items. if (changedCount < m_model->count()) { m_animation->start(widget, KItemListViewAnimation::CreateAnimation); } } else if (!m_animation->isStarted(widget, KItemListViewAnimation::CreateAnimation)) { // The item was already there before, so animate the moving of the position. // No moving animation is done if the item is animated by a create animation: This // prevents a "move animation mess" when inserting several ranges in parallel. applyNewPos = !moveWidget(widget, newPos); } } else if (!itemsRemoved && !itemsInserted && !wasHidden) { // The size of the view might have been changed. Animate the moving of the position. applyNewPos = !moveWidget(widget, newPos); } } else { m_animation->stop(widget); } if (applyNewPos) { widget->setPos(newPos); } Q_ASSERT(widget->index() == i); widget->setVisible(true); if (widget->size() != itemBounds.size()) { // Resize the widget for the item to the changed size. if (animate) { // If a dynamic item size is used then no animation is done in the direction // of the dynamic size. if (m_itemSize.width() <= 0) { // The width is dynamic, apply the new width without animation. widget->resize(itemBounds.width(), widget->size().height()); } else if (m_itemSize.height() <= 0) { // The height is dynamic, apply the new height without animation. widget->resize(widget->size().width(), itemBounds.height()); } m_animation->start(widget, KItemListViewAnimation::ResizeAnimation, itemBounds.size()); } else { widget->resize(itemBounds.size()); } } // Updating the cell-information must be done as last step: The decision whether the // moving-animation should be started at all is based on the previous cell-information. const Cell cell(m_layouter->itemColumn(i), m_layouter->itemRow(i)); m_visibleCells.insert(i, cell); } // Delete invisible KItemListWidget instances that have not been reused foreach (int index, reusableItems) { recycleWidget(m_visibleItems.value(index)); } if (supportsExpanding && firstSibblingIndex >= 0) { Q_ASSERT(lastSibblingIndex >= 0); updateSiblingsInformation(firstSibblingIndex, lastSibblingIndex); } if (m_grouped) { // Update the layout of all visible group headers QHashIterator it(m_visibleGroups); while (it.hasNext()) { it.next(); updateGroupHeaderLayout(it.key()); } } emitOffsetChanges(); } QList KItemListView::recycleInvisibleItems(int firstVisibleIndex, int lastVisibleIndex, LayoutAnimationHint hint) { // Determine all items that are completely invisible and might be // reused for items that just got (at least partly) visible. If the // animation hint is set to 'Animation' items that do e.g. an animated // moving of their position are not marked as invisible: This assures // that a scrolling inside the view can be done without breaking an animation. QList items; QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); KItemListWidget* widget = it.value(); const int index = widget->index(); const bool invisible = (index < firstVisibleIndex) || (index > lastVisibleIndex); if (invisible) { if (m_animation->isStarted(widget)) { if (hint == NoAnimation) { // Stopping the animation will call KItemListView::slotAnimationFinished() // and the widget will be recycled if necessary there. m_animation->stop(widget); } } else { widget->setVisible(false); items.append(index); if (m_grouped) { recycleGroupHeaderForWidget(widget); } } } } return items; } bool KItemListView::moveWidget(KItemListWidget* widget,const QPointF& newPos) { if (widget->pos() == newPos) { return false; } bool startMovingAnim = false; if (m_itemSize.isEmpty()) { // The items are not aligned in a grid but either as columns or rows. startMovingAnim = true; } else { // When having a grid the moving-animation should only be started, if it is done within // one row in the vertical scroll-orientation or one column in the horizontal scroll-orientation. // Otherwise instead of a moving-animation a create-animation on the new position will be used // instead. This is done to prevent overlapping (and confusing) moving-animations. const int index = widget->index(); const Cell cell = m_visibleCells.value(index); if (cell.column >= 0 && cell.row >= 0) { if (scrollOrientation() == Qt::Vertical) { startMovingAnim = (cell.row == m_layouter->itemRow(index)); } else { startMovingAnim = (cell.column == m_layouter->itemColumn(index)); } } } if (startMovingAnim) { m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos); return true; } m_animation->stop(widget); m_animation->start(widget, KItemListViewAnimation::CreateAnimation); return false; } void KItemListView::emitOffsetChanges() { const qreal newScrollOffset = m_layouter->scrollOffset(); if (m_oldScrollOffset != newScrollOffset) { emit scrollOffsetChanged(newScrollOffset, m_oldScrollOffset); m_oldScrollOffset = newScrollOffset; } const qreal newMaximumScrollOffset = m_layouter->maximumScrollOffset(); if (m_oldMaximumScrollOffset != newMaximumScrollOffset) { emit maximumScrollOffsetChanged(newMaximumScrollOffset, m_oldMaximumScrollOffset); m_oldMaximumScrollOffset = newMaximumScrollOffset; } const qreal newItemOffset = m_layouter->itemOffset(); if (m_oldItemOffset != newItemOffset) { emit itemOffsetChanged(newItemOffset, m_oldItemOffset); m_oldItemOffset = newItemOffset; } const qreal newMaximumItemOffset = m_layouter->maximumItemOffset(); if (m_oldMaximumItemOffset != newMaximumItemOffset) { emit maximumItemOffsetChanged(newMaximumItemOffset, m_oldMaximumItemOffset); m_oldMaximumItemOffset = newMaximumItemOffset; } } KItemListWidget* KItemListView::createWidget(int index) { KItemListWidget* widget = widgetCreator()->create(this); widget->setFlag(QGraphicsItem::ItemStacksBehindParent); m_visibleItems.insert(index, widget); m_visibleCells.insert(index, Cell()); updateWidgetProperties(widget, index); initializeItemListWidget(widget); return widget; } void KItemListView::recycleWidget(KItemListWidget* widget) { if (m_grouped) { recycleGroupHeaderForWidget(widget); } const int index = widget->index(); m_visibleItems.remove(index); m_visibleCells.remove(index); widgetCreator()->recycle(widget); } void KItemListView::setWidgetIndex(KItemListWidget* widget, int index) { const int oldIndex = widget->index(); m_visibleItems.remove(oldIndex); m_visibleCells.remove(oldIndex); m_visibleItems.insert(index, widget); m_visibleCells.insert(index, Cell()); widget->setIndex(index); } void KItemListView::moveWidgetToIndex(KItemListWidget* widget, int index) { const int oldIndex = widget->index(); const Cell oldCell = m_visibleCells.value(oldIndex); setWidgetIndex(widget, index); const Cell newCell(m_layouter->itemColumn(index), m_layouter->itemRow(index)); const bool vertical = (scrollOrientation() == Qt::Vertical); const bool updateCell = (vertical && oldCell.row == newCell.row) || (!vertical && oldCell.column == newCell.column); if (updateCell) { m_visibleCells.insert(index, newCell); } } void KItemListView::setLayouterSize(const QSizeF& size, SizeType sizeType) { switch (sizeType) { case LayouterSize: m_layouter->setSize(size); break; case ItemSize: m_layouter->setItemSize(size); break; default: break; } } void KItemListView::updateWidgetProperties(KItemListWidget* widget, int index) { widget->setVisibleRoles(m_visibleRoles); updateWidgetColumnWidths(widget); widget->setStyleOption(m_styleOption); const KItemListSelectionManager* selectionManager = m_controller->selectionManager(); // In SingleSelection mode (e.g., in the Places Panel), the current item is // always the selected item. It is not necessary to highlight the current item then. if (m_controller->selectionBehavior() != KItemListController::SingleSelection) { widget->setCurrent(index == selectionManager->currentItem()); } widget->setSelected(selectionManager->isSelected(index)); widget->setHovered(false); widget->setEnabledSelectionToggle(enabledSelectionToggles()); widget->setIndex(index); widget->setData(m_model->data(index)); widget->setSiblingsInformation(QBitArray()); updateAlternateBackgroundForWidget(widget); if (m_grouped) { updateGroupHeaderForWidget(widget); } } void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget) { Q_ASSERT(m_grouped); const int index = widget->index(); if (!m_layouter->isFirstGroupItem(index)) { // The widget does not represent the first item of a group // and hence requires no header recycleGroupHeaderForWidget(widget); return; } const QList > groups = model()->groups(); if (groups.isEmpty() || !groupHeaderCreator()) { return; } KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget); if (!groupHeader) { groupHeader = groupHeaderCreator()->create(this); groupHeader->setParentItem(widget); m_visibleGroups.insert(widget, groupHeader); connect(widget, &KItemListWidget::geometryChanged, this, &KItemListView::slotGeometryOfGroupHeaderParentChanged); } Q_ASSERT(groupHeader->parentItem() == widget); const int groupIndex = groupIndexForItem(index); Q_ASSERT(groupIndex >= 0); groupHeader->setData(groups.at(groupIndex).second); groupHeader->setRole(model()->sortRole()); groupHeader->setStyleOption(m_styleOption); groupHeader->setScrollOrientation(scrollOrientation()); groupHeader->setItemIndex(index); groupHeader->show(); } void KItemListView::updateGroupHeaderLayout(KItemListWidget* widget) { KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget); Q_ASSERT(groupHeader); const int index = widget->index(); const QRectF groupHeaderRect = m_layouter->groupHeaderRect(index); const QRectF itemRect = m_layouter->itemRect(index); // The group-header is a child of the itemlist widget. Translate the // group header position to the relative position. if (scrollOrientation() == Qt::Vertical) { // In the vertical scroll orientation the group header should always span // the whole width no matter which temporary position the parent widget // has. In this case the x-position and width will be adjusted manually. const qreal x = -widget->x() - itemOffset(); const qreal width = maximumItemOffset(); groupHeader->setPos(x, -groupHeaderRect.height()); groupHeader->resize(width, groupHeaderRect.size().height()); } else { groupHeader->setPos(groupHeaderRect.x() - itemRect.x(), -widget->y()); groupHeader->resize(groupHeaderRect.size()); } } void KItemListView::recycleGroupHeaderForWidget(KItemListWidget* widget) { KItemListGroupHeader* header = m_visibleGroups.value(widget); if (header) { header->setParentItem(nullptr); groupHeaderCreator()->recycle(header); m_visibleGroups.remove(widget); disconnect(widget, &KItemListWidget::geometryChanged, this, &KItemListView::slotGeometryOfGroupHeaderParentChanged); } } void KItemListView::updateVisibleGroupHeaders() { Q_ASSERT(m_grouped); m_layouter->markAsDirty(); QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); updateGroupHeaderForWidget(it.value()); } } int KItemListView::groupIndexForItem(int index) const { Q_ASSERT(m_grouped); const QList > groups = model()->groups(); if (groups.isEmpty()) { return -1; } int min = 0; int max = groups.count() - 1; int mid = 0; do { mid = (min + max) / 2; if (index > groups[mid].first) { min = mid + 1; } else { max = mid - 1; } } while (groups[mid].first != index && min <= max); if (min > max) { while (groups[mid].first > index && mid > 0) { --mid; } } return mid; } void KItemListView::updateAlternateBackgrounds() { QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); updateAlternateBackgroundForWidget(it.value()); } } void KItemListView::updateAlternateBackgroundForWidget(KItemListWidget* widget) { bool enabled = useAlternateBackgrounds(); if (enabled) { const int index = widget->index(); enabled = (index & 0x1) > 0; if (m_grouped) { const int groupIndex = groupIndexForItem(index); if (groupIndex >= 0) { const QList > groups = model()->groups(); const int indexOfFirstGroupItem = groups[groupIndex].first; const int relativeIndex = index - indexOfFirstGroupItem; enabled = (relativeIndex & 0x1) > 0; } } } widget->setAlternateBackground(enabled); } bool KItemListView::useAlternateBackgrounds() const { return m_itemSize.isEmpty() && m_visibleRoles.count() > 1; } QHash KItemListView::preferredColumnWidths(const KItemRangeList& itemRanges) const { QElapsedTimer timer; timer.start(); QHash widths; // Calculate the minimum width for each column that is required // to show the headline unclipped. const QFontMetricsF fontMetrics(m_headerWidget->font()); const int gripMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderGripMargin); const int headerMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderMargin); foreach (const QByteArray& visibleRole, visibleRoles()) { const QString headerText = m_model->roleDescription(visibleRole); const qreal headerWidth = fontMetrics.width(headerText) + gripMargin + headerMargin * 2; widths.insert(visibleRole, headerWidth); } // Calculate the preferred column withs for each item and ignore values // smaller than the width for showing the headline unclipped. const KItemListWidgetCreatorBase* creator = widgetCreator(); int calculatedItemCount = 0; bool maxTimeExceeded = false; foreach (const KItemRange& itemRange, itemRanges) { const int startIndex = itemRange.index; const int endIndex = startIndex + itemRange.count - 1; for (int i = startIndex; i <= endIndex; ++i) { foreach (const QByteArray& visibleRole, visibleRoles()) { qreal maxWidth = widths.value(visibleRole, 0); const qreal width = creator->preferredRoleColumnWidth(visibleRole, i, this); maxWidth = qMax(width, maxWidth); widths.insert(visibleRole, maxWidth); } if (calculatedItemCount > 100 && timer.elapsed() > 200) { // When having several thousands of items calculating the sizes can get // very expensive. We accept a possibly too small role-size in favour // of having no blocking user interface. maxTimeExceeded = true; break; } ++calculatedItemCount; } if (maxTimeExceeded) { break; } } return widths; } void KItemListView::applyColumnWidthsFromHeader() { // Apply the new size to the layouter const qreal requiredWidth = columnWidthsSum(); const QSizeF dynamicItemSize(qMax(size().width(), requiredWidth), m_itemSize.height()); m_layouter->setItemSize(dynamicItemSize); // Update the role sizes for all visible widgets QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); updateWidgetColumnWidths(it.value()); } } void KItemListView::updateWidgetColumnWidths(KItemListWidget* widget) { foreach (const QByteArray& role, m_visibleRoles) { widget->setColumnWidth(role, m_headerWidget->columnWidth(role)); } } void KItemListView::updatePreferredColumnWidths(const KItemRangeList& itemRanges) { Q_ASSERT(m_itemSize.isEmpty()); const int itemCount = m_model->count(); int rangesItemCount = 0; foreach (const KItemRange& range, itemRanges) { rangesItemCount += range.count; } if (itemCount == rangesItemCount) { const QHash preferredWidths = preferredColumnWidths(itemRanges); foreach (const QByteArray& role, m_visibleRoles) { m_headerWidget->setPreferredColumnWidth(role, preferredWidths.value(role)); } } else { // Only a sub range of the roles need to be determined. // The chances are good that the widths of the sub ranges // already fit into the available widths and hence no // expensive update might be required. bool changed = false; const QHash updatedWidths = preferredColumnWidths(itemRanges); QHashIterator it(updatedWidths); while (it.hasNext()) { it.next(); const QByteArray& role = it.key(); const qreal updatedWidth = it.value(); const qreal currentWidth = m_headerWidget->preferredColumnWidth(role); if (updatedWidth > currentWidth) { m_headerWidget->setPreferredColumnWidth(role, updatedWidth); changed = true; } } if (!changed) { // All the updated sizes are smaller than the current sizes and no change // of the stretched roles-widths is required return; } } if (m_headerWidget->automaticColumnResizing()) { applyAutomaticColumnWidths(); } } void KItemListView::updatePreferredColumnWidths() { if (m_model) { updatePreferredColumnWidths(KItemRangeList() << KItemRange(0, m_model->count())); } } void KItemListView::applyAutomaticColumnWidths() { Q_ASSERT(m_itemSize.isEmpty()); Q_ASSERT(m_headerWidget->automaticColumnResizing()); if (m_visibleRoles.isEmpty()) { return; } // Calculate the maximum size of an item by considering the // visible role sizes and apply them to the layouter. If the // size does not use the available view-size the size of the // first role will get stretched. foreach (const QByteArray& role, m_visibleRoles) { const qreal preferredWidth = m_headerWidget->preferredColumnWidth(role); m_headerWidget->setColumnWidth(role, preferredWidth); } const QByteArray firstRole = m_visibleRoles.first(); qreal firstColumnWidth = m_headerWidget->columnWidth(firstRole); QSizeF dynamicItemSize = m_itemSize; qreal requiredWidth = columnWidthsSum(); const qreal availableWidth = size().width(); if (requiredWidth < availableWidth) { // Stretch the first column to use the whole remaining width firstColumnWidth += availableWidth - requiredWidth; m_headerWidget->setColumnWidth(firstRole, firstColumnWidth); } else if (requiredWidth > availableWidth && m_visibleRoles.count() > 1) { // Shrink the first column to be able to show as much other // columns as possible qreal shrinkedFirstColumnWidth = firstColumnWidth - requiredWidth + availableWidth; // TODO: A proper calculation of the minimum width depends on the implementation // of KItemListWidget. Probably a kind of minimum size-hint should be introduced // later. const qreal minWidth = qMin(firstColumnWidth, qreal(m_styleOption.iconSize * 2 + 200)); if (shrinkedFirstColumnWidth < minWidth) { shrinkedFirstColumnWidth = minWidth; } m_headerWidget->setColumnWidth(firstRole, shrinkedFirstColumnWidth); requiredWidth -= firstColumnWidth - shrinkedFirstColumnWidth; } dynamicItemSize.rwidth() = qMax(requiredWidth, availableWidth); m_layouter->setItemSize(dynamicItemSize); // Update the role sizes for all visible widgets QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); updateWidgetColumnWidths(it.value()); } } qreal KItemListView::columnWidthsSum() const { qreal widthsSum = 0; foreach (const QByteArray& role, m_visibleRoles) { widthsSum += m_headerWidget->columnWidth(role); } return widthsSum; } QRectF KItemListView::headerBoundaries() const { return m_headerWidget->isVisible() ? m_headerWidget->geometry() : QRectF(); } bool KItemListView::changesItemGridLayout(const QSizeF& newGridSize, const QSizeF& newItemSize, const QSizeF& newItemMargin) const { if (newItemSize.isEmpty() || newGridSize.isEmpty()) { return false; } if (m_layouter->scrollOrientation() == Qt::Vertical) { const qreal itemWidth = m_layouter->itemSize().width(); if (itemWidth > 0) { const int newColumnCount = itemsPerSize(newGridSize.width(), newItemSize.width(), newItemMargin.width()); if (m_model->count() > newColumnCount) { const int oldColumnCount = itemsPerSize(m_layouter->size().width(), itemWidth, m_layouter->itemMargin().width()); return oldColumnCount != newColumnCount; } } } else { const qreal itemHeight = m_layouter->itemSize().height(); if (itemHeight > 0) { const int newRowCount = itemsPerSize(newGridSize.height(), newItemSize.height(), newItemMargin.height()); if (m_model->count() > newRowCount) { const int oldRowCount = itemsPerSize(m_layouter->size().height(), itemHeight, m_layouter->itemMargin().height()); return oldRowCount != newRowCount; } } } return false; } bool KItemListView::animateChangedItemCount(int changedItemCount) const { if (m_itemSize.isEmpty()) { // We have only columns or only rows, but no grid: An animation is usually // welcome when inserting or removing items. return !supportsItemExpanding(); } if (m_layouter->size().isEmpty() || m_layouter->itemSize().isEmpty()) { return false; } const int maximum = (scrollOrientation() == Qt::Vertical) ? m_layouter->size().width() / m_layouter->itemSize().width() : m_layouter->size().height() / m_layouter->itemSize().height(); // Only animate if up to 2/3 of a row or column are inserted or removed return changedItemCount <= maximum * 2 / 3; } bool KItemListView::scrollBarRequired(const QSizeF& size) const { const QSizeF oldSize = m_layouter->size(); m_layouter->setSize(size); const qreal maxOffset = m_layouter->maximumScrollOffset(); m_layouter->setSize(oldSize); return m_layouter->scrollOrientation() == Qt::Vertical ? maxOffset > size.height() : maxOffset > size.width(); } int KItemListView::showDropIndicator(const QPointF& pos) { QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); const KItemListWidget* widget = it.value(); const QPointF mappedPos = widget->mapFromItem(this, pos); const QRectF rect = itemRect(widget->index()); if (mappedPos.y() >= 0 && mappedPos.y() <= rect.height()) { if (m_model->supportsDropping(widget->index())) { // Keep 30% of the rectangle as the gap instead of always having a fixed gap const int gap = qMax(qreal(4.0), qreal(0.3) * rect.height()); if (mappedPos.y() >= gap && mappedPos.y() <= rect.height() - gap) { return -1; } } const bool isAboveItem = (mappedPos.y () < rect.height() / 2); const qreal y = isAboveItem ? rect.top() : rect.bottom(); const QRectF draggingInsertIndicator(rect.left(), y, rect.width(), 1); if (m_dropIndicator != draggingInsertIndicator) { m_dropIndicator = draggingInsertIndicator; update(); } int index = widget->index(); if (!isAboveItem) { ++index; } return index; } } const QRectF firstItemRect = itemRect(firstVisibleIndex()); return (pos.y() <= firstItemRect.top()) ? 0 : -1; } void KItemListView::hideDropIndicator() { if (!m_dropIndicator.isNull()) { m_dropIndicator = QRectF(); update(); } } void KItemListView::updateGroupHeaderHeight() { qreal groupHeaderHeight = m_styleOption.fontMetrics.height(); qreal groupHeaderMargin = 0; if (scrollOrientation() == Qt::Horizontal) { // The vertical margin above and below the header should be // equal to the horizontal margin, not the vertical margin // from m_styleOption. groupHeaderHeight += 2 * m_styleOption.horizontalMargin; groupHeaderMargin = m_styleOption.horizontalMargin; } else if (m_itemSize.isEmpty()){ groupHeaderHeight += 4 * m_styleOption.padding; groupHeaderMargin = m_styleOption.iconSize / 2; } else { groupHeaderHeight += 2 * m_styleOption.padding + m_styleOption.verticalMargin; groupHeaderMargin = m_styleOption.iconSize / 4; } m_layouter->setGroupHeaderHeight(groupHeaderHeight); m_layouter->setGroupHeaderMargin(groupHeaderMargin); updateVisibleGroupHeaders(); } void KItemListView::updateSiblingsInformation(int firstIndex, int lastIndex) { if (!supportsItemExpanding() || !m_model) { return; } if (firstIndex < 0 || lastIndex < 0) { firstIndex = m_layouter->firstVisibleIndex(); lastIndex = m_layouter->lastVisibleIndex(); } else { const bool isRangeVisible = (firstIndex <= m_layouter->lastVisibleIndex() && lastIndex >= m_layouter->firstVisibleIndex()); if (!isRangeVisible) { return; } } int previousParents = 0; QBitArray previousSiblings; // The rootIndex describes the first index where the siblings get // calculated from. For the calculation the upper most parent item // is required. For performance reasons it is checked first whether // the visible items before or after the current range already // contain a siblings information which can be used as base. int rootIndex = firstIndex; KItemListWidget* widget = m_visibleItems.value(firstIndex - 1); if (!widget) { // There is no visible widget before the range, check whether there // is one after the range: widget = m_visibleItems.value(lastIndex + 1); if (widget) { // The sibling information of the widget may only be used if // all items of the range have the same number of parents. const int parents = m_model->expandedParentsCount(lastIndex + 1); for (int i = lastIndex; i >= firstIndex; --i) { if (m_model->expandedParentsCount(i) != parents) { widget = nullptr; break; } } } } if (widget) { // Performance optimization: Use the sibling information of the visible // widget beside the given range. previousSiblings = widget->siblingsInformation(); if (previousSiblings.isEmpty()) { return; } previousParents = previousSiblings.count() - 1; previousSiblings.truncate(previousParents); } else { // Potentially slow path: Go back to the upper most parent of firstIndex // to be able to calculate the initial value for the siblings. while (rootIndex > 0 && m_model->expandedParentsCount(rootIndex) > 0) { --rootIndex; } } Q_ASSERT(previousParents >= 0); for (int i = rootIndex; i <= lastIndex; ++i) { // Update the parent-siblings in case if the current item represents // a child or an upper parent. const int currentParents = m_model->expandedParentsCount(i); Q_ASSERT(currentParents >= 0); if (previousParents < currentParents) { previousParents = currentParents; previousSiblings.resize(currentParents); previousSiblings.setBit(currentParents - 1, hasSiblingSuccessor(i - 1)); } else if (previousParents > currentParents) { previousParents = currentParents; previousSiblings.truncate(currentParents); } if (i >= firstIndex) { // The index represents a visible item. Apply the parent-siblings // and update the sibling of the current item. KItemListWidget* widget = m_visibleItems.value(i); if (!widget) { continue; } QBitArray siblings = previousSiblings; siblings.resize(siblings.count() + 1); siblings.setBit(siblings.count() - 1, hasSiblingSuccessor(i)); widget->setSiblingsInformation(siblings); } } } bool KItemListView::hasSiblingSuccessor(int index) const { bool hasSuccessor = false; const int parentsCount = m_model->expandedParentsCount(index); int successorIndex = index + 1; // Search the next sibling const int itemCount = m_model->count(); while (successorIndex < itemCount) { const int currentParentsCount = m_model->expandedParentsCount(successorIndex); if (currentParentsCount == parentsCount) { hasSuccessor = true; break; } else if (currentParentsCount < parentsCount) { break; } ++successorIndex; } if (m_grouped && hasSuccessor) { // If the sibling is part of another group, don't mark it as // successor as the group header is between the sibling connections. for (int i = index + 1; i <= successorIndex; ++i) { if (m_layouter->isFirstGroupItem(i)) { hasSuccessor = false; break; } } } return hasSuccessor; } void KItemListView::disconnectRoleEditingSignals(int index) { KStandardItemListWidget* widget = qobject_cast(m_visibleItems.value(index)); if (!widget) { return; } disconnect(widget, &KItemListWidget::roleEditingCanceled, this, nullptr); disconnect(widget, &KItemListWidget::roleEditingFinished, this, nullptr); disconnect(this, &KItemListView::scrollOffsetChanged, widget, nullptr); } int KItemListView::calculateAutoScrollingIncrement(int pos, int range, int oldInc) { int inc = 0; const int minSpeed = 4; const int maxSpeed = 128; const int speedLimiter = 96; const int autoScrollBorder = 64; // Limit the increment that is allowed to be added in comparison to 'oldInc'. // This assures that the autoscrolling speed grows gradually. const int incLimiter = 1; if (pos < autoScrollBorder) { inc = -minSpeed + qAbs(pos - autoScrollBorder) * (pos - autoScrollBorder) / speedLimiter; inc = qMax(inc, -maxSpeed); inc = qMax(inc, oldInc - incLimiter); } else if (pos > range - autoScrollBorder) { inc = minSpeed + qAbs(pos - range + autoScrollBorder) * (pos - range + autoScrollBorder) / speedLimiter; inc = qMin(inc, maxSpeed); inc = qMin(inc, oldInc + incLimiter); } return inc; } int KItemListView::itemsPerSize(qreal size, qreal itemSize, qreal itemMargin) { const qreal availableSize = size - itemMargin; const int count = availableSize / (itemSize + itemMargin); return count; } KItemListCreatorBase::~KItemListCreatorBase() { qDeleteAll(m_recycleableWidgets); qDeleteAll(m_createdWidgets); } void KItemListCreatorBase::addCreatedWidget(QGraphicsWidget* widget) { m_createdWidgets.insert(widget); } void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget* widget) { Q_ASSERT(m_createdWidgets.contains(widget)); m_createdWidgets.remove(widget); if (m_recycleableWidgets.count() < 100) { m_recycleableWidgets.append(widget); widget->setVisible(false); } else { delete widget; } } QGraphicsWidget* KItemListCreatorBase::popRecycleableWidget() { if (m_recycleableWidgets.isEmpty()) { return nullptr; } QGraphicsWidget* widget = m_recycleableWidgets.takeLast(); m_createdWidgets.insert(widget); return widget; } KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase() { } void KItemListWidgetCreatorBase::recycle(KItemListWidget* widget) { widget->setParentItem(nullptr); widget->setOpacity(1.0); pushRecycleableWidget(widget); } KItemListGroupHeaderCreatorBase::~KItemListGroupHeaderCreatorBase() { } void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeader* header) { header->setOpacity(1.0); pushRecycleableWidget(header); } diff --git a/src/kitemviews/kitemlistviewaccessible.cpp b/src/kitemviews/kitemlistviewaccessible.cpp index c593dad82..ce45ed81d 100644 --- a/src/kitemviews/kitemlistviewaccessible.cpp +++ b/src/kitemviews/kitemlistviewaccessible.cpp @@ -1,476 +1,473 @@ /*************************************************************************** * Copyright (C) 2012 by Amandeep Singh * * * * 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 * ***************************************************************************/ #ifndef QT_NO_ACCESSIBILITY #include "kitemlistviewaccessible.h" #include "kitemlistcontainer.h" #include "kitemlistcontroller.h" #include "kitemlistselectionmanager.h" #include "kitemlistview.h" #include "private/kitemlistviewlayouter.h" -#include #include #include -#include - KItemListView* KItemListViewAccessible::view() const { return qobject_cast(object()); } KItemListViewAccessible::KItemListViewAccessible(KItemListView* view_) : QAccessibleObject(view_) { Q_ASSERT(view()); m_cells.resize(childCount()); } KItemListViewAccessible::~KItemListViewAccessible() { foreach (QAccessibleInterface* child, m_cells) { if (child) { QAccessible::Id childId = QAccessible::uniqueId(child); QAccessible::deleteAccessibleInterface(childId); } } } void* KItemListViewAccessible::interface_cast(QAccessible::InterfaceType type) { if (type == QAccessible::TableInterface) { return static_cast(this); } return nullptr; } void KItemListViewAccessible::modelReset() { } QAccessibleInterface* KItemListViewAccessible::cell(int index) const { if (index < 0 || index >= view()->model()->count()) { return nullptr; } if (m_cells.size() <= index) { m_cells.resize(childCount()); } Q_ASSERT(index < m_cells.size()); QAccessibleInterface* child = m_cells.at(index); if (!child) { child = new KItemListAccessibleCell(view(), index); QAccessible::registerAccessibleInterface(child); } return child; } QAccessibleInterface* KItemListViewAccessible::cellAt(int row, int column) const { return cell(columnCount() * row + column); } QAccessibleInterface* KItemListViewAccessible::caption() const { return nullptr; } QString KItemListViewAccessible::columnDescription(int) const { return QString(); } int KItemListViewAccessible::columnCount() const { return view()->m_layouter->columnCount(); } int KItemListViewAccessible::rowCount() const { if (columnCount() <= 0) { return 0; } int itemCount = view()->model()->count(); int rowCount = itemCount / columnCount(); if (rowCount <= 0) { return 0; } if (itemCount % columnCount()) { ++rowCount; } return rowCount; } int KItemListViewAccessible::selectedCellCount() const { return view()->controller()->selectionManager()->selectedItems().count(); } int KItemListViewAccessible::selectedColumnCount() const { return 0; } int KItemListViewAccessible::selectedRowCount() const { return 0; } QString KItemListViewAccessible::rowDescription(int) const { return QString(); } QList KItemListViewAccessible::selectedCells() const { QList cells; const auto items = view()->controller()->selectionManager()->selectedItems(); cells.reserve(items.count()); for (int index : items) { cells.append(cell(index)); } return cells; } QList KItemListViewAccessible::selectedColumns() const { return QList(); } QList KItemListViewAccessible::selectedRows() const { return QList(); } QAccessibleInterface* KItemListViewAccessible::summary() const { return nullptr; } bool KItemListViewAccessible::isColumnSelected(int) const { return false; } bool KItemListViewAccessible::isRowSelected(int) const { return false; } bool KItemListViewAccessible::selectRow(int) { return true; } bool KItemListViewAccessible::selectColumn(int) { return true; } bool KItemListViewAccessible::unselectRow(int) { return true; } bool KItemListViewAccessible::unselectColumn(int) { return true; } void KItemListViewAccessible::modelChange(QAccessibleTableModelChangeEvent* /*event*/) {} QAccessible::Role KItemListViewAccessible::role() const { return QAccessible::Table; } QAccessible::State KItemListViewAccessible::state() const { QAccessible::State s; return s; } QAccessibleInterface* KItemListViewAccessible::childAt(int x, int y) const { const QPointF point = QPointF(x, y); int itemIndex = view()->itemAt(view()->mapFromScene(point)); return child(itemIndex); } QAccessibleInterface* KItemListViewAccessible::parent() const { // FIXME: return KItemListContainerAccessible here return nullptr; } int KItemListViewAccessible::childCount() const { return view()->model()->count(); } int KItemListViewAccessible::indexOfChild(const QAccessibleInterface* interface) const { const KItemListAccessibleCell* widget = static_cast(interface); return widget->index(); } QString KItemListViewAccessible::text(QAccessible::Text) const { return QString(); } QRect KItemListViewAccessible::rect() const { if (!view()->isVisible()) { return QRect(); } const QGraphicsScene* scene = view()->scene(); if (scene) { const QPoint origin = scene->views().at(0)->mapToGlobal(QPoint(0, 0)); const QRect viewRect = view()->geometry().toRect(); return viewRect.translated(origin); } else { return QRect(); } } QAccessibleInterface* KItemListViewAccessible::child(int index) const { if (index >= 0 && index < childCount()) { return cell(index); } return nullptr; } // Table Cell KItemListAccessibleCell::KItemListAccessibleCell(KItemListView* view, int index) : m_view(view), m_index(index) { Q_ASSERT(index >= 0 && index < view->model()->count()); } void* KItemListAccessibleCell::interface_cast(QAccessible::InterfaceType type) { if (type == QAccessible::TableCellInterface) { return static_cast(this); } return nullptr; } int KItemListAccessibleCell::columnExtent() const { return 1; } int KItemListAccessibleCell::rowExtent() const { return 1; } QList KItemListAccessibleCell::rowHeaderCells() const { return QList(); } QList KItemListAccessibleCell::columnHeaderCells() const { return QList(); } int KItemListAccessibleCell::columnIndex() const { return m_view->m_layouter->itemColumn(m_index); } int KItemListAccessibleCell::rowIndex() const { return m_view->m_layouter->itemRow(m_index); } bool KItemListAccessibleCell::isSelected() const { return m_view->controller()->selectionManager()->isSelected(m_index); } QAccessibleInterface* KItemListAccessibleCell::table() const { return QAccessible::queryAccessibleInterface(m_view); } QAccessible::Role KItemListAccessibleCell::role() const { return QAccessible::Cell; } QAccessible::State KItemListAccessibleCell::state() const { QAccessible::State state; state.selectable = true; if (isSelected()) { state.selected = true; } state.focusable = true; if (m_view->controller()->selectionManager()->currentItem() == m_index) { state.focused = true; } if (m_view->controller()->selectionBehavior() == KItemListController::MultiSelection) { state.multiSelectable = true; } if (m_view->model()->isExpandable(m_index)) { if (m_view->model()->isExpanded(m_index)) { state.expanded = true; } else { state.collapsed = true; } } return state; } bool KItemListAccessibleCell::isExpandable() const { return m_view->model()->isExpandable(m_index); } QRect KItemListAccessibleCell::rect() const { QRect rect = m_view->itemRect(m_index).toRect(); if (rect.isNull()) { return QRect(); } rect.translate(m_view->mapToScene(QPointF(0.0, 0.0)).toPoint()); rect.translate(m_view->scene()->views()[0]->mapToGlobal(QPoint(0, 0))); return rect; } QString KItemListAccessibleCell::text(QAccessible::Text t) const { switch (t) { case QAccessible::Name: { const QHash data = m_view->model()->data(m_index); return data["text"].toString(); } default: break; } return QString(); } void KItemListAccessibleCell::setText(QAccessible::Text, const QString&) { } QAccessibleInterface* KItemListAccessibleCell::child(int) const { return nullptr; } bool KItemListAccessibleCell::isValid() const { return m_view && (m_index >= 0) && (m_index < m_view->model()->count()); } QAccessibleInterface* KItemListAccessibleCell::childAt(int, int) const { return nullptr; } int KItemListAccessibleCell::childCount() const { return 0; } int KItemListAccessibleCell::indexOfChild(const QAccessibleInterface* child) const { Q_UNUSED(child); return -1; } QAccessibleInterface* KItemListAccessibleCell::parent() const { return QAccessible::queryAccessibleInterface(m_view); } int KItemListAccessibleCell::index() const { return m_index; } QObject* KItemListAccessibleCell::object() const { return nullptr; } // Container Interface KItemListContainerAccessible::KItemListContainerAccessible(KItemListContainer* container) : QAccessibleWidget(container) { } KItemListContainerAccessible::~KItemListContainerAccessible() { } int KItemListContainerAccessible::childCount() const { return 1; } int KItemListContainerAccessible::indexOfChild(const QAccessibleInterface* child) const { if (child->object() == container()->controller()->view()) { return 0; } return -1; } QAccessibleInterface* KItemListContainerAccessible::child(int index) const { if (index == 0) { return QAccessible::queryAccessibleInterface(container()->controller()->view()); } return nullptr; } const KItemListContainer* KItemListContainerAccessible::container() const { return qobject_cast(object()); } #endif // QT_NO_ACCESSIBILITY diff --git a/src/kitemviews/kitemlistwidget.cpp b/src/kitemviews/kitemlistwidget.cpp index 579d8b7a2..f92309254 100644 --- a/src/kitemviews/kitemlistwidget.cpp +++ b/src/kitemviews/kitemlistwidget.cpp @@ -1,527 +1,526 @@ /*************************************************************************** * Copyright (C) 2011 by Peter Penz * * * * Based on the Itemviews NG project from Trolltech Labs: * * http://qt.gitorious.org/qt-labs/itemviews-ng * * * * 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 "kitemlistwidget.h" #include "kitemlistview.h" -#include "kitemmodelbase.h" #include "private/kitemlistselectiontoggle.h" #include #include #include #include KItemListWidgetInformant::KItemListWidgetInformant() { } KItemListWidgetInformant::~KItemListWidgetInformant() { } KItemListWidget::KItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent) : QGraphicsWidget(parent, nullptr), m_informant(informant), m_index(-1), m_selected(false), m_current(false), m_hovered(false), m_alternateBackground(false), m_enabledSelectionToggle(false), m_data(), m_visibleRoles(), m_columnWidths(), m_styleOption(), m_siblingsInfo(), m_hoverOpacity(0), m_hoverCache(nullptr), m_hoverAnimation(nullptr), m_selectionToggle(nullptr), m_editedRole() { } KItemListWidget::~KItemListWidget() { clearHoverCache(); } void KItemListWidget::setIndex(int index) { if (m_index != index) { delete m_selectionToggle; m_selectionToggle = nullptr; if (m_hoverAnimation) { m_hoverAnimation->stop(); m_hoverOpacity = 0; } clearHoverCache(); m_index = index; } } int KItemListWidget::index() const { return m_index; } void KItemListWidget::setData(const QHash& data, const QSet& roles) { clearHoverCache(); if (roles.isEmpty()) { m_data = data; dataChanged(m_data); } else { foreach (const QByteArray& role, roles) { m_data[role] = data[role]; } dataChanged(m_data, roles); } update(); } QHash KItemListWidget::data() const { return m_data; } void KItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option); if (m_alternateBackground) { const QColor backgroundColor = m_styleOption.palette.color(QPalette::AlternateBase); const QRectF backgroundRect(0, 0, size().width(), size().height()); painter->fillRect(backgroundRect, backgroundColor); } if (m_selected && m_editedRole.isEmpty()) { const QStyle::State activeState(isActiveWindow() ? QStyle::State_Active : 0); drawItemStyleOption(painter, widget, activeState | QStyle::State_Enabled | QStyle::State_Selected | QStyle::State_Item); } if (m_current && m_editedRole.isEmpty()) { QStyleOptionFocusRect focusRectOption; focusRectOption.initFrom(widget); focusRectOption.rect = textFocusRect().toRect(); focusRectOption.state = QStyle::State_Enabled | QStyle::State_Item | QStyle::State_KeyboardFocusChange; if (m_selected) { focusRectOption.state |= QStyle::State_Selected; } style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusRectOption, painter, widget); } if (m_hoverOpacity > 0.0) { if (!m_hoverCache) { // Initialize the m_hoverCache pixmap to improve the drawing performance // when fading the hover background m_hoverCache = new QPixmap(size().toSize()); m_hoverCache->fill(Qt::transparent); QPainter pixmapPainter(m_hoverCache); const QStyle::State activeState(isActiveWindow() ? QStyle::State_Active : 0); drawItemStyleOption(&pixmapPainter, widget, activeState | QStyle::State_Enabled | QStyle::State_MouseOver | QStyle::State_Item); } const qreal opacity = painter->opacity(); painter->setOpacity(m_hoverOpacity * opacity); painter->drawPixmap(0, 0, *m_hoverCache); painter->setOpacity(opacity); } } void KItemListWidget::setVisibleRoles(const QList& roles) { const QList previousRoles = m_visibleRoles; m_visibleRoles = roles; visibleRolesChanged(roles, previousRoles); update(); } QList KItemListWidget::visibleRoles() const { return m_visibleRoles; } void KItemListWidget::setColumnWidth(const QByteArray& role, qreal width) { const qreal previousWidth = m_columnWidths.value(role); if (previousWidth != width) { m_columnWidths.insert(role, width); columnWidthChanged(role, width, previousWidth); update(); } } qreal KItemListWidget::columnWidth(const QByteArray& role) const { return m_columnWidths.value(role); } void KItemListWidget::setStyleOption(const KItemListStyleOption& option) { const KItemListStyleOption previous = m_styleOption; clearHoverCache(); m_styleOption = option; styleOptionChanged(option, previous); update(); } const KItemListStyleOption& KItemListWidget::styleOption() const { return m_styleOption; } void KItemListWidget::setSelected(bool selected) { if (m_selected != selected) { m_selected = selected; if (m_selectionToggle) { m_selectionToggle->setChecked(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); const int duration = style()->styleHint(QStyle::SH_Widget_Animate) ? 200 : 1; m_hoverAnimation->setDuration(duration); connect(m_hoverAnimation, &QPropertyAnimation::finished, this, &KItemListWidget::slotHoverAnimationFinished); } m_hoverAnimation->stop(); if (hovered) { const qreal startValue = qMax(hoverOpacity(), qreal(0.1)); m_hoverAnimation->setStartValue(startValue); m_hoverAnimation->setEndValue(1.0); if (m_enabledSelectionToggle && !(QApplication::mouseButtons() & Qt::LeftButton)) { initializeSelectionToggle(); } } else { m_hoverAnimation->setStartValue(hoverOpacity()); m_hoverAnimation->setEndValue(0.0); } m_hoverAnimation->start(); hoveredChanged(hovered); update(); } bool KItemListWidget::isHovered() const { return m_hovered; } void KItemListWidget::setHoverPosition(const QPointF& pos) { if (m_selectionToggle) { m_selectionToggle->setHovered(selectionToggleRect().contains(pos)); } } void KItemListWidget::setAlternateBackground(bool enable) { if (m_alternateBackground != enable) { m_alternateBackground = enable; alternateBackgroundChanged(enable); update(); } } bool KItemListWidget::alternateBackground() const { return m_alternateBackground; } void KItemListWidget::setEnabledSelectionToggle(bool enable) { if (m_enabledSelectionToggle != enable) { m_enabledSelectionToggle = enable; update(); } } bool KItemListWidget::enabledSelectionToggle() const { return m_enabledSelectionToggle; } void KItemListWidget::setSiblingsInformation(const QBitArray& siblings) { const QBitArray previous = m_siblingsInfo; m_siblingsInfo = siblings; siblingsInformationChanged(m_siblingsInfo, previous); update(); } QBitArray KItemListWidget::siblingsInformation() const { return m_siblingsInfo; } void KItemListWidget::setEditedRole(const QByteArray& role) { if (m_editedRole != role) { const QByteArray previous = m_editedRole; m_editedRole = role; editedRoleChanged(role, previous); } } QByteArray KItemListWidget::editedRole() const { return m_editedRole; } bool KItemListWidget::contains(const QPointF& point) const { if (!QGraphicsWidget::contains(point)) { return false; } return iconRect().contains(point) || textRect().contains(point) || expansionToggleRect().contains(point) || selectionToggleRect().contains(point); } QRectF KItemListWidget::textFocusRect() const { return textRect(); } QRectF KItemListWidget::selectionToggleRect() const { return QRectF(); } QRectF KItemListWidget::expansionToggleRect() const { return QRectF(); } QPixmap KItemListWidget::createDragPixmap(const QStyleOptionGraphicsItem* option, QWidget* widget) { QPixmap pixmap(size().toSize() * widget->devicePixelRatio()); pixmap.setDevicePixelRatio(widget->devicePixelRatio()); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); const bool oldAlternateBackground = m_alternateBackground; const bool wasSelected = m_selected; const bool wasHovered = m_hovered; setAlternateBackground(false); setSelected(false); setHovered(false); paint(&painter, option, widget); setAlternateBackground(oldAlternateBackground); setSelected(wasSelected); setHovered(wasHovered); return pixmap; } void KItemListWidget::dataChanged(const QHash& current, const QSet& roles) { Q_UNUSED(current); Q_UNUSED(roles); } void KItemListWidget::visibleRolesChanged(const QList& current, const QList& previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListWidget::columnWidthChanged(const QByteArray& role, qreal current, qreal previous) { Q_UNUSED(role); Q_UNUSED(current); Q_UNUSED(previous); } void KItemListWidget::styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous) { Q_UNUSED(current); Q_UNUSED(previous); } 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::alternateBackgroundChanged(bool enabled) { Q_UNUSED(enabled); } void KItemListWidget::siblingsInformationChanged(const QBitArray& current, const QBitArray& previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListWidget::editedRoleChanged(const QByteArray& current, const QByteArray& previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListWidget::resizeEvent(QGraphicsSceneResizeEvent* event) { QGraphicsWidget::resizeEvent(event); clearHoverCache(); if (m_selectionToggle) { const QRectF& toggleRect = selectionToggleRect(); m_selectionToggle->setPos(toggleRect.topLeft()); m_selectionToggle->resize(toggleRect.size()); } } qreal KItemListWidget::hoverOpacity() const { return m_hoverOpacity; } void KItemListWidget::slotHoverAnimationFinished() { if (!m_hovered && m_selectionToggle) { m_selectionToggle->deleteLater(); m_selectionToggle = nullptr; } } void KItemListWidget::initializeSelectionToggle() { Q_ASSERT(m_enabledSelectionToggle); if (!m_selectionToggle) { m_selectionToggle = new KItemListSelectionToggle(this); } const QRectF toggleRect = selectionToggleRect(); m_selectionToggle->setPos(toggleRect.topLeft()); m_selectionToggle->resize(toggleRect.size()); m_selectionToggle->setChecked(isSelected()); } void KItemListWidget::setHoverOpacity(qreal opacity) { m_hoverOpacity = opacity; if (m_selectionToggle) { m_selectionToggle->setOpacity(opacity); } if (m_hoverOpacity <= 0.0) { delete m_hoverCache; m_hoverCache = nullptr; } update(); } void KItemListWidget::clearHoverCache() { delete m_hoverCache; m_hoverCache = nullptr; } void KItemListWidget::drawItemStyleOption(QPainter* painter, QWidget* widget, QStyle::State styleState) { QStyleOptionViewItem viewItemOption; viewItemOption.initFrom(widget); viewItemOption.state = styleState; viewItemOption.viewItemPosition = QStyleOptionViewItem::OnlyOne; viewItemOption.showDecorationSelected = true; viewItemOption.rect = selectionRect().toRect(); widget->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &viewItemOption, painter, widget); } diff --git a/src/kitemviews/kitemset.cpp b/src/kitemviews/kitemset.cpp index f855368c1..e36829eb5 100644 --- a/src/kitemviews/kitemset.cpp +++ b/src/kitemviews/kitemset.cpp @@ -1,348 +1,345 @@ /*************************************************************************** * Copyright (C) 2013 by Frank Reininghaus * * * * 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 "kitemset.h" -#include - -#include KItemSet::iterator KItemSet::insert(int i) { if (m_itemRanges.empty()) { m_itemRanges.push_back(KItemRange(i, 1)); return iterator(m_itemRanges.begin(), 0); } KItemRangeList::iterator rangeBegin = m_itemRanges.begin(); if (i < rangeBegin->index) { // The inserted index is smaller than all existing items. if (i == rangeBegin->index - 1) { // Move the beginning of the first range one item to the front. --rangeBegin->index; ++rangeBegin->count; } else { // Insert a new range at the beginning. rangeBegin = m_itemRanges.insert(rangeBegin, KItemRange(i, 1)); } return iterator(rangeBegin, 0); } KItemRangeList::iterator rangeEnd = m_itemRanges.end(); KItemRangeList::iterator lastRange = rangeEnd - 1; if (i >= lastRange->index) { // i either belongs to the last range, or it is larger than all existing items. const int lastItemPlus1 = lastRange->index + lastRange->count; if (i == lastItemPlus1) { // Move the end of the last range one item to the back. ++lastRange->count; } else if (i > lastItemPlus1) { // Append a new range. lastRange = m_itemRanges.insert(rangeEnd, KItemRange(i, 1)); } return iterator(lastRange, i - lastRange->index); } // We know that i is between the smallest existing item and the first item // of the last range. Find the lowest range whose 'index' is smaller than i. KItemRangeList::iterator low = rangeBegin; KItemRangeList::iterator high = lastRange; while (low + 1 != high) { const int span = high - low; Q_ASSERT(span >= 2); KItemRangeList::iterator mid = low + span / 2; if (mid->index > i) { high = mid; } else { low = mid; } } Q_ASSERT(low->index <= i && high->index > i); if (i == low->index + low->count) { // i is just one item behind the range low. if (i == high->index - 1) { // i closes the gap between low and high. Merge the two ranges. const int newRangeCount = low->count + 1 + high->count; KItemRangeList::iterator behindNewRange = m_itemRanges.erase(high); KItemRangeList::iterator newRange = behindNewRange - 1; newRange->count = newRangeCount; return iterator(newRange, i - newRange->index); } else { // Extend low by one item. ++low->count; return iterator(low, low->count - 1); } } else if (i > low->index + low->count) { if (i == high->index - 1) { // Extend high by one item to the front. --high->index; ++high->count; return iterator(high, 0); } else { // Insert a new range between low and high. KItemRangeList::iterator newRange = m_itemRanges.insert(high, KItemRange(i, 1)); return iterator(newRange, 0); } } else { // The range low already contains i. return iterator(low, i - low->index); } } KItemSet::iterator KItemSet::erase(iterator it) { KItemRangeList::iterator rangeIt = it.m_rangeIt; if (it.m_offset == 0) { // Removed index is at the beginning of a range. if (rangeIt->count > 1) { ++rangeIt->index; --rangeIt->count; } else { // The range only contains the removed index. rangeIt = m_itemRanges.erase(rangeIt); } return iterator(rangeIt, 0); } else if (it.m_offset == rangeIt->count - 1) { // Removed index is at the end of a range. --rangeIt->count; ++rangeIt; return iterator(rangeIt, 0); } else { // The removed index is in the middle of a range. const int newRangeIndex = *it + 1; const int newRangeCount = rangeIt->count - it.m_offset - 1; const KItemRange newRange(newRangeIndex, newRangeCount); rangeIt->count = it.m_offset; ++rangeIt; rangeIt = m_itemRanges.insert(rangeIt, newRange); return iterator(rangeIt, 0); } } KItemSet KItemSet::operator+(const KItemSet& other) const { KItemSet sum; KItemRangeList::const_iterator it1 = m_itemRanges.constBegin(); KItemRangeList::const_iterator it2 = other.m_itemRanges.constBegin(); const KItemRangeList::const_iterator end1 = m_itemRanges.constEnd(); const KItemRangeList::const_iterator end2 = other.m_itemRanges.constEnd(); while (it1 != end1 || it2 != end2) { if (it1 == end1) { // We are past the end of 'this' already. Append all remaining // item ranges from 'other'. while (it2 != end2) { sum.m_itemRanges.append(*it2); ++it2; } } else if (it2 == end2) { // We are past the end of 'other' already. Append all remaining // item ranges from 'this'. while (it1 != end1) { sum.m_itemRanges.append(*it1); ++it1; } } else { // Find the beginning of the next range. int index = qMin(it1->index, it2->index); int count = 0; do { if (it1 != end1 && it1->index <= index + count) { // The next range from 'this' overlaps with the current range in the sum. count = qMax(count, it1->index + it1->count - index); ++it1; } if (it2 != end2 && it2->index <= index + count) { // The next range from 'other' overlaps with the current range in the sum. count = qMax(count, it2->index + it2->count - index); ++it2; } } while ((it1 != end1 && it1->index <= index + count) || (it2 != end2 && it2->index <= index + count)); sum.m_itemRanges.append(KItemRange(index, count)); } } return sum; } KItemSet KItemSet::operator^(const KItemSet& other) const { // We are looking for all ints which are either in *this or in other, // but not in both. KItemSet result; // When we go through all integers from INT_MIN to INT_MAX and start // in the state "do not add to result", every beginning/end of a range // of *this and other toggles the "add/do not add to result" state. // Therefore, we just have to put ints where any range starts/ends to // a sorted array, and then we can calculate the result quite easily. QVector rangeBoundaries; rangeBoundaries.resize(2 * (m_itemRanges.count() + other.m_itemRanges.count())); const QVector::iterator begin = rangeBoundaries.begin(); const QVector::iterator end = rangeBoundaries.end(); QVector::iterator it = begin; foreach (const KItemRange& range, m_itemRanges) { *it++ = range.index; *it++ = range.index + range.count; } const QVector::iterator middle = it; foreach (const KItemRange& range, other.m_itemRanges) { *it++ = range.index; *it++ = range.index + range.count; } Q_ASSERT(it == end); std::inplace_merge(begin, middle, end); it = begin; while (it != end) { const int rangeBegin = *it; ++it; if (*it == rangeBegin) { // It seems that ranges from both *this and other start at // rangeBegin. Do not start a new range, but read the next int. // // Example: Consider the symmetric difference of the sets // {1, 2, 3, 4} and {1, 2}. The sorted list of range boundaries is // 1 1 3 5. Discarding the duplicate 1 yields the result // rangeBegin = 3, rangeEnd = 5, which corresponds to the set {3, 4}. ++it; } else { // The end of the current range is the next *single* int that we // find. If an int appears twice in rangeBoundaries, the range does // not end. // // Example: Consider the symmetric difference of the sets // {1, 2, 3, 4, 8, 9, 10} and {5, 6, 7}. The sorted list of range // boundaries is 1 5 5 8 8 11, and discarding all duplicates yields // the result rangeBegin = 1, rangeEnd = 11, which corresponds to // the set {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}. bool foundEndOfRange = false; int rangeEnd; do { rangeEnd = *it; ++it; if (it == end || *it != rangeEnd) { foundEndOfRange = true; } else { ++it; } } while (!foundEndOfRange); result.m_itemRanges.append(KItemRange(rangeBegin, rangeEnd - rangeBegin)); } } return result; } bool KItemSet::isValid() const { const KItemRangeList::const_iterator begin = m_itemRanges.constBegin(); const KItemRangeList::const_iterator end = m_itemRanges.constEnd(); for (KItemRangeList::const_iterator it = begin; it != end; ++it) { if (it->count <= 0) { return false; } if (it != begin) { const KItemRangeList::const_iterator previous = it - 1; if (previous->index + previous->count >= it->index) { return false; } } } return true; } KItemRangeList::iterator KItemSet::rangeForItem(int i) { const KItemRangeList::iterator end = m_itemRanges.end(); KItemRangeList::iterator low = m_itemRanges.begin(); KItemRangeList::iterator high = end; if (low == end || low->index > i) { return end; } while (low != high && low + 1 != high) { KItemRangeList::iterator mid = low + (high - low) / 2; if (mid->index > i) { high = mid; } else { low = mid; } } Q_ASSERT(low->index <= i); if (low->index + low->count > i) { return low; } return end; } KItemRangeList::const_iterator KItemSet::constRangeForItem(int i) const { const KItemRangeList::const_iterator end = m_itemRanges.constEnd(); KItemRangeList::const_iterator low = m_itemRanges.constBegin(); KItemRangeList::const_iterator high = end; if (low == end || low->index > i) { return end; } while (low != high && low + 1 != high) { KItemRangeList::const_iterator mid = low + (high - low) / 2; if (mid->index > i) { high = mid; } else { low = mid; } } Q_ASSERT(low->index <= i); if (low->index + low->count > i) { return low; } return end; } diff --git a/src/kitemviews/kstandarditem.cpp b/src/kitemviews/kstandarditem.cpp index d4d1fa2f9..4fb3f8f98 100644 --- a/src/kitemviews/kstandarditem.cpp +++ b/src/kitemviews/kstandarditem.cpp @@ -1,171 +1,169 @@ /*************************************************************************** * Copyright (C) 2012 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 "kstandarditem.h" -#include -#include #include "kstandarditemmodel.h" KStandardItem::KStandardItem(KStandardItem* parent) : m_parent(parent), m_children(), m_model(nullptr), m_data() { } KStandardItem::KStandardItem(const QString& text, KStandardItem* parent) : m_parent(parent), m_children(), m_model(nullptr), m_data() { setText(text); } KStandardItem::KStandardItem(const QString& icon, const QString& text, KStandardItem* parent) : m_parent(parent), m_children(), m_model(nullptr), m_data() { setIcon(icon); setText(text); } KStandardItem::KStandardItem(const KStandardItem& item) : m_parent(item.m_parent), m_children(item.m_children), m_model(item.m_model), m_data(item.m_data) { } KStandardItem::~KStandardItem() { } void KStandardItem::setText(const QString& text) { setDataValue("text", text); } QString KStandardItem::text() const { return m_data["text"].toString(); } void KStandardItem::setIcon(const QString& icon) { setDataValue("iconName", icon); } QString KStandardItem::icon() const { return m_data["iconName"].toString(); } void KStandardItem::setIconOverlays(const QStringList& overlays) { setDataValue("iconOverlays", overlays); } QStringList KStandardItem::iconOverlays() const { return m_data["iconOverlays"].toStringList(); } void KStandardItem::setGroup(const QString& group) { setDataValue("group", group); } QString KStandardItem::group() const { return m_data["group"].toString(); } void KStandardItem::setDataValue(const QByteArray& role, const QVariant& value) { const QVariant previous = m_data.value(role); if (previous == value) { return; } m_data.insert(role, value); onDataValueChanged(role, value, previous); if (m_model) { const int index = m_model->index(this); QSet changedRoles; changedRoles.insert(role); m_model->onItemChanged(index, changedRoles); emit m_model->itemsChanged(KItemRangeList() << KItemRange(index, 1), changedRoles); } } QVariant KStandardItem::dataValue(const QByteArray& role) const { return m_data[role]; } void KStandardItem::setParent(KStandardItem* parent) { // TODO: not implemented yet m_parent = parent; } KStandardItem* KStandardItem::parent() const { return m_parent; } void KStandardItem::setData(const QHash& values) { const QHash previous = m_data; m_data = values; onDataChanged(values, previous); } QHash KStandardItem::data() const { return m_data; } QList KStandardItem::children() const { return m_children; } void KStandardItem::onDataValueChanged(const QByteArray& role, const QVariant& current, const QVariant& previous) { Q_UNUSED(role); Q_UNUSED(current); Q_UNUSED(previous); } void KStandardItem::onDataChanged(const QHash& current, const QHash& previous) { Q_UNUSED(current); Q_UNUSED(previous); } diff --git a/src/kitemviews/kstandarditemlistview.cpp b/src/kitemviews/kstandarditemlistview.cpp index 6471002c7..00f111053 100644 --- a/src/kitemviews/kstandarditemlistview.cpp +++ b/src/kitemviews/kstandarditemlistview.cpp @@ -1,180 +1,179 @@ /*************************************************************************** * Copyright (C) 2012 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 "kstandarditemlistview.h" #include #include "kstandarditemlistwidget.h" -#include "kstandarditemlistgroupheader.h" KStandardItemListView::KStandardItemListView(QGraphicsWidget* parent) : KItemListView(parent), m_itemLayout(DetailsLayout) { setAcceptDrops(true); setScrollOrientation(Qt::Vertical); setVisibleRoles({"text"}); } KStandardItemListView::~KStandardItemListView() { } void KStandardItemListView::setItemLayout(ItemLayout layout) { if (m_itemLayout == layout) { return; } beginTransaction(); const ItemLayout previous = m_itemLayout; m_itemLayout = layout; setSupportsItemExpanding(itemLayoutSupportsItemExpanding(layout)); setScrollOrientation(layout == CompactLayout ? Qt::Horizontal : Qt::Vertical); onItemLayoutChanged(layout, previous); endTransaction(); } KStandardItemListView::ItemLayout KStandardItemListView::itemLayout() const { return m_itemLayout; } KItemListWidgetCreatorBase* KStandardItemListView::defaultWidgetCreator() const { return new KItemListWidgetCreator(); } KItemListGroupHeaderCreatorBase* KStandardItemListView::defaultGroupHeaderCreator() const { return new KItemListGroupHeaderCreator(); } void KStandardItemListView::initializeItemListWidget(KItemListWidget* item) { KStandardItemListWidget* standardItemListWidget = qobject_cast(item); Q_ASSERT(standardItemListWidget); switch (itemLayout()) { case IconsLayout: standardItemListWidget->setLayout(KStandardItemListWidget::IconsLayout); break; case CompactLayout: standardItemListWidget->setLayout(KStandardItemListWidget::CompactLayout); break; case DetailsLayout: standardItemListWidget->setLayout(KStandardItemListWidget::DetailsLayout); break; default: Q_ASSERT(false); break; } standardItemListWidget->setSupportsItemExpanding(supportsItemExpanding()); } bool KStandardItemListView::itemSizeHintUpdateRequired(const QSet& changedRoles) const { // The only thing that can modify the item's size hint is the amount of space // needed to display the text for the visible roles. // Even if the icons have a different size they are always aligned within // the area defined by KItemStyleOption.iconSize and hence result in no // change of the item-size. foreach (const QByteArray& role, visibleRoles()) { if (changedRoles.contains(role)) { return true; } } return false; } bool KStandardItemListView::itemLayoutSupportsItemExpanding(ItemLayout layout) const { return layout == DetailsLayout; } void KStandardItemListView::onItemLayoutChanged(ItemLayout current, ItemLayout previous) { Q_UNUSED(current); Q_UNUSED(previous); updateLayoutOfVisibleItems(); } void KStandardItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous) { Q_UNUSED(current); Q_UNUSED(previous); updateLayoutOfVisibleItems(); } void KStandardItemListView::onSupportsItemExpandingChanged(bool supportsExpanding) { Q_UNUSED(supportsExpanding); updateLayoutOfVisibleItems(); } void KStandardItemListView::polishEvent() { switch (m_itemLayout) { case IconsLayout: applyDefaultStyleOption(style()->pixelMetric(QStyle::PM_LargeIconSize), 2, 4, 8); break; case CompactLayout: applyDefaultStyleOption(style()->pixelMetric(QStyle::PM_SmallIconSize), 2, 8, 0); break; case DetailsLayout: applyDefaultStyleOption(style()->pixelMetric(QStyle::PM_SmallIconSize), 2, 0, 0); break; default: Q_ASSERT(false); break; } QGraphicsWidget::polishEvent(); } void KStandardItemListView::applyDefaultStyleOption(int iconSize, int padding, int horizontalMargin, int verticalMargin) { KItemListStyleOption option = styleOption(); bool changed = false; if (option.iconSize < 0) { option.iconSize = iconSize; changed = true; } if (option.padding < 0) { option.padding = padding; changed = true; } if (option.horizontalMargin < 0) { option.horizontalMargin = horizontalMargin; changed = true; } if (option.verticalMargin < 0) { option.verticalMargin = verticalMargin; changed = true; } if (changed) { setStyleOption(option); } } void KStandardItemListView::updateLayoutOfVisibleItems() { if (model()) { foreach (KItemListWidget* widget, visibleItemListWidgets()) { initializeItemListWidget(widget); } } } diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp index aa12f884c..0841ad401 100644 --- a/src/kitemviews/kstandarditemlistwidget.cpp +++ b/src/kitemviews/kstandarditemlistwidget.cpp @@ -1,1525 +1,1521 @@ /*************************************************************************** * Copyright (C) 2012 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 "kstandarditemlistwidget.h" #include "kfileitemlistview.h" #include "kfileitemmodel.h" -#include #include #include #include #include #include "private/kfileitemclipboard.h" #include "private/kitemlistroleeditor.h" #include "private/kpixmapmodifier.h" #include #include #include -#include #include -#include -#include #include #include // #define KSTANDARDITEMLISTWIDGET_DEBUG KStandardItemListWidgetInformant::KStandardItemListWidgetInformant() : KItemListWidgetInformant() { } KStandardItemListWidgetInformant::~KStandardItemListWidgetInformant() { } void KStandardItemListWidgetInformant::calculateItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const { switch (static_cast(view)->itemLayout()) { case KStandardItemListWidget::IconsLayout: calculateIconsLayoutItemSizeHints(logicalHeightHints, logicalWidthHint, view); break; case KStandardItemListWidget::CompactLayout: calculateCompactLayoutItemSizeHints(logicalHeightHints, logicalWidthHint, view); break; case KStandardItemListWidget::DetailsLayout: calculateDetailsLayoutItemSizeHints(logicalHeightHints, logicalWidthHint, view); break; default: Q_ASSERT(false); break; } } qreal KStandardItemListWidgetInformant::preferredRoleColumnWidth(const QByteArray& role, int index, const KItemListView* view) const { const QHash values = view->model()->data(index); const KItemListStyleOption& option = view->styleOption(); const QString text = roleText(role, values); qreal width = KStandardItemListWidget::columnPadding(option); const QFontMetrics& normalFontMetrics = option.fontMetrics; const QFontMetrics linkFontMetrics(customizedFontForLinks(option.font)); if (role == "rating") { width += KStandardItemListWidget::preferredRatingSize(option).width(); } else { // If current item is a link, we use the customized link font metrics instead of the normal font metrics. const QFontMetrics& fontMetrics = itemIsLink(index, view) ? linkFontMetrics : normalFontMetrics; width += fontMetrics.width(text); if (role == "text") { if (view->supportsItemExpanding()) { // Increase the width by the expansion-toggle and the current expansion level const int expandedParentsCount = values.value("expandedParentsCount", 0).toInt(); const qreal height = option.padding * 2 + qMax(option.iconSize, fontMetrics.height()); width += (expandedParentsCount + 1) * height; } // Increase the width by the required space for the icon width += option.padding * 2 + option.iconSize; } } return width; } QString KStandardItemListWidgetInformant::itemText(int index, const KItemListView* view) const { return view->model()->data(index).value("text").toString(); } bool KStandardItemListWidgetInformant::itemIsLink(int index, const KItemListView* view) const { Q_UNUSED(index); Q_UNUSED(view); return false; } QString KStandardItemListWidgetInformant::roleText(const QByteArray& role, const QHash& values) const { if (role == "rating") { // Always use an empty text, as the rating is shown by the image m_rating. return QString(); } return values.value(role).toString(); } QFont KStandardItemListWidgetInformant::customizedFontForLinks(const QFont& baseFont) const { return baseFont; } void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const { const KItemListStyleOption& option = view->styleOption(); const QFont& normalFont = option.font; const int additionalRolesCount = qMax(view->visibleRoles().count() - 1, 0); const qreal itemWidth = view->itemSize().width(); const qreal maxWidth = itemWidth - 2 * option.padding; const qreal additionalRolesSpacing = additionalRolesCount * option.fontMetrics.lineSpacing(); const qreal spacingAndIconHeight = option.iconSize + option.padding * 3; const QFont linkFont = customizedFontForLinks(normalFont); QTextOption textOption(Qt::AlignHCenter); textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); for (int index = 0; index < logicalHeightHints.count(); ++index) { if (logicalHeightHints.at(index) > 0.0) { continue; } // If the current item is a link, we use the customized link font instead of the normal font. const QFont& font = itemIsLink(index, view) ? linkFont : normalFont; const QString& text = KStringHandler::preProcessWrap(itemText(index, view)); // Calculate the number of lines required for wrapping the name qreal textHeight = 0; QTextLayout layout(text, font); layout.setTextOption(textOption); layout.beginLayout(); QTextLine line; int lineCount = 0; while ((line = layout.createLine()).isValid()) { line.setLineWidth(maxWidth); line.naturalTextWidth(); textHeight += line.height(); ++lineCount; if (lineCount == option.maxTextLines) { break; } } layout.endLayout(); // Add one line for each additional information textHeight += additionalRolesSpacing; logicalHeightHints[index] = textHeight + spacingAndIconHeight; } logicalWidthHint = itemWidth; } void KStandardItemListWidgetInformant::calculateCompactLayoutItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const { const KItemListStyleOption& option = view->styleOption(); const QFontMetrics& normalFontMetrics = option.fontMetrics; const int additionalRolesCount = qMax(view->visibleRoles().count() - 1, 0); const QList& visibleRoles = view->visibleRoles(); const bool showOnlyTextRole = (visibleRoles.count() == 1) && (visibleRoles.first() == "text"); const qreal maxWidth = option.maxTextWidth; const qreal paddingAndIconWidth = option.padding * 4 + option.iconSize; const qreal height = option.padding * 2 + qMax(option.iconSize, (1 + additionalRolesCount) * normalFontMetrics.lineSpacing()); const QFontMetrics linkFontMetrics(customizedFontForLinks(option.font)); for (int index = 0; index < logicalHeightHints.count(); ++index) { if (logicalHeightHints.at(index) > 0.0) { continue; } // If the current item is a link, we use the customized link font metrics instead of the normal font metrics. const QFontMetrics& fontMetrics = itemIsLink(index, view) ? linkFontMetrics : normalFontMetrics; // For each row exactly one role is shown. Calculate the maximum required width that is necessary // to show all roles without horizontal clipping. qreal maximumRequiredWidth = 0.0; if (showOnlyTextRole) { maximumRequiredWidth = fontMetrics.width(itemText(index, view)); } else { const QHash& values = view->model()->data(index); foreach (const QByteArray& role, visibleRoles) { const QString& text = roleText(role, values); const qreal requiredWidth = fontMetrics.width(text); maximumRequiredWidth = qMax(maximumRequiredWidth, requiredWidth); } } qreal width = paddingAndIconWidth + maximumRequiredWidth; if (maxWidth > 0 && width > maxWidth) { width = maxWidth; } logicalHeightHints[index] = width; } logicalWidthHint = height; } void KStandardItemListWidgetInformant::calculateDetailsLayoutItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const { const KItemListStyleOption& option = view->styleOption(); const qreal height = option.padding * 2 + qMax(option.iconSize, option.fontMetrics.height()); logicalHeightHints.fill(height); logicalWidthHint = -1.0; } KStandardItemListWidget::KStandardItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent) : KItemListWidget(informant, parent), m_isCut(false), m_isHidden(false), m_customizedFont(), m_customizedFontMetrics(m_customizedFont), m_isExpandable(false), m_supportsItemExpanding(false), m_dirtyLayout(true), m_dirtyContent(true), m_dirtyContentRoles(), m_layout(IconsLayout), m_pixmapPos(), m_pixmap(), m_scaledPixmapSize(), m_iconRect(), m_hoverPixmap(), m_textInfo(), m_textRect(), m_sortedVisibleRoles(), m_expansionArea(), m_customTextColor(), m_additionalInfoTextColor(), m_overlay(), m_rating(), m_roleEditor(nullptr), m_oldRoleEditor(nullptr) { } KStandardItemListWidget::~KStandardItemListWidget() { qDeleteAll(m_textInfo); m_textInfo.clear(); if (m_roleEditor) { m_roleEditor->deleteLater(); } if (m_oldRoleEditor) { m_oldRoleEditor->deleteLater(); } } void KStandardItemListWidget::setLayout(Layout layout) { if (m_layout != layout) { m_layout = layout; m_dirtyLayout = true; updateAdditionalInfoTextColor(); update(); } } KStandardItemListWidget::Layout KStandardItemListWidget::layout() const { return m_layout; } void KStandardItemListWidget::setSupportsItemExpanding(bool supportsItemExpanding) { if (m_supportsItemExpanding != supportsItemExpanding) { m_supportsItemExpanding = supportsItemExpanding; m_dirtyLayout = true; update(); } } bool KStandardItemListWidget::supportsItemExpanding() const { return m_supportsItemExpanding; } void KStandardItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { const_cast(this)->triggerCacheRefreshing(); KItemListWidget::paint(painter, option, widget); if (!m_expansionArea.isEmpty()) { drawSiblingsInformation(painter); } const KItemListStyleOption& itemListStyleOption = styleOption(); if (isHovered()) { if (hoverOpacity() < 1.0) { /* * Linear interpolation between m_pixmap and m_hoverPixmap. * * Note that this cannot be achieved by painting m_hoverPixmap over * m_pixmap, even if the opacities are adjusted. For details see * https://git.reviewboard.kde.org/r/109614/ */ // Paint pixmap1 so that pixmap1 = m_pixmap * (1.0 - hoverOpacity()) QPixmap pixmap1(m_pixmap.size()); pixmap1.setDevicePixelRatio(m_pixmap.devicePixelRatio()); pixmap1.fill(Qt::transparent); { QPainter p(&pixmap1); p.setOpacity(1.0 - hoverOpacity()); p.drawPixmap(0, 0, m_pixmap); } // Paint pixmap2 so that pixmap2 = m_hoverPixmap * hoverOpacity() QPixmap pixmap2(pixmap1.size()); pixmap2.setDevicePixelRatio(pixmap1.devicePixelRatio()); pixmap2.fill(Qt::transparent); { QPainter p(&pixmap2); p.setOpacity(hoverOpacity()); p.drawPixmap(0, 0, m_hoverPixmap); } // Paint pixmap2 on pixmap1 using CompositionMode_Plus // Now pixmap1 = pixmap2 + m_pixmap * (1.0 - hoverOpacity()) // = m_hoverPixmap * hoverOpacity() + m_pixmap * (1.0 - hoverOpacity()) { QPainter p(&pixmap1); p.setCompositionMode(QPainter::CompositionMode_Plus); p.drawPixmap(0, 0, pixmap2); } // Finally paint pixmap1 on the widget drawPixmap(painter, pixmap1); } else { drawPixmap(painter, m_hoverPixmap); } } else { drawPixmap(painter, m_pixmap); } painter->setFont(m_customizedFont); painter->setPen(textColor()); const TextInfo* textInfo = m_textInfo.value("text"); if (!textInfo) { // It seems that we can end up here even if m_textInfo does not contain // the key "text", see bug 306167. According to triggerCacheRefreshing(), // this can only happen if the index is negative. This can happen when // the item is about to be removed, see KItemListView::slotItemsRemoved(). // TODO: try to reproduce the crash and find a better fix. return; } painter->drawStaticText(textInfo->pos, textInfo->staticText); bool clipAdditionalInfoBounds = false; if (m_supportsItemExpanding) { // Prevent a possible overlapping of the additional-information texts // with the icon. This can happen if the user has minimized the width // of the name-column to a very small value. const qreal minX = m_pixmapPos.x() + m_pixmap.width() + 4 * itemListStyleOption.padding; if (textInfo->pos.x() + columnWidth("text") > minX) { clipAdditionalInfoBounds = true; painter->save(); painter->setClipRect(minX, 0, size().width() - minX, size().height(), Qt::IntersectClip); } } painter->setPen(m_additionalInfoTextColor); painter->setFont(m_customizedFont); for (int i = 1; i < m_sortedVisibleRoles.count(); ++i) { const TextInfo* textInfo = m_textInfo.value(m_sortedVisibleRoles[i]); painter->drawStaticText(textInfo->pos, textInfo->staticText); } if (!m_rating.isNull()) { const TextInfo* ratingTextInfo = m_textInfo.value("rating"); QPointF pos = ratingTextInfo->pos; const Qt::Alignment align = ratingTextInfo->staticText.textOption().alignment(); if (align & Qt::AlignHCenter) { pos.rx() += (size().width() - m_rating.width()) / 2 - 2; } painter->drawPixmap(pos, m_rating); } if (clipAdditionalInfoBounds) { painter->restore(); } #ifdef KSTANDARDITEMLISTWIDGET_DEBUG painter->setBrush(Qt::NoBrush); painter->setPen(Qt::green); painter->drawRect(m_iconRect); painter->setPen(Qt::blue); painter->drawRect(m_textRect); painter->setPen(Qt::red); painter->drawText(QPointF(0, m_customizedFontMetrics.height()), QString::number(index())); painter->drawRect(rect()); #endif } QRectF KStandardItemListWidget::iconRect() const { const_cast(this)->triggerCacheRefreshing(); return m_iconRect; } QRectF KStandardItemListWidget::textRect() const { const_cast(this)->triggerCacheRefreshing(); return m_textRect; } QRectF KStandardItemListWidget::textFocusRect() const { // In the compact- and details-layout a larger textRect() is returned to be aligned // with the iconRect(). This is useful to have a larger selection/hover-area // when having a quite large icon size but only one line of text. Still the // focus rectangle should be shown as narrow as possible around the text. const_cast(this)->triggerCacheRefreshing(); switch (m_layout) { case CompactLayout: { QRectF rect = m_textRect; const TextInfo* topText = m_textInfo.value(m_sortedVisibleRoles.first()); const TextInfo* bottomText = m_textInfo.value(m_sortedVisibleRoles.last()); rect.setTop(topText->pos.y()); rect.setBottom(bottomText->pos.y() + bottomText->staticText.size().height()); return rect; } case DetailsLayout: { QRectF rect = m_textRect; const TextInfo* textInfo = m_textInfo.value(m_sortedVisibleRoles.first()); rect.setTop(textInfo->pos.y()); rect.setBottom(textInfo->pos.y() + textInfo->staticText.size().height()); const KItemListStyleOption& option = styleOption(); if (option.extendedSelectionRegion) { const QString text = textInfo->staticText.text(); rect.setWidth(m_customizedFontMetrics.width(text) + 2 * option.padding); } return rect; } default: break; } return m_textRect; } QRectF KStandardItemListWidget::selectionRect() const { const_cast(this)->triggerCacheRefreshing(); switch (m_layout) { case IconsLayout: return m_textRect; case CompactLayout: case DetailsLayout: { const int padding = styleOption().padding; QRectF adjustedIconRect = iconRect().adjusted(-padding, -padding, padding, padding); return adjustedIconRect | m_textRect; } default: Q_ASSERT(false); break; } return m_textRect; } QRectF KStandardItemListWidget::expansionToggleRect() const { const_cast(this)->triggerCacheRefreshing(); return m_isExpandable ? m_expansionArea : QRectF(); } QRectF KStandardItemListWidget::selectionToggleRect() const { const_cast(this)->triggerCacheRefreshing(); const int iconHeight = styleOption().iconSize; int toggleSize = KIconLoader::SizeSmall; if (iconHeight >= KIconLoader::SizeEnormous) { toggleSize = KIconLoader::SizeMedium; } else if (iconHeight >= KIconLoader::SizeLarge) { toggleSize = KIconLoader::SizeSmallMedium; } QPointF pos = iconRect().topLeft(); // If the selection toggle has a very small distance to the // widget borders, the size of the selection toggle will get // increased to prevent an accidental clicking of the item // when trying to hit the toggle. const int widgetHeight = size().height(); const int widgetWidth = size().width(); const int minMargin = 2; if (toggleSize + minMargin * 2 >= widgetHeight) { pos.rx() -= (widgetHeight - toggleSize) / 2; toggleSize = widgetHeight; pos.setY(0); } if (toggleSize + minMargin * 2 >= widgetWidth) { pos.ry() -= (widgetWidth - toggleSize) / 2; toggleSize = widgetWidth; pos.setX(0); } return QRectF(pos, QSizeF(toggleSize, toggleSize)); } QPixmap KStandardItemListWidget::createDragPixmap(const QStyleOptionGraphicsItem* option, QWidget* widget) { QPixmap pixmap = KItemListWidget::createDragPixmap(option, widget); if (m_layout != DetailsLayout) { return pixmap; } // Only return the content of the text-column as pixmap const int leftClip = m_pixmapPos.x(); const TextInfo* textInfo = m_textInfo.value("text"); const int rightClip = textInfo->pos.x() + textInfo->staticText.size().width() + 2 * styleOption().padding; QPixmap clippedPixmap(rightClip - leftClip + 1, pixmap.height()); clippedPixmap.fill(Qt::transparent); QPainter painter(&clippedPixmap); painter.drawPixmap(-leftClip, 0, pixmap); return clippedPixmap; } KItemListWidgetInformant* KStandardItemListWidget::createInformant() { return new KStandardItemListWidgetInformant(); } void KStandardItemListWidget::invalidateCache() { m_dirtyLayout = true; m_dirtyContent = true; } void KStandardItemListWidget::refreshCache() { } bool KStandardItemListWidget::isRoleRightAligned(const QByteArray& role) const { Q_UNUSED(role); return false; } bool KStandardItemListWidget::isHidden() const { return false; } QFont KStandardItemListWidget::customizedFont(const QFont& baseFont) const { return baseFont; } QPalette::ColorRole KStandardItemListWidget::normalTextColorRole() const { return QPalette::Text; } void KStandardItemListWidget::setTextColor(const QColor& color) { if (color != m_customTextColor) { m_customTextColor = color; updateAdditionalInfoTextColor(); update(); } } QColor KStandardItemListWidget::textColor() const { if (!isSelected()) { if (m_isHidden) { return m_additionalInfoTextColor; } else if (m_customTextColor.isValid()) { return m_customTextColor; } } const QPalette::ColorGroup group = isActiveWindow() ? QPalette::Active : QPalette::Inactive; const QPalette::ColorRole role = isSelected() ? QPalette::HighlightedText : normalTextColorRole(); return styleOption().palette.color(group, role); } void KStandardItemListWidget::setOverlay(const QPixmap& overlay) { m_overlay = overlay; m_dirtyContent = true; update(); } QPixmap KStandardItemListWidget::overlay() const { return m_overlay; } QString KStandardItemListWidget::roleText(const QByteArray& role, const QHash& values) const { return static_cast(informant())->roleText(role, values); } void KStandardItemListWidget::dataChanged(const QHash& current, const QSet& roles) { Q_UNUSED(current); m_dirtyContent = true; QSet dirtyRoles; if (roles.isEmpty()) { dirtyRoles = visibleRoles().toSet(); } else { dirtyRoles = roles; } // The URL might have changed (i.e., if the sort order of the items has // been changed). Therefore, the "is cut" state must be updated. KFileItemClipboard* clipboard = KFileItemClipboard::instance(); const QUrl itemUrl = data().value("url").toUrl(); m_isCut = clipboard->isCut(itemUrl); // The icon-state might depend from other roles and hence is // marked as dirty whenever a role has been changed dirtyRoles.insert("iconPixmap"); dirtyRoles.insert("iconName"); QSetIterator it(dirtyRoles); while (it.hasNext()) { const QByteArray& role = it.next(); m_dirtyContentRoles.insert(role); } } void KStandardItemListWidget::visibleRolesChanged(const QList& current, const QList& previous) { Q_UNUSED(previous); m_sortedVisibleRoles = current; m_dirtyLayout = true; } void KStandardItemListWidget::columnWidthChanged(const QByteArray& role, qreal current, qreal previous) { Q_UNUSED(role); Q_UNUSED(current); Q_UNUSED(previous); m_dirtyLayout = true; } void KStandardItemListWidget::styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous) { Q_UNUSED(current); Q_UNUSED(previous); updateAdditionalInfoTextColor(); m_dirtyLayout = true; } void KStandardItemListWidget::hoveredChanged(bool hovered) { Q_UNUSED(hovered); m_dirtyLayout = true; } void KStandardItemListWidget::selectedChanged(bool selected) { Q_UNUSED(selected); updateAdditionalInfoTextColor(); m_dirtyContent = true; } void KStandardItemListWidget::siblingsInformationChanged(const QBitArray& current, const QBitArray& previous) { Q_UNUSED(current); Q_UNUSED(previous); m_dirtyLayout = true; } int KStandardItemListWidget::selectionLength(const QString& text) const { return text.length(); } void KStandardItemListWidget::editedRoleChanged(const QByteArray& current, const QByteArray& previous) { Q_UNUSED(previous); QGraphicsView* parent = scene()->views()[0]; if (current.isEmpty() || !parent || current != "text") { if (m_roleEditor) { emit roleEditingCanceled(index(), current, data().value(current)); disconnect(m_roleEditor, &KItemListRoleEditor::roleEditingCanceled, this, &KStandardItemListWidget::slotRoleEditingCanceled); disconnect(m_roleEditor, &KItemListRoleEditor::roleEditingFinished, this, &KStandardItemListWidget::slotRoleEditingFinished); if (m_oldRoleEditor) { m_oldRoleEditor->deleteLater(); } m_oldRoleEditor = m_roleEditor; m_roleEditor->hide(); m_roleEditor = nullptr; } return; } Q_ASSERT(!m_roleEditor); const TextInfo* textInfo = m_textInfo.value("text"); m_roleEditor = new KItemListRoleEditor(parent); m_roleEditor->setRole(current); m_roleEditor->setFont(styleOption().font); const QString text = data().value(current).toString(); m_roleEditor->setPlainText(text); QTextOption textOption = textInfo->staticText.textOption(); m_roleEditor->document()->setDefaultTextOption(textOption); const int textSelectionLength = selectionLength(text); if (textSelectionLength > 0) { QTextCursor cursor = m_roleEditor->textCursor(); cursor.movePosition(QTextCursor::StartOfBlock); cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, textSelectionLength); m_roleEditor->setTextCursor(cursor); } connect(m_roleEditor, &KItemListRoleEditor::roleEditingCanceled, this, &KStandardItemListWidget::slotRoleEditingCanceled); connect(m_roleEditor, &KItemListRoleEditor::roleEditingFinished, this, &KStandardItemListWidget::slotRoleEditingFinished); // Adjust the geometry of the editor QRectF rect = roleEditingRect(current); const int frameWidth = m_roleEditor->frameWidth(); rect.adjust(-frameWidth, -frameWidth, frameWidth, frameWidth); rect.translate(pos()); if (rect.right() > parent->width()) { rect.setWidth(parent->width() - rect.left()); } m_roleEditor->setGeometry(rect.toRect()); m_roleEditor->show(); m_roleEditor->setFocus(); } void KStandardItemListWidget::resizeEvent(QGraphicsSceneResizeEvent* event) { if (m_roleEditor) { setEditedRole(QByteArray()); Q_ASSERT(!m_roleEditor); } KItemListWidget::resizeEvent(event); m_dirtyLayout = true; } void KStandardItemListWidget::showEvent(QShowEvent* event) { KItemListWidget::showEvent(event); // Listen to changes of the clipboard to mark the item as cut/uncut KFileItemClipboard* clipboard = KFileItemClipboard::instance(); const QUrl itemUrl = data().value("url").toUrl(); m_isCut = clipboard->isCut(itemUrl); connect(clipboard, &KFileItemClipboard::cutItemsChanged, this, &KStandardItemListWidget::slotCutItemsChanged); } void KStandardItemListWidget::hideEvent(QHideEvent* event) { disconnect(KFileItemClipboard::instance(), &KFileItemClipboard::cutItemsChanged, this, &KStandardItemListWidget::slotCutItemsChanged); KItemListWidget::hideEvent(event); } bool KStandardItemListWidget::event(QEvent *event) { if (event->type() == QEvent::WindowDeactivate || event->type() == QEvent::WindowActivate || event->type() == QEvent::PaletteChange) { m_dirtyContent = true; } return KItemListWidget::event(event); } void KStandardItemListWidget::finishRoleEditing() { if (!editedRole().isEmpty() && m_roleEditor) { slotRoleEditingFinished(editedRole(), KIO::encodeFileName(m_roleEditor->toPlainText())); } } void KStandardItemListWidget::slotCutItemsChanged() { const QUrl itemUrl = data().value("url").toUrl(); const bool isCut = KFileItemClipboard::instance()->isCut(itemUrl); if (m_isCut != isCut) { m_isCut = isCut; m_pixmap = QPixmap(); m_dirtyContent = true; update(); } } void KStandardItemListWidget::slotRoleEditingCanceled(const QByteArray& role, const QVariant& value) { closeRoleEditor(); emit roleEditingCanceled(index(), role, value); setEditedRole(QByteArray()); } void KStandardItemListWidget::slotRoleEditingFinished(const QByteArray& role, const QVariant& value) { closeRoleEditor(); emit roleEditingFinished(index(), role, value); setEditedRole(QByteArray()); } void KStandardItemListWidget::triggerCacheRefreshing() { if ((!m_dirtyContent && !m_dirtyLayout) || index() < 0) { return; } refreshCache(); const QHash values = data(); m_isExpandable = m_supportsItemExpanding && values["isExpandable"].toBool(); m_isHidden = isHidden(); m_customizedFont = customizedFont(styleOption().font); m_customizedFontMetrics = QFontMetrics(m_customizedFont); updateExpansionArea(); updateTextsCache(); updatePixmapCache(); m_dirtyLayout = false; m_dirtyContent = false; m_dirtyContentRoles.clear(); } void KStandardItemListWidget::updateExpansionArea() { if (m_supportsItemExpanding) { const QHash values = data(); const int expandedParentsCount = values.value("expandedParentsCount", 0).toInt(); if (expandedParentsCount >= 0) { const KItemListStyleOption& option = styleOption(); const qreal widgetHeight = size().height(); const qreal inc = (widgetHeight - option.iconSize) / 2; const qreal x = expandedParentsCount * widgetHeight + inc; const qreal y = inc; m_expansionArea = QRectF(x, y, option.iconSize, option.iconSize); return; } } m_expansionArea = QRectF(); } void KStandardItemListWidget::updatePixmapCache() { // Precondition: Requires already updated m_textPos values to calculate // the remaining height when the alignment is vertical. const QSizeF widgetSize = size(); const bool iconOnTop = (m_layout == IconsLayout); const KItemListStyleOption& option = styleOption(); const qreal padding = option.padding; const int maxIconWidth = iconOnTop ? widgetSize.width() - 2 * padding : option.iconSize; const int maxIconHeight = option.iconSize; const QHash values = data(); bool updatePixmap = (m_pixmap.width() != maxIconWidth || m_pixmap.height() != maxIconHeight); if (!updatePixmap && m_dirtyContent) { updatePixmap = m_dirtyContentRoles.isEmpty() || m_dirtyContentRoles.contains("iconPixmap") || m_dirtyContentRoles.contains("iconName") || m_dirtyContentRoles.contains("iconOverlays"); } if (updatePixmap) { m_pixmap = values["iconPixmap"].value(); if (m_pixmap.isNull()) { // Use the icon that fits to the MIME-type QString iconName = values["iconName"].toString(); if (iconName.isEmpty()) { // The icon-name has not been not resolved by KFileItemModelRolesUpdater, // use a generic icon as fallback iconName = QStringLiteral("unknown"); } const QStringList overlays = values["iconOverlays"].toStringList(); m_pixmap = pixmapForIcon(iconName, overlays, maxIconHeight, isSelected() && isActiveWindow() ? QIcon::Selected : QIcon::Normal); } else if (m_pixmap.width() / m_pixmap.devicePixelRatio() != maxIconWidth || m_pixmap.height() / m_pixmap.devicePixelRatio() != maxIconHeight) { // A custom pixmap has been applied. Assure that the pixmap // is scaled to the maximum available size. KPixmapModifier::scale(m_pixmap, QSize(maxIconWidth, maxIconHeight) * qApp->devicePixelRatio()); } if (m_isCut) { KIconEffect* effect = KIconLoader::global()->iconEffect(); m_pixmap = effect->apply(m_pixmap, KIconLoader::Desktop, KIconLoader::DisabledState); } if (m_isHidden) { KIconEffect::semiTransparent(m_pixmap); } if (m_layout == IconsLayout && isSelected()) { const QColor color = palette().brush(QPalette::Normal, QPalette::Highlight).color(); QImage image = m_pixmap.toImage(); KIconEffect::colorize(image, color, 0.8f); m_pixmap = QPixmap::fromImage(image); } } if (!m_overlay.isNull()) { QPainter painter(&m_pixmap); painter.drawPixmap(0, (m_pixmap.height() - m_overlay.height()) / m_pixmap.devicePixelRatio(), m_overlay); } int scaledIconSize = 0; if (iconOnTop) { const TextInfo* textInfo = m_textInfo.value("text"); scaledIconSize = static_cast(textInfo->pos.y() - 2 * padding); } else { const int textRowsCount = (m_layout == CompactLayout) ? visibleRoles().count() : 1; const qreal requiredTextHeight = textRowsCount * m_customizedFontMetrics.height(); scaledIconSize = (requiredTextHeight < maxIconHeight) ? widgetSize.height() - 2 * padding : maxIconHeight; } const int maxScaledIconWidth = iconOnTop ? widgetSize.width() - 2 * padding : scaledIconSize; const int maxScaledIconHeight = scaledIconSize; m_scaledPixmapSize = m_pixmap.size(); m_scaledPixmapSize.scale(maxScaledIconWidth, maxScaledIconHeight, Qt::KeepAspectRatio); if (iconOnTop) { // Center horizontally and align on bottom within the icon-area m_pixmapPos.setX((widgetSize.width() - m_scaledPixmapSize.width()) / 2); m_pixmapPos.setY(padding + scaledIconSize - m_scaledPixmapSize.height()); } else { // Center horizontally and vertically within the icon-area const TextInfo* textInfo = m_textInfo.value("text"); m_pixmapPos.setX(textInfo->pos.x() - 2 * padding - (scaledIconSize + m_scaledPixmapSize.width()) / 2); m_pixmapPos.setY(padding + (scaledIconSize - m_scaledPixmapSize.height()) / 2); } m_iconRect = QRectF(m_pixmapPos, QSizeF(m_scaledPixmapSize)); // Prepare the pixmap that is used when the item gets hovered if (isHovered()) { m_hoverPixmap = m_pixmap; KIconEffect* effect = KIconLoader::global()->iconEffect(); // In the KIconLoader terminology, active = hover. if (effect->hasEffect(KIconLoader::Desktop, KIconLoader::ActiveState)) { m_hoverPixmap = effect->apply(m_pixmap, KIconLoader::Desktop, KIconLoader::ActiveState); } else { m_hoverPixmap = m_pixmap; } } else if (hoverOpacity() <= 0.0) { // No hover animation is ongoing. Clear m_hoverPixmap to save memory. m_hoverPixmap = QPixmap(); } } void KStandardItemListWidget::updateTextsCache() { QTextOption textOption; switch (m_layout) { case IconsLayout: textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); textOption.setAlignment(Qt::AlignHCenter); break; case CompactLayout: case DetailsLayout: textOption.setAlignment(Qt::AlignLeft); textOption.setWrapMode(QTextOption::NoWrap); break; default: Q_ASSERT(false); break; } qDeleteAll(m_textInfo); m_textInfo.clear(); for (int i = 0; i < m_sortedVisibleRoles.count(); ++i) { TextInfo* textInfo = new TextInfo(); textInfo->staticText.setTextFormat(Qt::PlainText); textInfo->staticText.setPerformanceHint(QStaticText::AggressiveCaching); textInfo->staticText.setTextOption(textOption); m_textInfo.insert(m_sortedVisibleRoles[i], textInfo); } switch (m_layout) { case IconsLayout: updateIconsLayoutTextCache(); break; case CompactLayout: updateCompactLayoutTextCache(); break; case DetailsLayout: updateDetailsLayoutTextCache(); break; default: Q_ASSERT(false); break; } const TextInfo* ratingTextInfo = m_textInfo.value("rating"); if (ratingTextInfo) { // The text of the rating-role has been set to empty to get // replaced by a rating-image showing the rating as stars. const KItemListStyleOption& option = styleOption(); QSizeF ratingSize = preferredRatingSize(option); const qreal availableWidth = (m_layout == DetailsLayout) ? columnWidth("rating") - columnPadding(option) : size().width(); if (ratingSize.width() > availableWidth) { ratingSize.rwidth() = availableWidth; } const qreal dpr = qApp->devicePixelRatio(); m_rating = QPixmap(ratingSize.toSize() * dpr); m_rating.setDevicePixelRatio(dpr); m_rating.fill(Qt::transparent); QPainter painter(&m_rating); const QRect rect(QPoint(0, 0), ratingSize.toSize()); const int rating = data().value("rating").toInt(); KRatingPainter::paintRating(&painter, rect, Qt::AlignJustify | Qt::AlignVCenter, rating); } else if (!m_rating.isNull()) { m_rating = QPixmap(); } } void KStandardItemListWidget::updateIconsLayoutTextCache() { // +------+ // | Icon | // +------+ // // Name role that // might get wrapped above // several lines. // Additional role 1 // Additional role 2 const QHash values = data(); const KItemListStyleOption& option = styleOption(); const qreal padding = option.padding; const qreal maxWidth = size().width() - 2 * padding; const qreal widgetHeight = size().height(); const qreal lineSpacing = m_customizedFontMetrics.lineSpacing(); // Initialize properties for the "text" role. It will be used as anchor // for initializing the position of the other roles. TextInfo* nameTextInfo = m_textInfo.value("text"); const QString nameText = KStringHandler::preProcessWrap(values["text"].toString()); nameTextInfo->staticText.setText(nameText); // Calculate the number of lines required for the name and the required width qreal nameWidth = 0; qreal nameHeight = 0; QTextLine line; QTextLayout layout(nameTextInfo->staticText.text(), m_customizedFont); layout.setTextOption(nameTextInfo->staticText.textOption()); layout.beginLayout(); int nameLineIndex = 0; while ((line = layout.createLine()).isValid()) { line.setLineWidth(maxWidth); nameWidth = qMax(nameWidth, line.naturalTextWidth()); nameHeight += line.height(); ++nameLineIndex; if (nameLineIndex == option.maxTextLines) { // The maximum number of textlines has been reached. If this is // the case provide an elided text if necessary. const int textLength = line.textStart() + line.textLength(); if (textLength < nameText.length()) { // Elide the last line of the text qreal elidingWidth = maxWidth; qreal lastLineWidth; do { QString lastTextLine = nameText.mid(line.textStart()); lastTextLine = m_customizedFontMetrics.elidedText(lastTextLine, Qt::ElideRight, elidingWidth); const QString elidedText = nameText.left(line.textStart()) + lastTextLine; nameTextInfo->staticText.setText(elidedText); lastLineWidth = m_customizedFontMetrics.boundingRect(lastTextLine).width(); // We do the text eliding in a loop with decreasing width (1 px / iteration) // to avoid problems related to different width calculation code paths // within Qt. (see bug 337104) elidingWidth -= 1.0; } while (lastLineWidth > maxWidth); nameWidth = qMax(nameWidth, lastLineWidth); } break; } } layout.endLayout(); // Use one line for each additional information const int additionalRolesCount = qMax(visibleRoles().count() - 1, 0); nameTextInfo->staticText.setTextWidth(maxWidth); nameTextInfo->pos = QPointF(padding, widgetHeight - nameHeight - additionalRolesCount * lineSpacing - padding); m_textRect = QRectF(padding + (maxWidth - nameWidth) / 2, nameTextInfo->pos.y(), nameWidth, nameHeight); // Calculate the position for each additional information qreal y = nameTextInfo->pos.y() + nameHeight; foreach (const QByteArray& role, m_sortedVisibleRoles) { if (role == "text") { continue; } const QString text = roleText(role, values); TextInfo* textInfo = m_textInfo.value(role); textInfo->staticText.setText(text); qreal requiredWidth = 0; QTextLayout layout(text, m_customizedFont); QTextOption textOption; textOption.setWrapMode(QTextOption::NoWrap); layout.setTextOption(textOption); layout.beginLayout(); QTextLine textLine = layout.createLine(); if (textLine.isValid()) { textLine.setLineWidth(maxWidth); requiredWidth = textLine.naturalTextWidth(); if (requiredWidth > maxWidth) { const QString elidedText = m_customizedFontMetrics.elidedText(text, Qt::ElideRight, maxWidth); textInfo->staticText.setText(elidedText); requiredWidth = m_customizedFontMetrics.width(elidedText); } else if (role == "rating") { // Use the width of the rating pixmap, because the rating text is empty. requiredWidth = m_rating.width(); } } layout.endLayout(); textInfo->pos = QPointF(padding, y); textInfo->staticText.setTextWidth(maxWidth); const QRectF textRect(padding + (maxWidth - requiredWidth) / 2, y, requiredWidth, lineSpacing); m_textRect |= textRect; y += lineSpacing; } // Add a padding to the text rectangle m_textRect.adjust(-padding, -padding, padding, padding); } void KStandardItemListWidget::updateCompactLayoutTextCache() { // +------+ Name role // | Icon | Additional role 1 // +------+ Additional role 2 const QHash values = data(); const KItemListStyleOption& option = styleOption(); const qreal widgetHeight = size().height(); const qreal lineSpacing = m_customizedFontMetrics.lineSpacing(); const qreal textLinesHeight = qMax(visibleRoles().count(), 1) * lineSpacing; const int scaledIconSize = (textLinesHeight < option.iconSize) ? widgetHeight - 2 * option.padding : option.iconSize; qreal maximumRequiredTextWidth = 0; const qreal x = option.padding * 3 + scaledIconSize; qreal y = qRound((widgetHeight - textLinesHeight) / 2); const qreal maxWidth = size().width() - x - option.padding; foreach (const QByteArray& role, m_sortedVisibleRoles) { const QString text = roleText(role, values); TextInfo* textInfo = m_textInfo.value(role); textInfo->staticText.setText(text); qreal requiredWidth = m_customizedFontMetrics.width(text); if (requiredWidth > maxWidth) { requiredWidth = maxWidth; const QString elidedText = m_customizedFontMetrics.elidedText(text, Qt::ElideRight, maxWidth); textInfo->staticText.setText(elidedText); } textInfo->pos = QPointF(x, y); textInfo->staticText.setTextWidth(maxWidth); maximumRequiredTextWidth = qMax(maximumRequiredTextWidth, requiredWidth); y += lineSpacing; } m_textRect = QRectF(x - 2 * option.padding, 0, maximumRequiredTextWidth + 3 * option.padding, widgetHeight); } void KStandardItemListWidget::updateDetailsLayoutTextCache() { // Precondition: Requires already updated m_expansionArea // to determine the left position. // +------+ // | Icon | Name role Additional role 1 Additional role 2 // +------+ m_textRect = QRectF(); const KItemListStyleOption& option = styleOption(); const QHash values = data(); const qreal widgetHeight = size().height(); const int scaledIconSize = widgetHeight - 2 * option.padding; const int fontHeight = m_customizedFontMetrics.height(); const qreal columnWidthInc = columnPadding(option); qreal firstColumnInc = scaledIconSize; if (m_supportsItemExpanding) { firstColumnInc += (m_expansionArea.left() + m_expansionArea.right() + widgetHeight) / 2; } else { firstColumnInc += option.padding; } qreal x = firstColumnInc; const qreal y = qMax(qreal(option.padding), (widgetHeight - fontHeight) / 2); foreach (const QByteArray& role, m_sortedVisibleRoles) { QString text = roleText(role, values); // Elide the text in case it does not fit into the available column-width qreal requiredWidth = m_customizedFontMetrics.width(text); const qreal roleWidth = columnWidth(role); qreal availableTextWidth = roleWidth - columnWidthInc; const bool isTextRole = (role == "text"); if (isTextRole) { availableTextWidth -= firstColumnInc; } if (requiredWidth > availableTextWidth) { text = m_customizedFontMetrics.elidedText(text, Qt::ElideRight, availableTextWidth); requiredWidth = m_customizedFontMetrics.width(text); } TextInfo* textInfo = m_textInfo.value(role); textInfo->staticText.setText(text); textInfo->pos = QPointF(x + columnWidthInc / 2, y); x += roleWidth; if (isTextRole) { const qreal textWidth = option.extendedSelectionRegion ? size().width() - textInfo->pos.x() : requiredWidth + 2 * option.padding; m_textRect = QRectF(textInfo->pos.x() - 2 * option.padding, 0, textWidth + option.padding, size().height()); // The column after the name should always be aligned on the same x-position independent // from the expansion-level shown in the name column x -= firstColumnInc; } else if (isRoleRightAligned(role)) { textInfo->pos.rx() += roleWidth - requiredWidth - columnWidthInc; } } } void KStandardItemListWidget::updateAdditionalInfoTextColor() { QColor c1; if (m_customTextColor.isValid()) { c1 = m_customTextColor; } else if (isSelected() && m_layout != DetailsLayout) { c1 = styleOption().palette.highlightedText().color(); } else { c1 = styleOption().palette.text().color(); } // For the color of the additional info the inactive text color // is not used as this might lead to unreadable text for some color schemes. Instead // the text color c1 is slightly mixed with the background color. const QColor c2 = styleOption().palette.base().color(); const int p1 = 70; const int p2 = 100 - p1; m_additionalInfoTextColor = QColor((c1.red() * p1 + c2.red() * p2) / 100, (c1.green() * p1 + c2.green() * p2) / 100, (c1.blue() * p1 + c2.blue() * p2) / 100); } void KStandardItemListWidget::drawPixmap(QPainter* painter, const QPixmap& pixmap) { if (m_scaledPixmapSize != pixmap.size() / pixmap.devicePixelRatio()) { QPixmap scaledPixmap = pixmap; KPixmapModifier::scale(scaledPixmap, m_scaledPixmapSize * qApp->devicePixelRatio()); scaledPixmap.setDevicePixelRatio(qApp->devicePixelRatio()); painter->drawPixmap(m_pixmapPos, scaledPixmap); #ifdef KSTANDARDITEMLISTWIDGET_DEBUG painter->setPen(Qt::blue); painter->drawRect(QRectF(m_pixmapPos, QSizeF(m_scaledPixmapSize))); #endif } else { painter->drawPixmap(m_pixmapPos, pixmap); } } void KStandardItemListWidget::drawSiblingsInformation(QPainter* painter) { const int siblingSize = size().height(); const int x = (m_expansionArea.left() + m_expansionArea.right() - siblingSize) / 2; QRect siblingRect(x, 0, siblingSize, siblingSize); QStyleOption option; option.palette.setColor(QPalette::Text, option.palette.color(normalTextColorRole())); bool isItemSibling = true; const QBitArray siblings = siblingsInformation(); for (int i = siblings.count() - 1; i >= 0; --i) { option.rect = siblingRect; option.state = siblings.at(i) ? QStyle::State_Sibling : QStyle::State_None; if (isItemSibling) { option.state |= QStyle::State_Item; if (m_isExpandable) { option.state |= QStyle::State_Children; } if (data().value("isExpanded").toBool()) { option.state |= QStyle::State_Open; } isItemSibling = false; } style()->drawPrimitive(QStyle::PE_IndicatorBranch, &option, painter); siblingRect.translate(-siblingRect.width(), 0); } } QRectF KStandardItemListWidget::roleEditingRect(const QByteArray& role) const { const TextInfo* textInfo = m_textInfo.value(role); if (!textInfo) { return QRectF(); } QRectF rect(textInfo->pos, textInfo->staticText.size()); if (m_layout == DetailsLayout) { rect.setWidth(columnWidth(role) - rect.x()); } return rect; } void KStandardItemListWidget::closeRoleEditor() { disconnect(m_roleEditor, &KItemListRoleEditor::roleEditingCanceled, this, &KStandardItemListWidget::slotRoleEditingCanceled); disconnect(m_roleEditor, &KItemListRoleEditor::roleEditingFinished, this, &KStandardItemListWidget::slotRoleEditingFinished); if (m_roleEditor->hasFocus()) { // If the editing was not ended by a FocusOut event, we have // to transfer the keyboard focus back to the KItemListContainer. scene()->views()[0]->parentWidget()->setFocus(); } if (m_oldRoleEditor) { m_oldRoleEditor->deleteLater(); } m_oldRoleEditor = m_roleEditor; m_roleEditor->hide(); m_roleEditor = nullptr; } QPixmap KStandardItemListWidget::pixmapForIcon(const QString& name, const QStringList& overlays, int size, QIcon::Mode mode) { static const QIcon fallbackIcon = QIcon::fromTheme(QStringLiteral("unknown")); size *= qApp->devicePixelRatio(); const QString key = "KStandardItemListWidget:" % name % ":" % overlays.join(QStringLiteral(":")) % ":" % QString::number(size) % ":" % QString::number(mode); QPixmap pixmap; if (!QPixmapCache::find(key, pixmap)) { const QIcon icon = QIcon::fromTheme(name, fallbackIcon); int requestedSize; if (size <= KIconLoader::SizeSmall) { requestedSize = KIconLoader::SizeSmall; } else if (size <= KIconLoader::SizeSmallMedium) { requestedSize = KIconLoader::SizeSmallMedium; } else if (size <= KIconLoader::SizeMedium) { requestedSize = KIconLoader::SizeMedium; } else if (size <= KIconLoader::SizeLarge) { requestedSize = KIconLoader::SizeLarge; } else if (size <= KIconLoader::SizeHuge) { requestedSize = KIconLoader::SizeHuge; } else if (size <= KIconLoader::SizeEnormous) { requestedSize = KIconLoader::SizeEnormous; } else if (size <= KIconLoader::SizeEnormous * 2) { requestedSize = KIconLoader::SizeEnormous * 2; } else { requestedSize = size; } pixmap = icon.pixmap(requestedSize / qApp->devicePixelRatio(), requestedSize / qApp->devicePixelRatio(), mode); if (requestedSize != size) { KPixmapModifier::scale(pixmap, QSize(size, size)); } // Strangely KFileItem::overlays() returns empty string-values, so // we need to check first whether an overlay must be drawn at all. // It is more efficient to do it here, as KIconLoader::drawOverlays() // assumes that an overlay will be drawn and has some additional // setup time. foreach (const QString& overlay, overlays) { if (!overlay.isEmpty()) { // There is at least one overlay, draw all overlays above m_pixmap // and cancel the check KIconLoader::global()->drawOverlays(overlays, pixmap, KIconLoader::Desktop); break; } } QPixmapCache::insert(key, pixmap); } pixmap.setDevicePixelRatio(qApp->devicePixelRatio()); return pixmap; } QSizeF KStandardItemListWidget::preferredRatingSize(const KItemListStyleOption& option) { const qreal height = option.fontMetrics.ascent(); return QSizeF(height * 5, height); } qreal KStandardItemListWidget::columnPadding(const KItemListStyleOption& option) { return option.padding * 6; } diff --git a/src/kitemviews/kstandarditemmodel.cpp b/src/kitemviews/kstandarditemmodel.cpp index 0779614c3..a4d42b571 100644 --- a/src/kitemviews/kstandarditemmodel.cpp +++ b/src/kitemviews/kstandarditemmodel.cpp @@ -1,239 +1,238 @@ /*************************************************************************** * Copyright (C) 2012 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 "kstandarditemmodel.h" #include "kstandarditem.h" -#include -#include + KStandardItemModel::KStandardItemModel(QObject* parent) : KItemModelBase(parent), m_items(), m_indexesForItems() { } KStandardItemModel::~KStandardItemModel() { qDeleteAll(m_items); m_items.clear(); m_indexesForItems.clear(); } void KStandardItemModel::insertItem(int index, KStandardItem* item) { if (index < 0 || index > count() || !item) { delete item; return; } if (!m_indexesForItems.contains(item)) { item->m_model = this; m_items.insert(index, item); m_indexesForItems.insert(item, index); // Inserting an item requires to update the indexes // afterwards from m_indexesForItems. for (int i = index + 1; i < m_items.count(); ++i) { m_indexesForItems.insert(m_items[i], i); } // TODO: no hierarchical items are handled yet onItemInserted(index); emit itemsInserted(KItemRangeList() << KItemRange(index, 1)); } } void KStandardItemModel::changeItem(int index, KStandardItem* item) { if (index < 0 || index >= count() || !item) { delete item; return; } item->m_model = this; QSet changedRoles; KStandardItem* oldItem = m_items[index]; const QHash oldData = oldItem->data(); const QHash newData = item->data(); // Determine which roles have been changed QHashIterator it(oldData); while (it.hasNext()) { it.next(); const QByteArray role = it.key(); const QVariant oldValue = it.value(); if (newData.contains(role) && newData.value(role) != oldValue) { changedRoles.insert(role); } } m_indexesForItems.remove(oldItem); delete oldItem; oldItem = nullptr; m_items[index] = item; m_indexesForItems.insert(item, index); onItemChanged(index, changedRoles); emit itemsChanged(KItemRangeList() << KItemRange(index, 1), changedRoles); } void KStandardItemModel::removeItem(int index) { if (index >= 0 && index < count()) { KStandardItem* item = m_items[index]; m_indexesForItems.remove(item); m_items.removeAt(index); // Removing an item requires to update the indexes // afterwards from m_indexesForItems. for (int i = index; i < m_items.count(); ++i) { m_indexesForItems.insert(m_items[i], i); } onItemRemoved(index, item); delete item; item = nullptr; emit itemsRemoved(KItemRangeList() << KItemRange(index, 1)); // TODO: no hierarchical items are handled yet } } void KStandardItemModel::clear() { int size = m_items.size(); m_items.clear(); m_indexesForItems.clear(); emit itemsRemoved(KItemRangeList() << KItemRange(0, size)); } KStandardItem* KStandardItemModel::item(int index) const { if (index < 0 || index >= m_items.count()) { return nullptr; } return m_items[index]; } int KStandardItemModel::index(const KStandardItem* item) const { return m_indexesForItems.value(item, -1); } void KStandardItemModel::appendItem(KStandardItem *item) { insertItem(m_items.count(), item); } int KStandardItemModel::count() const { return m_items.count(); } QHash KStandardItemModel::data(int index) const { if (index >= 0 && index < count()) { const KStandardItem* item = m_items[index]; if (item) { return item->data(); } } return QHash(); } bool KStandardItemModel::setData(int index, const QHash& values) { Q_UNUSED(values); if (index < 0 || index >= count()) { return false; } return true; } QMimeData* KStandardItemModel::createMimeData(const KItemSet& indexes) const { Q_UNUSED(indexes); return nullptr; } int KStandardItemModel::indexForKeyboardSearch(const QString& text, int startFromIndex) const { Q_UNUSED(text); Q_UNUSED(startFromIndex); return -1; } bool KStandardItemModel::supportsDropping(int index) const { Q_UNUSED(index); return false; } QString KStandardItemModel::roleDescription(const QByteArray& role) const { Q_UNUSED(role); return QString(); } QList > KStandardItemModel::groups() const { QList > groups; const QByteArray role = sortRole().isEmpty() ? "group" : sortRole(); bool isFirstGroupValue = true; QString groupValue; const int maxIndex = count() - 1; for (int i = 0; i <= maxIndex; ++i) { const QString newGroupValue = m_items.at(i)->dataValue(role).toString(); if (newGroupValue != groupValue || isFirstGroupValue) { groupValue = newGroupValue; groups.append(QPair(i, newGroupValue)); isFirstGroupValue = false; } } return groups; } void KStandardItemModel::onItemInserted(int index) { Q_UNUSED(index); } void KStandardItemModel::onItemChanged(int index, const QSet& changedRoles) { Q_UNUSED(index); Q_UNUSED(changedRoles); } void KStandardItemModel::onItemRemoved(int index, KStandardItem* removedItem) { Q_UNUSED(index); Q_UNUSED(removedItem); } diff --git a/src/kitemviews/private/kbaloorolesprovider.cpp b/src/kitemviews/private/kbaloorolesprovider.cpp index 53fc0b3b9..182428db5 100644 --- a/src/kitemviews/private/kbaloorolesprovider.cpp +++ b/src/kitemviews/private/kbaloorolesprovider.cpp @@ -1,210 +1,209 @@ /*************************************************************************** * Copyright (C) 2012 by Peter Penz * * Copyright (C) 2013 by Vishesh Handa * * * * 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 "kbaloorolesprovider.h" #include #include #include #include #include #include #include -#include #include struct KBalooRolesProviderSingleton { KBalooRolesProvider instance; }; Q_GLOBAL_STATIC(KBalooRolesProviderSingleton, s_balooRolesProvider) KBalooRolesProvider& KBalooRolesProvider::instance() { return s_balooRolesProvider->instance; } KBalooRolesProvider::~KBalooRolesProvider() { } QSet KBalooRolesProvider::roles() const { return m_roles; } QHash KBalooRolesProvider::roleValues(const Baloo::File& file, const QSet& roles) const { QHash values; int width = -1; int height = -1; QMapIterator it(file.properties()); while (it.hasNext()) { it.next(); const KFileMetaData::PropertyInfo pi(it.key()); const QString property = pi.name(); const QByteArray role = roleForProperty(property); if (role.isEmpty() || !roles.contains(role)) { continue; } const QVariant value = it.value(); if (role == "imageSize") { // Merge the two properties for width and height // as one string into the "imageSize" role if (property == QLatin1String("width")) { width = value.toInt(); } else if (property == QLatin1String("height")) { height = value.toInt(); } if (width >= 0 && height >= 0) { QString widthAndHeight = QString::number(width); widthAndHeight += QLatin1String(" x "); widthAndHeight += QString::number(height); values.insert(role, widthAndHeight); } } else if (role == "orientation") { const QString orientation = orientationFromValue(value.toInt()); values.insert(role, orientation); } else if (role == "duration") { const QString duration = durationFromValue(value.toInt()); values.insert(role, duration); } else if (role == "bitrate") { const QString bitrate = bitrateFromValue(value.toInt()); values.insert(role, bitrate); } else { values.insert(role, value.toString()); } } KFileMetaData::UserMetaData md(file.path()); if (roles.contains("tags")) { values.insert("tags", tagsFromValues(md.tags())); } if (roles.contains("rating")) { values.insert("rating", QString::number(md.rating())); } if (roles.contains("comment")) { values.insert("comment", md.userComment()); } if (roles.contains("originUrl")) { values.insert("originUrl", md.originUrl()); } return values; } QByteArray KBalooRolesProvider::roleForProperty(const QString& property) const { return m_roleForProperty.value(property); } KBalooRolesProvider::KBalooRolesProvider() : m_roles(), m_roleForProperty() { struct PropertyInfo { const char* const property; const char* const role; }; // Mapping from the URIs to the KFileItemModel roles. Note that this must not be // a 1:1 mapping: One role may contain several URI-values (e.g. the URIs for height and // width of an image are mapped to the role "imageSize") static const PropertyInfo propertyInfoList[] = { { "rating", "rating" }, { "tag", "tags" }, { "comment", "comment" }, { "title", "title" }, { "wordCount", "wordCount" }, { "lineCount", "lineCount" }, { "width", "imageSize" }, { "height", "imageSize" }, { "imageDateTime", "imageDateTime"}, { "nexif.orientation", "orientation", }, { "artist", "artist" }, { "genre", "genre" }, { "album", "album" }, { "duration", "duration" }, { "bitRate", "bitrate" }, { "releaseYear", "releaseYear" }, { "trackNumber", "track" }, { "originUrl", "originUrl" } }; for (unsigned int i = 0; i < sizeof(propertyInfoList) / sizeof(PropertyInfo); ++i) { m_roleForProperty.insert(propertyInfoList[i].property, propertyInfoList[i].role); m_roles.insert(propertyInfoList[i].role); } } QString KBalooRolesProvider::tagsFromValues(const QStringList& values) const { QStringList alphabeticalOrderTags = values; QCollator coll; coll.setNumericMode(true); std::sort(alphabeticalOrderTags.begin(), alphabeticalOrderTags.end(), [&](const QString& s1, const QString& s2){ return coll.compare(s1, s2) < 0; }); return alphabeticalOrderTags.join(QStringLiteral(", ")); } QString KBalooRolesProvider::orientationFromValue(int value) const { QString string; switch (value) { case 1: string = i18nc("@item:intable Image orientation", "Unchanged"); break; case 2: string = i18nc("@item:intable Image orientation", "Horizontally flipped"); break; case 3: string = i18nc("@item:intable image orientation", "180° rotated"); break; case 4: string = i18nc("@item:intable image orientation", "Vertically flipped"); break; case 5: string = i18nc("@item:intable image orientation", "Transposed"); break; case 6: string = i18nc("@item:intable image orientation", "90° rotated"); break; case 7: string = i18nc("@item:intable image orientation", "Transversed"); break; case 8: string = i18nc("@item:intable image orientation", "270° rotated"); break; default: break; } return string; } QString KBalooRolesProvider::durationFromValue(int value) const { QTime duration(0, 0, 0); duration = duration.addSecs(value); return duration.toString(QStringLiteral("hh:mm:ss")); } QString KBalooRolesProvider::bitrateFromValue(int value) const { KFormat form; QString bitrate = i18nc("@label bitrate (per second)", "%1/s", form.formatByteSize(value, 1, KFormat::MetricBinaryDialect)); return bitrate; } diff --git a/src/kitemviews/private/kdirectorycontentscounter.cpp b/src/kitemviews/private/kdirectorycontentscounter.cpp index 90716173c..2c33edd41 100644 --- a/src/kitemviews/private/kdirectorycontentscounter.cpp +++ b/src/kitemviews/private/kdirectorycontentscounter.cpp @@ -1,184 +1,183 @@ /*************************************************************************** * Copyright (C) 2011 by Peter Penz * * Copyright (C) 2013 by Frank Reininghaus * * * * 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 "kdirectorycontentscounter.h" -#include "kdirectorycontentscounterworker.h" #include #include #include KDirectoryContentsCounter::KDirectoryContentsCounter(KFileItemModel* model, QObject* parent) : QObject(parent), m_model(model), m_queue(), m_worker(nullptr), m_workerIsBusy(false), m_dirWatcher(nullptr), m_watchedDirs() { connect(m_model, &KFileItemModel::itemsRemoved, this, &KDirectoryContentsCounter::slotItemsRemoved); if (!m_workerThread) { m_workerThread = new QThread(); m_workerThread->start(); } m_worker = new KDirectoryContentsCounterWorker(); m_worker->moveToThread(m_workerThread); ++m_workersCount; connect(this, &KDirectoryContentsCounter::requestDirectoryContentsCount, m_worker, &KDirectoryContentsCounterWorker::countDirectoryContents); connect(m_worker, &KDirectoryContentsCounterWorker::result, this, &KDirectoryContentsCounter::slotResult); m_dirWatcher = new KDirWatch(this); connect(m_dirWatcher, &KDirWatch::dirty, this, &KDirectoryContentsCounter::slotDirWatchDirty); } KDirectoryContentsCounter::~KDirectoryContentsCounter() { --m_workersCount; if (m_workersCount > 0) { // The worker thread will continue running. It could even be running // a method of m_worker at the moment, so we delete it using // deleteLater() to prevent a crash. m_worker->deleteLater(); } else { // There are no remaining workers -> stop the worker thread. m_workerThread->quit(); m_workerThread->wait(); delete m_workerThread; m_workerThread = nullptr; // The worker thread has finished running now, so it's safe to delete // m_worker. deleteLater() would not work at all because the event loop // which would deliver the event to m_worker is not running any more. delete m_worker; } } void KDirectoryContentsCounter::addDirectory(const QString& path) { startWorker(path); } int KDirectoryContentsCounter::countDirectoryContentsSynchronously(const QString& path) { if (!m_dirWatcher->contains(path)) { m_dirWatcher->addDir(path); m_watchedDirs.insert(path); } KDirectoryContentsCounterWorker::Options options; if (m_model->showHiddenFiles()) { options |= KDirectoryContentsCounterWorker::CountHiddenFiles; } if (m_model->showDirectoriesOnly()) { options |= KDirectoryContentsCounterWorker::CountDirectoriesOnly; } return KDirectoryContentsCounterWorker::subItemsCount(path, options); } void KDirectoryContentsCounter::slotResult(const QString& path, int count) { m_workerIsBusy = false; if (!m_dirWatcher->contains(path)) { m_dirWatcher->addDir(path); m_watchedDirs.insert(path); } if (!m_queue.isEmpty()) { startWorker(m_queue.dequeue()); } emit result(path, count); } void KDirectoryContentsCounter::slotDirWatchDirty(const QString& path) { const int index = m_model->index(QUrl::fromLocalFile(path)); if (index >= 0) { if (!m_model->fileItem(index).isDir()) { // If INotify is used, KDirWatch issues the dirty() signal // also for changed files inside the directory, even if we // don't enable this behavior explicitly (see bug 309740). return; } startWorker(path); } } void KDirectoryContentsCounter::slotItemsRemoved() { const bool allItemsRemoved = (m_model->count() == 0); if (!m_watchedDirs.isEmpty()) { // Don't let KDirWatch watch for removed items if (allItemsRemoved) { foreach (const QString& path, m_watchedDirs) { m_dirWatcher->removeDir(path); } m_watchedDirs.clear(); m_queue.clear(); } else { QMutableSetIterator it(m_watchedDirs); while (it.hasNext()) { const QString& path = it.next(); if (m_model->index(QUrl::fromLocalFile(path)) < 0) { m_dirWatcher->removeDir(path); it.remove(); } } } } } void KDirectoryContentsCounter::startWorker(const QString& path) { if (m_workerIsBusy) { m_queue.enqueue(path); } else { KDirectoryContentsCounterWorker::Options options; if (m_model->showHiddenFiles()) { options |= KDirectoryContentsCounterWorker::CountHiddenFiles; } if (m_model->showDirectoriesOnly()) { options |= KDirectoryContentsCounterWorker::CountDirectoriesOnly; } emit requestDirectoryContentsCount(path, options); m_workerIsBusy = true; } } QThread* KDirectoryContentsCounter::m_workerThread = nullptr; int KDirectoryContentsCounter::m_workersCount = 0; diff --git a/src/kitemviews/private/kfileitemmodelfilter.cpp b/src/kitemviews/private/kfileitemmodelfilter.cpp index a388a242c..fc56cc71d 100644 --- a/src/kitemviews/private/kfileitemmodelfilter.cpp +++ b/src/kitemviews/private/kfileitemmodelfilter.cpp @@ -1,122 +1,121 @@ /*************************************************************************** * Copyright (C) 2011 by Janardhan Reddy * * * * * * 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 "kfileitemmodelfilter.h" #include -#include KFileItemModelFilter::KFileItemModelFilter() : m_useRegExp(false), m_regExp(nullptr), m_lowerCasePattern(), m_pattern() { } KFileItemModelFilter::~KFileItemModelFilter() { delete m_regExp; m_regExp = nullptr; } void KFileItemModelFilter::setPattern(const QString& filter) { m_pattern = filter; m_lowerCasePattern = filter.toLower(); if (filter.contains('*') || filter.contains('?') || filter.contains('[')) { if (!m_regExp) { m_regExp = new QRegExp(); m_regExp->setCaseSensitivity(Qt::CaseInsensitive); m_regExp->setMinimal(false); m_regExp->setPatternSyntax(QRegExp::WildcardUnix); } m_regExp->setPattern(filter); m_useRegExp = m_regExp->isValid(); } else { m_useRegExp = false; } } QString KFileItemModelFilter::pattern() const { return m_pattern; } void KFileItemModelFilter::setMimeTypes(const QStringList& types) { m_mimeTypes = types; } QStringList KFileItemModelFilter::mimeTypes() const { return m_mimeTypes; } bool KFileItemModelFilter::hasSetFilters() const { return (!m_pattern.isEmpty() || !m_mimeTypes.isEmpty()); } bool KFileItemModelFilter::matches(const KFileItem& item) const { const bool hasPatternFilter = !m_pattern.isEmpty(); const bool hasMimeTypesFilter = !m_mimeTypes.isEmpty(); // If no filter is set, return true. if (!hasPatternFilter && !hasMimeTypesFilter) { return true; } // If both filters are set, return true when both filters are matched if (hasPatternFilter && hasMimeTypesFilter) { return (matchesPattern(item) && matchesType(item)); } // If only one filter is set, return true when that filter is matched if (hasPatternFilter) { return matchesPattern(item); } return matchesType(item); } bool KFileItemModelFilter::matchesPattern(const KFileItem& item) const { if (m_useRegExp) { return m_regExp->exactMatch(item.text()); } else { return item.text().toLower().contains(m_lowerCasePattern); } } bool KFileItemModelFilter::matchesType(const KFileItem& item) const { foreach (const QString& mimeType, m_mimeTypes) { if (item.mimetype() == mimeType) { return true; } } return m_mimeTypes.isEmpty(); } diff --git a/src/kitemviews/private/kitemlistroleeditor.cpp b/src/kitemviews/private/kitemlistroleeditor.cpp index 5ff013429..e79a9f9d1 100644 --- a/src/kitemviews/private/kitemlistroleeditor.cpp +++ b/src/kitemviews/private/kitemlistroleeditor.cpp @@ -1,149 +1,148 @@ /*************************************************************************** * Copyright (C) 2012 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 "kitemlistroleeditor.h" #include -#include KItemListRoleEditor::KItemListRoleEditor(QWidget *parent) : KTextEdit(parent), m_role(), m_blockFinishedSignal(false) { setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setAcceptRichText(false); enableFindReplace(false); document()->setDocumentMargin(0); if (parent) { parent->installEventFilter(this); } connect(this, &KItemListRoleEditor::textChanged, this, &KItemListRoleEditor::autoAdjustSize); } KItemListRoleEditor::~KItemListRoleEditor() { } void KItemListRoleEditor::setRole(const QByteArray& role) { m_role = role; } QByteArray KItemListRoleEditor::role() const { return m_role; } bool KItemListRoleEditor::eventFilter(QObject* watched, QEvent* event) { if (watched == parentWidget() && event->type() == QEvent::Resize) { emitRoleEditingFinished(); } return KTextEdit::eventFilter(watched, event); } bool KItemListRoleEditor::event(QEvent* event) { if (event->type() == QEvent::FocusOut) { QFocusEvent* focusEvent = static_cast(event); if (focusEvent->reason() != Qt::PopupFocusReason) { emitRoleEditingFinished(); } } return KTextEdit::event(event); } void KItemListRoleEditor::keyPressEvent(QKeyEvent* event) { switch (event->key()) { case Qt::Key_Escape: // Emitting the signal roleEditingCanceled might result // in losing the focus. Per default losing the focus emits // a roleEditingFinished signal (see KItemListRoleEditor::event), // which is not wanted in this case. m_blockFinishedSignal = true; emit roleEditingCanceled(m_role, KIO::encodeFileName(toPlainText())); m_blockFinishedSignal = false; event->accept(); return; case Qt::Key_Enter: case Qt::Key_Return: emitRoleEditingFinished(); event->accept(); return; case Qt::Key_Left: case Qt::Key_Right: { QTextCursor cursor = textCursor(); if (event->modifiers() == Qt::NoModifier && cursor.hasSelection()) { if (event->key() == Qt::Key_Left) { cursor.setPosition(cursor.selectionStart()); } else { cursor.setPosition(cursor.selectionEnd()); } cursor.clearSelection(); setTextCursor(cursor); event->accept(); return; } break; } default: break; } KTextEdit::keyPressEvent(event); } void KItemListRoleEditor::autoAdjustSize() { const qreal frameBorder = 2 * frameWidth(); const qreal requiredWidth = document()->size().width(); const qreal availableWidth = size().width() - frameBorder; if (requiredWidth > availableWidth) { qreal newWidth = requiredWidth + frameBorder; if (parentWidget() && pos().x() + newWidth > parentWidget()->width()) { newWidth = parentWidget()->width() - pos().x(); } resize(newWidth, size().height()); } const qreal requiredHeight = document()->size().height(); const qreal availableHeight = size().height() - frameBorder; if (requiredHeight > availableHeight) { qreal newHeight = requiredHeight + frameBorder; if (parentWidget() && pos().y() + newHeight > parentWidget()->height()) { newHeight = parentWidget()->height() - pos().y(); } resize(size().width(), newHeight); } } void KItemListRoleEditor::emitRoleEditingFinished() { if (!m_blockFinishedSignal) { emit roleEditingFinished(m_role, KIO::encodeFileName(toPlainText())); } } diff --git a/src/kitemviews/private/kitemlistsmoothscroller.cpp b/src/kitemviews/private/kitemlistsmoothscroller.cpp index 77a842838..f1f6145c4 100644 --- a/src/kitemviews/private/kitemlistsmoothscroller.cpp +++ b/src/kitemviews/private/kitemlistsmoothscroller.cpp @@ -1,211 +1,210 @@ /*************************************************************************** * 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 "kitemlistsmoothscroller.h" #include -#include #include #include #include #include KItemListSmoothScroller::KItemListSmoothScroller(QScrollBar* scrollBar, QObject* parent) : QObject(parent), m_scrollBarPressed(false), m_smoothScrolling(true), m_scrollBar(scrollBar), m_animation(nullptr) { m_animation = new QPropertyAnimation(this); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) const int animationDuration = m_scrollBar->style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, m_scrollBar); const bool animationEnabled = (animationDuration > 0); #else const int animationDuration = 100; const bool animationEnabled = m_scrollBar->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, m_scrollBar); #endif m_animation->setDuration(animationEnabled ? animationDuration : 1); connect(m_animation, &QPropertyAnimation::stateChanged, this, &KItemListSmoothScroller::slotAnimationStateChanged); m_scrollBar->installEventFilter(this); } KItemListSmoothScroller::~KItemListSmoothScroller() { } void KItemListSmoothScroller::setScrollBar(QScrollBar *scrollBar) { m_scrollBar = scrollBar; } QScrollBar* KItemListSmoothScroller::scrollBar() const { return m_scrollBar; } void KItemListSmoothScroller::setTargetObject(QObject* target) { m_animation->setTargetObject(target); } QObject* KItemListSmoothScroller::targetObject() const { return m_animation->targetObject(); } void KItemListSmoothScroller::setPropertyName(const QByteArray& propertyName) { m_animation->setPropertyName(propertyName); } QByteArray KItemListSmoothScroller::propertyName() const { return m_animation->propertyName(); } void KItemListSmoothScroller::scrollContentsBy(qreal distance) { QObject* target = targetObject(); if (!target) { return; } const QByteArray name = propertyName(); const qreal currentOffset = target->property(name).toReal(); if (static_cast(currentOffset) == m_scrollBar->value()) { // The current offset is already synchronous to the scrollbar return; } const bool animRunning = (m_animation->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. const qreal oldEndOffset = m_animation->endValue().toReal(); distance += (currentOffset - oldEndOffset); } const qreal endOffset = currentOffset - distance; if (m_smoothScrolling || animRunning) { qreal startOffset = currentOffset; if (animRunning) { // If the animation was running and has been interrupted by assigning a new end-offset // one frame must be added to the start-offset to keep the animation smooth. This also // assures that animation proceeds even in cases where new end-offset are triggered // within a very short timeslots. startOffset += (endOffset - currentOffset) * 1000 / (m_animation->duration() * 60); if (currentOffset < endOffset) { startOffset = qMin(startOffset, endOffset); } else { startOffset = qMax(startOffset, endOffset); } } m_animation->stop(); m_animation->setStartValue(startOffset); m_animation->setEndValue(endOffset); m_animation->setEasingCurve(animRunning ? QEasingCurve::OutQuad : QEasingCurve::InOutQuad); m_animation->start(); target->setProperty(name, startOffset); } else { target->setProperty(name, endOffset); } } void KItemListSmoothScroller::scrollTo(qreal position) { int newValue = position; newValue = qBound(0, newValue, m_scrollBar->maximum()); if (newValue != m_scrollBar->value()) { m_smoothScrolling = true; m_scrollBar->setValue(newValue); } } bool KItemListSmoothScroller::requestScrollBarUpdate(int newMaximum) { if (m_animation->state() == QAbstractAnimation::Running) { if (newMaximum == m_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 false; } // 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_animation->stop(); } return true; } bool KItemListSmoothScroller::eventFilter(QObject* obj, QEvent* event) { Q_ASSERT(obj == m_scrollBar); switch (event->type()) { case QEvent::MouseButtonPress: m_scrollBarPressed = true; m_smoothScrolling = true; break; case QEvent::MouseButtonRelease: m_scrollBarPressed = false; m_smoothScrolling = false; break; case QEvent::Wheel: return false; // we're the ones sending them default: break; } return QObject::eventFilter(obj, event); } void KItemListSmoothScroller::slotAnimationStateChanged(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) { Q_UNUSED(oldState); if (newState == QAbstractAnimation::Stopped && m_smoothScrolling && !m_scrollBarPressed) { m_smoothScrolling = false; } } void KItemListSmoothScroller::handleWheelEvent(QWheelEvent* event) { const bool previous = m_smoothScrolling; m_smoothScrolling = true; QWheelEvent copy = *event; QApplication::sendEvent(m_scrollBar, ©); event->setAccepted(copy.isAccepted()); m_smoothScrolling = previous; } diff --git a/src/kitemviews/private/kitemlistviewanimation.cpp b/src/kitemviews/private/kitemlistviewanimation.cpp index 89b345210..7c6782b7a 100644 --- a/src/kitemviews/private/kitemlistviewanimation.cpp +++ b/src/kitemviews/private/kitemlistviewanimation.cpp @@ -1,239 +1,237 @@ /*************************************************************************** * 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 "kitemlistviewanimation.h" #include - -#include #include KItemListViewAnimation::KItemListViewAnimation(QObject* parent) : QObject(parent), m_scrollOrientation(Qt::Vertical), m_scrollOffset(0), m_animation() { } KItemListViewAnimation::~KItemListViewAnimation() { for (int type = 0; type < AnimationTypeCount; ++type) { qDeleteAll(m_animation[type]); } } void KItemListViewAnimation::setScrollOrientation(Qt::Orientation orientation) { m_scrollOrientation = orientation; } Qt::Orientation KItemListViewAnimation::scrollOrientation() const { return m_scrollOrientation; } void KItemListViewAnimation::setScrollOffset(qreal offset) { const qreal diff = m_scrollOffset - offset; m_scrollOffset = offset; // The change of the offset requires that the position of all // animated QGraphicsWidgets get adjusted. An exception is made // for the delete animation that should just fade away on the // existing position. for (int type = 0; type < AnimationTypeCount; ++type) { if (type == DeleteAnimation) { continue; } QHashIterator it(m_animation[type]); while (it.hasNext()) { it.next(); QGraphicsWidget* widget = it.key(); QPropertyAnimation* propertyAnim = it.value(); QPointF currentPos = widget->pos(); if (m_scrollOrientation == Qt::Vertical) { currentPos.ry() += diff; } else { currentPos.rx() += diff; } if (type == MovingAnimation) { // Stop the animation, calculate the moved start- and end-value // and restart the animation for the remaining duration. const int remainingDuration = propertyAnim->duration() - propertyAnim->currentTime(); const bool block = propertyAnim->signalsBlocked(); propertyAnim->blockSignals(true); propertyAnim->stop(); QPointF endPos = propertyAnim->endValue().toPointF(); if (m_scrollOrientation == Qt::Vertical) { endPos.ry() += diff; } else { endPos.rx() += diff; } propertyAnim->setDuration(remainingDuration); propertyAnim->setStartValue(currentPos); propertyAnim->setEndValue(endPos); propertyAnim->start(); propertyAnim->blockSignals(block); } else { widget->setPos(currentPos); } } } } qreal KItemListViewAnimation::scrollOffset() const { return m_scrollOffset; } void KItemListViewAnimation::start(QGraphicsWidget* widget, AnimationType type, const QVariant& endValue) { stop(widget, type); QPropertyAnimation* propertyAnim = nullptr; const int animationDuration = widget->style()->styleHint(QStyle::SH_Widget_Animate) ? 200 : 1; switch (type) { case MovingAnimation: { const QPointF newPos = endValue.toPointF(); if (newPos == widget->pos()) { return; } propertyAnim = new QPropertyAnimation(widget, "pos"); propertyAnim->setDuration(animationDuration); propertyAnim->setEndValue(newPos); break; } case CreateAnimation: { propertyAnim = new QPropertyAnimation(widget, "opacity"); propertyAnim->setEasingCurve(QEasingCurve::InQuart); propertyAnim->setDuration(animationDuration); propertyAnim->setStartValue(0.0); propertyAnim->setEndValue(1.0); break; } case DeleteAnimation: { propertyAnim = new QPropertyAnimation(widget, "opacity"); propertyAnim->setEasingCurve(QEasingCurve::OutQuart); propertyAnim->setDuration(animationDuration); propertyAnim->setStartValue(1.0); propertyAnim->setEndValue(0.0); break; } case ResizeAnimation: { const QSizeF newSize = endValue.toSizeF(); if (newSize == widget->size()) { return; } propertyAnim = new QPropertyAnimation(widget, "size"); propertyAnim->setDuration(animationDuration); propertyAnim->setEndValue(newSize); break; } default: break; } Q_ASSERT(propertyAnim); connect(propertyAnim, &QPropertyAnimation::finished, this, &KItemListViewAnimation::slotFinished); m_animation[type].insert(widget, propertyAnim); propertyAnim->start(); } void KItemListViewAnimation::stop(QGraphicsWidget* widget, AnimationType type) { QPropertyAnimation* propertyAnim = m_animation[type].value(widget); if (propertyAnim) { propertyAnim->stop(); switch (type) { case MovingAnimation: break; case CreateAnimation: widget->setOpacity(1.0); break; case DeleteAnimation: widget->setOpacity(0.0); break; case ResizeAnimation: break; default: break; } m_animation[type].remove(widget); delete propertyAnim; emit finished(widget, type); } } void KItemListViewAnimation::stop(QGraphicsWidget* widget) { for (int type = 0; type < AnimationTypeCount; ++type) { stop(widget, static_cast(type)); } } bool KItemListViewAnimation::isStarted(QGraphicsWidget *widget, AnimationType type) const { return m_animation[type].value(widget); } bool KItemListViewAnimation::isStarted(QGraphicsWidget* widget) const { for (int type = 0; type < AnimationTypeCount; ++type) { if (isStarted(widget, static_cast(type))) { return true; } } return false; } void KItemListViewAnimation::slotFinished() { QPropertyAnimation* finishedAnim = qobject_cast(sender()); for (int type = 0; type < AnimationTypeCount; ++type) { QMutableHashIterator it(m_animation[type]); while (it.hasNext()) { it.next(); QPropertyAnimation* propertyAnim = it.value(); if (propertyAnim == finishedAnim) { QGraphicsWidget* widget = it.key(); it.remove(); finishedAnim->deleteLater(); emit finished(widget, static_cast(type)); return; } } } Q_ASSERT(false); } diff --git a/src/kitemviews/private/kpixmapmodifier.cpp b/src/kitemviews/private/kpixmapmodifier.cpp index 9340c9790..3bf11483b 100644 --- a/src/kitemviews/private/kpixmapmodifier.cpp +++ b/src/kitemviews/private/kpixmapmodifier.cpp @@ -1,362 +1,360 @@ // krazy:excludeall=copyright (email of Maxim is missing) /* This file is a part of the KDE project Copyright © 2006 Zack Rusin Copyright © 2006-2007, 2008 Fredrik Höglund The stack blur algorithm was invented by Mario Klingemann This implementation is based on the version in Anti-Grain Geometry Version 2.4, Copyright © 2002-2005 Maxim Shemanarev (http://www.antigrain.com) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "kpixmapmodifier.h" #include #include -#include -#include #include static const quint32 stackBlur8Mul[255] = { 512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512, 454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512, 482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456, 437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512, 497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328, 320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456, 446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335, 329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512, 505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405, 399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328, 324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271, 268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456, 451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388, 385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335, 332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292, 289,287,285,282,280,278,275,273,271,269,267,265,263,261,259 }; static const quint32 stackBlur8Shr[255] = { 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 }; static void blurHorizontal(QImage& image, unsigned int* stack, int div, int radius) { int stackindex; int stackstart; quint32 * const pixels = reinterpret_cast(image.bits()); quint32 pixel; int w = image.width(); int h = image.height(); int wm = w - 1; unsigned int mulSum = stackBlur8Mul[radius]; unsigned int shrSum = stackBlur8Shr[radius]; unsigned int sum, sumIn, sumOut; for (int y = 0; y < h; y++) { sum = 0; sumIn = 0; sumOut = 0; const int yw = y * w; pixel = pixels[yw]; for (int i = 0; i <= radius; i++) { stack[i] = qAlpha(pixel); sum += stack[i] * (i + 1); sumOut += stack[i]; } for (int i = 1; i <= radius; i++) { pixel = pixels[yw + qMin(i, wm)]; unsigned int* stackpix = &stack[i + radius]; *stackpix = qAlpha(pixel); sum += *stackpix * (radius + 1 - i); sumIn += *stackpix; } stackindex = radius; for (int x = 0, i = yw; x < w; x++) { pixels[i++] = (((sum * mulSum) >> shrSum) << 24) & 0xff000000; sum -= sumOut; stackstart = stackindex + div - radius; if (stackstart >= div) { stackstart -= div; } unsigned int* stackpix = &stack[stackstart]; sumOut -= *stackpix; pixel = pixels[yw + qMin(x + radius + 1, wm)]; *stackpix = qAlpha(pixel); sumIn += *stackpix; sum += sumIn; if (++stackindex >= div) { stackindex = 0; } stackpix = &stack[stackindex]; sumOut += *stackpix; sumIn -= *stackpix; } } } static void blurVertical(QImage& image, unsigned int* stack, int div, int radius) { int stackindex; int stackstart; quint32 * const pixels = reinterpret_cast(image.bits()); quint32 pixel; int w = image.width(); int h = image.height(); int hm = h - 1; int mul_sum = stackBlur8Mul[radius]; int shr_sum = stackBlur8Shr[radius]; unsigned int sum, sumIn, sumOut; for (int x = 0; x < w; x++) { sum = 0; sumIn = 0; sumOut = 0; pixel = pixels[x]; for (int i = 0; i <= radius; i++) { stack[i] = qAlpha(pixel); sum += stack[i] * (i + 1); sumOut += stack[i]; } for (int i = 1; i <= radius; i++) { pixel = pixels[qMin(i, hm) * w + x]; unsigned int* stackpix = &stack[i + radius]; *stackpix = qAlpha(pixel); sum += *stackpix * (radius + 1 - i); sumIn += *stackpix; } stackindex = radius; for (int y = 0, i = x; y < h; y++, i += w) { pixels[i] = (((sum * mul_sum) >> shr_sum) << 24) & 0xff000000; sum -= sumOut; stackstart = stackindex + div - radius; if (stackstart >= div) stackstart -= div; unsigned int* stackpix = &stack[stackstart]; sumOut -= *stackpix; pixel = pixels[qMin(y + radius + 1, hm) * w + x]; *stackpix = qAlpha(pixel); sumIn += *stackpix; sum += sumIn; if (++stackindex >= div) { stackindex = 0; } stackpix = &stack[stackindex]; sumOut += *stackpix; sumIn -= *stackpix; } } } static void stackBlur(QImage& image, float radius) { radius = qRound(radius); int div = int(radius * 2) + 1; unsigned int* stack = new unsigned int[div]; blurHorizontal(image, stack, div, radius); blurVertical(image, stack, div, radius); delete [] stack; } static void shadowBlur(QImage& image, float radius, const QColor& color) { if (radius < 0) { return; } if (radius > 0) { stackBlur(image, radius); } // Correct the color and opacity of the shadow QPainter p(&image); p.setCompositionMode(QPainter::CompositionMode_SourceIn); p.fillRect(image.rect(), color); } namespace { /** Helper class for drawing frames for KPixmapModifier::applyFrame(). */ class TileSet { public: enum { LeftMargin = 3, TopMargin = 2, RightMargin = 3, BottomMargin = 4 }; enum Tile { TopLeftCorner = 0, TopSide, TopRightCorner, LeftSide, RightSide, BottomLeftCorner, BottomSide, BottomRightCorner, NumTiles }; TileSet() { QImage image(8 * 3, 8 * 3, QImage::Format_ARGB32_Premultiplied); QPainter p(&image); p.setCompositionMode(QPainter::CompositionMode_Source); p.fillRect(image.rect(), Qt::transparent); p.fillRect(image.rect().adjusted(3, 3, -3, -3), Qt::black); p.end(); shadowBlur(image, 3, Qt::black); QPixmap pixmap = QPixmap::fromImage(image); m_tiles[TopLeftCorner] = pixmap.copy(0, 0, 8, 8); m_tiles[TopSide] = pixmap.copy(8, 0, 8, 8); m_tiles[TopRightCorner] = pixmap.copy(16, 0, 8, 8); m_tiles[LeftSide] = pixmap.copy(0, 8, 8, 8); m_tiles[RightSide] = pixmap.copy(16, 8, 8, 8); m_tiles[BottomLeftCorner] = pixmap.copy(0, 16, 8, 8); m_tiles[BottomSide] = pixmap.copy(8, 16, 8, 8); m_tiles[BottomRightCorner] = pixmap.copy(16, 16, 8, 8); } void paint(QPainter* p, const QRect& r) { p->drawPixmap(r.topLeft(), m_tiles[TopLeftCorner]); if (r.width() - 16 > 0) { p->drawTiledPixmap(r.x() + 8, r.y(), r.width() - 16, 8, m_tiles[TopSide]); } p->drawPixmap(r.right() - 8 + 1, r.y(), m_tiles[TopRightCorner]); if (r.height() - 16 > 0) { p->drawTiledPixmap(r.x(), r.y() + 8, 8, r.height() - 16, m_tiles[LeftSide]); p->drawTiledPixmap(r.right() - 8 + 1, r.y() + 8, 8, r.height() - 16, m_tiles[RightSide]); } p->drawPixmap(r.x(), r.bottom() - 8 + 1, m_tiles[BottomLeftCorner]); if (r.width() - 16 > 0) { p->drawTiledPixmap(r.x() + 8, r.bottom() - 8 + 1, r.width() - 16, 8, m_tiles[BottomSide]); } p->drawPixmap(r.right() - 8 + 1, r.bottom() - 8 + 1, m_tiles[BottomRightCorner]); const QRect contentRect = r.adjusted(LeftMargin + 1, TopMargin + 1, -(RightMargin + 1), -(BottomMargin + 1)); p->fillRect(contentRect, Qt::transparent); } QPixmap m_tiles[NumTiles]; }; } void KPixmapModifier::scale(QPixmap& pixmap, const QSize& scaledSize) { if (scaledSize.isEmpty()) { pixmap = QPixmap(); return; } qreal dpr = pixmap.devicePixelRatio(); pixmap = pixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); pixmap.setDevicePixelRatio(dpr); } void KPixmapModifier::applyFrame(QPixmap& icon, const QSize& scaledSize) { static TileSet tileSet; qreal dpr = qApp->devicePixelRatio(); // Resize the icon to the maximum size minus the space required for the frame const QSize size(scaledSize.width() - TileSet::LeftMargin - TileSet::RightMargin, scaledSize.height() - TileSet::TopMargin - TileSet::BottomMargin); scale(icon, size * dpr); icon.setDevicePixelRatio(dpr); QPixmap framedIcon(icon.size().width() + (TileSet::LeftMargin + TileSet::RightMargin) * dpr, icon.size().height() + (TileSet::TopMargin + TileSet::BottomMargin) * dpr); framedIcon.setDevicePixelRatio(dpr); framedIcon.fill(Qt::transparent); QPainter painter; painter.begin(&framedIcon); painter.setCompositionMode(QPainter::CompositionMode_Source); tileSet.paint(&painter, QRect(QPoint(0,0), framedIcon.size() / dpr)); painter.setCompositionMode(QPainter::CompositionMode_SourceOver); painter.drawPixmap(TileSet::LeftMargin, TileSet::TopMargin, icon); icon = framedIcon; } QSize KPixmapModifier::sizeInsideFrame(const QSize& frameSize) { return QSize(frameSize.width() - TileSet::LeftMargin - TileSet::RightMargin, frameSize.height() - TileSet::TopMargin - TileSet::BottomMargin); } diff --git a/src/main.cpp b/src/main.cpp index 789a52996..e05c67d96 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,164 +1,163 @@ /*************************************************************************** * Copyright (C) 2006 by Peter Penz * * Copyright (C) 2006 by Stefan Monov * * Copyright (C) 2015 by Mathieu Tarral * * * * 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 "dolphin_version.h" #include "dolphinmainwindow.h" #include "dolphin_generalsettings.h" #include "dbusinterface.h" #include "global.h" #include "dolphindebug.h" #include #include #include #include -#include #include #include #include #ifndef Q_OS_WIN #include #endif #include extern "C" Q_DECL_EXPORT int kdemain(int argc, char **argv) { #ifndef Q_OS_WIN // Check whether we are running as root if (getuid() == 0) { std::cout << "Executing Dolphin as root is not possible." << std::endl; return EXIT_FAILURE; } #endif QApplication app(argc, argv); app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); app.setWindowIcon(QIcon::fromTheme(QStringLiteral("system-file-manager"), app.windowIcon())); KCrash::initialize(); Kdelibs4ConfigMigrator migrate(QStringLiteral("dolphin")); migrate.setConfigFiles(QStringList() << QStringLiteral("dolphinrc")); migrate.setUiFiles(QStringList() << QStringLiteral("dolphinpart.rc") << QStringLiteral("dolphinui.rc")); migrate.migrate(); KLocalizedString::setApplicationDomain("dolphin"); KAboutData aboutData(QStringLiteral("dolphin"), i18n("Dolphin"), QStringLiteral(DOLPHIN_VERSION_STRING), i18nc("@title", "File Manager"), KAboutLicense::GPL, i18nc("@info:credit", "(C) 2006-2016 Peter Penz, Frank Reininghaus, and Emmanuel Pescosta")); aboutData.setHomepage(QStringLiteral("http://dolphin.kde.org")); aboutData.addAuthor(i18nc("@info:credit", "Emmanuel Pescosta"), i18nc("@info:credit", "Maintainer (since 2014) and developer"), QStringLiteral("emmanuelpescosta099@gmail.com")); aboutData.addAuthor(i18nc("@info:credit", "Frank Reininghaus"), i18nc("@info:credit", "Maintainer (2012-2014) and developer"), QStringLiteral("frank78ac@googlemail.com")); aboutData.addAuthor(i18nc("@info:credit", "Peter Penz"), i18nc("@info:credit", "Maintainer and developer (2006-2012)"), QStringLiteral("peter.penz19@gmail.com")); aboutData.addAuthor(i18nc("@info:credit", "Sebastian Trüg"), i18nc("@info:credit", "Developer"), QStringLiteral("trueg@kde.org")); aboutData.addAuthor(i18nc("@info:credit", "David Faure"), i18nc("@info:credit", "Developer"), QStringLiteral("faure@kde.org")); aboutData.addAuthor(i18nc("@info:credit", "Aaron J. Seigo"), i18nc("@info:credit", "Developer"), QStringLiteral("aseigo@kde.org")); aboutData.addAuthor(i18nc("@info:credit", "Rafael Fernández López"), i18nc("@info:credit", "Developer"), QStringLiteral("ereslibre@kde.org")); aboutData.addAuthor(i18nc("@info:credit", "Kevin Ottens"), i18nc("@info:credit", "Developer"), QStringLiteral("ervin@kde.org")); aboutData.addAuthor(i18nc("@info:credit", "Holger Freyther"), i18nc("@info:credit", "Developer"), QStringLiteral("freyther@gmx.net")); aboutData.addAuthor(i18nc("@info:credit", "Max Blazejak"), i18nc("@info:credit", "Developer"), QStringLiteral("m43ksrocks@gmail.com")); aboutData.addAuthor(i18nc("@info:credit", "Michael Austin"), i18nc("@info:credit", "Documentation"), QStringLiteral("tuxedup@users.sourceforge.net")); KAboutData::setApplicationData(aboutData); KDBusService dolphinDBusService; DBusInterface interface; QCommandLineParser parser; parser.addVersionOption(); parser.addHelpOption(); aboutData.setupCommandLine(&parser); // command line options parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("select"), i18nc("@info:shell", "The files and folders passed as arguments " "will be selected."))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("split"), i18nc("@info:shell", "Dolphin will get started with a split view."))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("daemon"), i18nc("@info:shell", "Start Dolphin Daemon (only required for DBus Interface)"))); parser.addPositionalArgument(QStringLiteral("+[Url]"), i18nc("@info:shell", "Document to open")); parser.process(app); aboutData.processCommandLine(&parser); if (parser.isSet(QStringLiteral("daemon"))) { return app.exec(); } const QStringList args = parser.positionalArguments(); QList urls = Dolphin::validateUris(args); if (urls.isEmpty()) { // We need at least one URL to open Dolphin urls.append(Dolphin::homeUrl()); } const bool splitView = parser.isSet(QStringLiteral("split")) || GeneralSettings::splitView(); if (splitView && urls.size() < 2) { // Split view does only make sense if we have at least 2 URLs urls.append(urls.last()); } DolphinMainWindow* mainWindow = new DolphinMainWindow(); mainWindow->setAttribute(Qt::WA_DeleteOnClose); if (parser.isSet(QStringLiteral("select"))) { mainWindow->openFiles(urls, splitView); } else { mainWindow->openDirectories(urls, splitView); } mainWindow->show(); if (app.isSessionRestored()) { const QString className = KXmlGuiWindow::classNameOfToplevel(1); if (className == QLatin1String("DolphinMainWindow")) { mainWindow->restore(1); } else { qCWarning(DolphinDebug) << "Unknown class " << className << " in session saved data!"; } } return app.exec(); // krazy:exclude=crash; } diff --git a/src/panels/folders/folderspanel.cpp b/src/panels/folders/folderspanel.cpp index 4a476ad76..e455780e0 100644 --- a/src/panels/folders/folderspanel.cpp +++ b/src/panels/folders/folderspanel.cpp @@ -1,377 +1,373 @@ /*************************************************************************** * Copyright (C) 2006-2010 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 "folderspanel.h" #include "dolphin_folderspanelsettings.h" #include "dolphin_generalsettings.h" #include "treeviewcontextmenu.h" #include "foldersitemlistwidget.h" #include #include #include -#include #include #include #include -#include #include #include #include #include #include #include #include -#include #include #include #include #include #include -#include "dolphindebug.h" #include "global.h" FoldersPanel::FoldersPanel(QWidget* parent) : Panel(parent), m_updateCurrentItem(false), m_controller(nullptr), m_model(nullptr) { setLayoutDirection(Qt::LeftToRight); } FoldersPanel::~FoldersPanel() { FoldersPanelSettings::self()->save(); if (m_controller) { KItemListView* view = m_controller->view(); m_controller->setView(nullptr); delete view; } } void FoldersPanel::setShowHiddenFiles(bool show) { FoldersPanelSettings::setHiddenFilesShown(show); m_model->setShowHiddenFiles(show); } bool FoldersPanel::showHiddenFiles() const { return FoldersPanelSettings::hiddenFilesShown(); } void FoldersPanel::setLimitFoldersPanelToHome(bool enable) { FoldersPanelSettings::setLimitFoldersPanelToHome(enable); reloadTree(); } bool FoldersPanel::limitFoldersPanelToHome() const { return FoldersPanelSettings::limitFoldersPanelToHome(); } void FoldersPanel::setAutoScrolling(bool enable) { // TODO: Not supported yet in Dolphin 2.0 FoldersPanelSettings::setAutoScrolling(enable); } bool FoldersPanel::autoScrolling() const { return FoldersPanelSettings::autoScrolling(); } void FoldersPanel::rename(const KFileItem& item) { if (GeneralSettings::renameInline()) { const int index = m_model->index(item); m_controller->view()->editRole(index, "text"); } else { RenameDialog* dialog = new RenameDialog(this, KFileItemList() << item); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); dialog->raise(); dialog->activateWindow(); } } bool FoldersPanel::urlChanged() { if (!url().isValid() || url().scheme().contains(QStringLiteral("search"))) { // Skip results shown by a search, as possible identical // directory names are useless without parent-path information. return false; } if (m_controller) { loadTree(url()); } return true; } void FoldersPanel::reloadTree() { if (m_controller) { loadTree(url(), AllowJumpHome); } } void FoldersPanel::showEvent(QShowEvent* event) { if (event->spontaneous()) { Panel::showEvent(event); return; } if (!m_controller) { // Postpone the creating of the controller to the first show event. // This assures that no performance and memory overhead is given when the folders panel is not // used at all and stays invisible. KFileItemListView* view = new KFileItemListView(); view->setWidgetCreator(new KItemListWidgetCreator()); view->setSupportsItemExpanding(true); // Set the opacity to 0 initially. The opacity will be increased after the loading of the initial tree // has been finished in slotLoadingCompleted(). This prevents an unnecessary animation-mess when // opening the folders panel. view->setOpacity(0); connect(view, &KFileItemListView::roleEditingFinished, this, &FoldersPanel::slotRoleEditingFinished); m_model = new KFileItemModel(this); m_model->setShowDirectoriesOnly(true); m_model->setShowHiddenFiles(FoldersPanelSettings::hiddenFilesShown()); // Use a QueuedConnection to give the view the possibility to react first on the // finished loading. connect(m_model, &KFileItemModel::directoryLoadingCompleted, this, &FoldersPanel::slotLoadingCompleted, Qt::QueuedConnection); m_controller = new KItemListController(m_model, view, this); m_controller->setSelectionBehavior(KItemListController::SingleSelection); m_controller->setAutoActivationBehavior(KItemListController::ExpansionOnly); m_controller->setMouseDoubleClickAction(KItemListController::ActivateAndExpandItem); m_controller->setAutoActivationDelay(750); m_controller->setSingleClickActivationEnforced(true); connect(m_controller, &KItemListController::itemActivated, this, &FoldersPanel::slotItemActivated); connect(m_controller, &KItemListController::itemMiddleClicked, this, &FoldersPanel::slotItemMiddleClicked); connect(m_controller, &KItemListController::itemContextMenuRequested, this, &FoldersPanel::slotItemContextMenuRequested); connect(m_controller, &KItemListController::viewContextMenuRequested, this, &FoldersPanel::slotViewContextMenuRequested); connect(m_controller, &KItemListController::itemDropEvent, this, &FoldersPanel::slotItemDropEvent); KItemListContainer* container = new KItemListContainer(m_controller, this); container->setEnabledFrame(false); QVBoxLayout* layout = new QVBoxLayout(this); layout->setMargin(0); layout->addWidget(container); } loadTree(url()); Panel::showEvent(event); } void FoldersPanel::keyPressEvent(QKeyEvent* event) { const int key = event->key(); if ((key == Qt::Key_Enter) || (key == Qt::Key_Return)) { event->accept(); } else { Panel::keyPressEvent(event); } } void FoldersPanel::slotItemActivated(int index) { const KFileItem item = m_model->fileItem(index); if (!item.isNull()) { emit folderActivated(item.url()); } } void FoldersPanel::slotItemMiddleClicked(int index) { const KFileItem item = m_model->fileItem(index); if (!item.isNull()) { emit folderMiddleClicked(item.url()); } } void FoldersPanel::slotItemContextMenuRequested(int index, const QPointF& pos) { Q_UNUSED(pos); const KFileItem fileItem = m_model->fileItem(index); QPointer contextMenu = new TreeViewContextMenu(this, fileItem); contextMenu.data()->open(); if (contextMenu.data()) { delete contextMenu.data(); } } void FoldersPanel::slotViewContextMenuRequested(const QPointF& pos) { Q_UNUSED(pos); QPointer contextMenu = new TreeViewContextMenu(this, KFileItem()); contextMenu.data()->open(); if (contextMenu.data()) { delete contextMenu.data(); } } void FoldersPanel::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event) { if (index >= 0) { KFileItem destItem = m_model->fileItem(index); if (destItem.isNull()) { return; } QDropEvent dropEvent(event->pos().toPoint(), event->possibleActions(), event->mimeData(), event->buttons(), event->modifiers()); KIO::DropJob *job = DragAndDropHelper::dropUrls(destItem.mostLocalUrl(), &dropEvent, this); if (job) { connect(job, &KIO::DropJob::result, this, [this](KJob *job) { if (job->error()) emit errorMessage(job->errorString()); }); } } } void FoldersPanel::slotRoleEditingFinished(int index, const QByteArray& role, const QVariant& value) { if (role == "text") { const KFileItem item = m_model->fileItem(index); const QString newName = value.toString(); if (!newName.isEmpty() && newName != item.text() && newName != QLatin1String(".") && newName != QLatin1String("..")) { const QUrl oldUrl = item.url(); QUrl newUrl = oldUrl.adjusted(QUrl::RemoveFilename); newUrl.setPath(newUrl.path() + KIO::encodeFileName(newName)); KIO::Job* job = KIO::moveAs(oldUrl, newUrl); KJobWidgets::setWindow(job, this); KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, {oldUrl}, newUrl, job); job->uiDelegate()->setAutoErrorHandlingEnabled(true); } } } void FoldersPanel::slotLoadingCompleted() { if (m_controller->view()->opacity() == 0) { // The loading of the initial tree after opening the Folders panel // has been finished. Trigger the increasing of the opacity after // a short delay to give the view the chance to finish its internal // animations. // TODO: Check whether it makes sense to allow accessing the // view-internal delay for usecases like this. QTimer::singleShot(250, this, &FoldersPanel::startFadeInAnimation); } if (!m_updateCurrentItem) { return; } const int index = m_model->index(url()); updateCurrentItem(index); m_updateCurrentItem = false; } void FoldersPanel::startFadeInAnimation() { QPropertyAnimation* anim = new QPropertyAnimation(m_controller->view(), "opacity", this); anim->setStartValue(0); anim->setEndValue(1); anim->setEasingCurve(QEasingCurve::InOutQuad); anim->start(QAbstractAnimation::DeleteWhenStopped); anim->setDuration(200); } void FoldersPanel::loadTree(const QUrl& url, FoldersPanel::NavigationBehaviour navigationBehaviour) { Q_ASSERT(m_controller); m_updateCurrentItem = false; bool jumpHome = false; QUrl baseUrl; if (!url.isLocalFile()) { // Clear the path for non-local URLs and use it as base baseUrl = url; baseUrl.setPath(QStringLiteral("/")); } else if (Dolphin::homeUrl().isParentOf(url) || (Dolphin::homeUrl() == url)) { if (FoldersPanelSettings::limitFoldersPanelToHome() ) { baseUrl = Dolphin::homeUrl(); } else { // Use the root directory as base for local URLs (#150941) baseUrl = QUrl::fromLocalFile(QDir::rootPath()); } } else if (FoldersPanelSettings::limitFoldersPanelToHome() && navigationBehaviour == AllowJumpHome) { baseUrl = Dolphin::homeUrl(); jumpHome = true; } else { // Use the root directory as base for local URLs (#150941) baseUrl = QUrl::fromLocalFile(QDir::rootPath()); } if (m_model->directory() != baseUrl && !jumpHome) { m_updateCurrentItem = true; m_model->refreshDirectory(baseUrl); } const int index = m_model->index(url); if (jumpHome) { emit folderActivated(baseUrl); } else if (index >= 0) { updateCurrentItem(index); } else if (url == baseUrl) { // clear the selection when visiting the base url updateCurrentItem(-1); } else { m_updateCurrentItem = true; m_model->expandParentDirectories(url); // slotLoadingCompleted() will be invoked after the model has // expanded the url } } void FoldersPanel::updateCurrentItem(int index) { KItemListSelectionManager* selectionManager = m_controller->selectionManager(); selectionManager->setCurrentItem(index); selectionManager->clearSelection(); selectionManager->setSelected(index); m_controller->view()->scrollToItem(index); } diff --git a/src/panels/folders/treeviewcontextmenu.cpp b/src/panels/folders/treeviewcontextmenu.cpp index 132637f15..99802ed18 100644 --- a/src/panels/folders/treeviewcontextmenu.cpp +++ b/src/panels/folders/treeviewcontextmenu.cpp @@ -1,252 +1,250 @@ /*************************************************************************** * Copyright (C) 2006-2010 by Peter Penz * * Copyright (C) 2006 by Cvetoslav Ludmiloff * * * * 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 "treeviewcontextmenu.h" -#include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include "folderspanel.h" #include #include #include #include #include "global.h" TreeViewContextMenu::TreeViewContextMenu(FoldersPanel* parent, const KFileItem& fileInfo) : QObject(parent), m_parent(parent), m_fileItem(fileInfo) { } TreeViewContextMenu::~TreeViewContextMenu() { } void TreeViewContextMenu::open() { QMenu* popup = new QMenu(m_parent); if (!m_fileItem.isNull()) { KFileItemListProperties capabilities(KFileItemList() << m_fileItem); // insert 'Cut', 'Copy' and 'Paste' QAction* cutAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-cut")), i18nc("@action:inmenu", "Cut"), this); cutAction->setEnabled(capabilities.supportsMoving()); connect(cutAction, &QAction::triggered, this, &TreeViewContextMenu::cut); QAction* copyAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@action:inmenu", "Copy"), this); connect(copyAction, &QAction::triggered, this, &TreeViewContextMenu::copy); const QMimeData *mimeData = QApplication::clipboard()->mimeData(); bool canPaste; const QString text = KIO::pasteActionText(mimeData, &canPaste, m_fileItem); QAction* pasteAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-paste")), text, this); connect(pasteAction, &QAction::triggered, this, &TreeViewContextMenu::paste); pasteAction->setEnabled(canPaste); popup->addAction(cutAction); popup->addAction(copyAction); popup->addAction(pasteAction); popup->addSeparator(); // insert 'Rename' QAction* renameAction = new QAction(i18nc("@action:inmenu", "Rename..."), this); renameAction->setEnabled(capabilities.supportsMoving()); renameAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); connect(renameAction, &QAction::triggered, this, &TreeViewContextMenu::rename); popup->addAction(renameAction); // insert 'Move to Trash' and (optionally) 'Delete' KSharedConfig::Ptr globalConfig = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::IncludeGlobals); KConfigGroup configGroup(globalConfig, "KDE"); bool showDeleteCommand = configGroup.readEntry("ShowDeleteCommand", false); const QUrl url = m_fileItem.url(); if (url.isLocalFile()) { QAction* moveToTrashAction = new QAction(QIcon::fromTheme(QStringLiteral("user-trash")), i18nc("@action:inmenu", "Move to Trash"), this); const bool enableMoveToTrash = capabilities.isLocal() && capabilities.supportsMoving(); moveToTrashAction->setEnabled(enableMoveToTrash); connect(moveToTrashAction, &QAction::triggered, this, &TreeViewContextMenu::moveToTrash); popup->addAction(moveToTrashAction); } else { showDeleteCommand = true; } if (showDeleteCommand) { QAction* deleteAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@action:inmenu", "Delete"), this); deleteAction->setEnabled(capabilities.supportsDeleting()); connect(deleteAction, &QAction::triggered, this, &TreeViewContextMenu::deleteItem); popup->addAction(deleteAction); } popup->addSeparator(); } // insert 'Show Hidden Files' QAction* showHiddenFilesAction = new QAction(i18nc("@action:inmenu", "Show Hidden Files"), this); showHiddenFilesAction->setCheckable(true); showHiddenFilesAction->setChecked(m_parent->showHiddenFiles()); popup->addAction(showHiddenFilesAction); connect(showHiddenFilesAction, &QAction::toggled, this, &TreeViewContextMenu::setShowHiddenFiles); // insert 'Limit to Home Directory' const QUrl url = m_fileItem.url(); const bool enableLimitToHomeDirectory = url.isLocalFile(); QAction* limitFoldersPanelToHomeAction = new QAction(i18nc("@action:inmenu", "Limit to Home Directory"), this); limitFoldersPanelToHomeAction->setCheckable(true); limitFoldersPanelToHomeAction->setEnabled(enableLimitToHomeDirectory); limitFoldersPanelToHomeAction->setChecked(m_parent->limitFoldersPanelToHome()); popup->addAction(limitFoldersPanelToHomeAction); connect(limitFoldersPanelToHomeAction, &QAction::toggled, this, &TreeViewContextMenu::setLimitFoldersPanelToHome); // insert 'Automatic Scrolling' QAction* autoScrollingAction = new QAction(i18nc("@action:inmenu", "Automatic Scrolling"), this); autoScrollingAction->setCheckable(true); autoScrollingAction->setChecked(m_parent->autoScrolling()); // TODO: Temporary disabled. Horizontal autoscrolling will be implemented later either // in KItemViews or manually as part of the FoldersPanel //popup->addAction(autoScrollingAction); connect(autoScrollingAction, &QAction::toggled, this, &TreeViewContextMenu::setAutoScrolling); if (!m_fileItem.isNull()) { // insert 'Properties' entry QAction* propertiesAction = new QAction(i18nc("@action:inmenu", "Properties"), this); propertiesAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); connect(propertiesAction, &QAction::triggered, this, &TreeViewContextMenu::showProperties); popup->addAction(propertiesAction); } QList customActions = m_parent->customContextMenuActions(); if (!customActions.isEmpty()) { popup->addSeparator(); foreach (QAction* action, customActions) { popup->addAction(action); } } QPointer popupPtr = popup; popup->exec(QCursor::pos()); if (popupPtr.data()) { popupPtr.data()->deleteLater(); } } void TreeViewContextMenu::populateMimeData(QMimeData* mimeData, bool cut) { QList kdeUrls; kdeUrls.append(m_fileItem.url()); QList mostLocalUrls; bool dummy; mostLocalUrls.append(m_fileItem.mostLocalUrl(dummy)); KIO::setClipboardDataCut(mimeData, cut); KUrlMimeData::setUrls(kdeUrls, mostLocalUrls, mimeData); } void TreeViewContextMenu::cut() { QMimeData* mimeData = new QMimeData(); populateMimeData(mimeData, true); QApplication::clipboard()->setMimeData(mimeData); } void TreeViewContextMenu::copy() { QMimeData* mimeData = new QMimeData(); populateMimeData(mimeData, false); QApplication::clipboard()->setMimeData(mimeData); } void TreeViewContextMenu::paste() { KIO::PasteJob *job = KIO::paste(QApplication::clipboard()->mimeData(), m_fileItem.url()); KJobWidgets::setWindow(job, m_parent); } void TreeViewContextMenu::rename() { m_parent->rename(m_fileItem); } void TreeViewContextMenu::moveToTrash() { const QList list{m_fileItem.url()}; KIO::JobUiDelegate uiDelegate; uiDelegate.setWindow(m_parent); if (uiDelegate.askDeleteConfirmation(list, KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation)) { KIO::Job* job = KIO::trash(list); KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Trash, list, QUrl(QStringLiteral("trash:/")), job); KJobWidgets::setWindow(job, m_parent); job->uiDelegate()->setAutoErrorHandlingEnabled(true); } } void TreeViewContextMenu::deleteItem() { const QList list{m_fileItem.url()}; KIO::JobUiDelegate uiDelegate; uiDelegate.setWindow(m_parent); if (uiDelegate.askDeleteConfirmation(list, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::DefaultConfirmation)) { KIO::Job* job = KIO::del(list); KJobWidgets::setWindow(job, m_parent); job->uiDelegate()->setAutoErrorHandlingEnabled(true); } } void TreeViewContextMenu::showProperties() { KPropertiesDialog* dialog = new KPropertiesDialog(m_fileItem.url(), m_parent); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); } void TreeViewContextMenu::setShowHiddenFiles(bool show) { m_parent->setShowHiddenFiles(show); } void TreeViewContextMenu::setLimitFoldersPanelToHome(bool enable) { m_parent->setLimitFoldersPanelToHome(enable); } void TreeViewContextMenu::setAutoScrolling(bool enable) { m_parent->setAutoScrolling(enable); } diff --git a/src/panels/information/informationpanelcontent.cpp b/src/panels/information/informationpanelcontent.cpp index 157568269..7507d54fb 100644 --- a/src/panels/information/informationpanelcontent.cpp +++ b/src/panels/information/informationpanelcontent.cpp @@ -1,431 +1,424 @@ /*************************************************************************** * Copyright (C) 2009 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 "informationpanelcontent.h" -#include #include #include #include #include #include #include #include #include #include #include #include #ifndef HAVE_BALOO #include #else #include #endif #include #include #include #include -#include -#include #include -#include -#include #include #include -#include #include #include -#include #include #include "dolphin_informationpanelsettings.h" #include "filemetadataconfigurationdialog.h" #include "phononwidget.h" #include "pixmapviewer.h" InformationPanelContent::InformationPanelContent(QWidget* parent) : QWidget(parent), m_item(), m_previewJob(nullptr), m_outdatedPreviewTimer(nullptr), m_preview(nullptr), m_phononWidget(nullptr), m_nameLabel(nullptr), m_metaDataWidget(nullptr), m_metaDataArea(nullptr), m_placesItemModel(nullptr) { parent->installEventFilter(this); // Initialize timer for disabling an outdated preview with a small // delay. This prevents flickering if the new preview can be generated // within a very small timeframe. m_outdatedPreviewTimer = new QTimer(this); m_outdatedPreviewTimer->setInterval(300); m_outdatedPreviewTimer->setSingleShot(true); connect(m_outdatedPreviewTimer, &QTimer::timeout, this, &InformationPanelContent::markOutdatedPreview); QVBoxLayout* layout = new QVBoxLayout(this); // preview const int minPreviewWidth = KIconLoader::SizeEnormous + KIconLoader::SizeMedium; m_preview = new PixmapViewer(parent); m_preview->setMinimumWidth(minPreviewWidth); m_preview->setMinimumHeight(KIconLoader::SizeEnormous); m_phononWidget = new PhononWidget(parent); m_phononWidget->hide(); m_phononWidget->setMinimumWidth(minPreviewWidth); connect(m_phononWidget, &PhononWidget::hasVideoChanged, this, &InformationPanelContent::slotHasVideoChanged); // name m_nameLabel = new QLabel(parent); QFont font = m_nameLabel->font(); font.setBold(true); m_nameLabel->setFont(font); m_nameLabel->setTextFormat(Qt::PlainText); m_nameLabel->setAlignment(Qt::AlignHCenter); m_nameLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); const bool previewsShown = InformationPanelSettings::previewsShown(); m_preview->setVisible(previewsShown); #ifndef HAVE_BALOO m_metaDataWidget = new KFileMetaDataWidget(parent); connect(m_metaDataWidget, &KFileMetaDataWidget::urlActivated, this, &InformationPanelContent::urlActivated); #else m_metaDataWidget = new Baloo::FileMetaDataWidget(parent); connect(m_metaDataWidget, &Baloo::FileMetaDataWidget::urlActivated, this, &InformationPanelContent::urlActivated); #endif m_metaDataWidget->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); m_metaDataWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); // Encapsulate the MetaDataWidget inside a container that has a dummy widget // at the bottom. This prevents that the meta data widget gets vertically stretched // in the case where the height of m_metaDataArea > m_metaDataWidget. QWidget* metaDataWidgetContainer = new QWidget(parent); QVBoxLayout* containerLayout = new QVBoxLayout(metaDataWidgetContainer); containerLayout->setContentsMargins(0, 0, 0, 0); containerLayout->setSpacing(0); containerLayout->addWidget(m_metaDataWidget); containerLayout->addStretch(); m_metaDataArea = new QScrollArea(parent); m_metaDataArea->setWidget(metaDataWidgetContainer); m_metaDataArea->setWidgetResizable(true); m_metaDataArea->setFrameShape(QFrame::NoFrame); QWidget* viewport = m_metaDataArea->viewport(); viewport->installEventFilter(this); layout->addWidget(m_preview); layout->addWidget(m_phononWidget); layout->addWidget(m_nameLabel); layout->addWidget(new KSeparator()); layout->addWidget(m_metaDataArea); m_placesItemModel = new PlacesItemModel(this); } InformationPanelContent::~InformationPanelContent() { InformationPanelSettings::self()->save(); } void InformationPanelContent::showItem(const KFileItem& item) { // If there is a preview job, kill it to prevent that we have jobs for // multiple items running, and thus a race condition (bug 250787). if (m_previewJob) { m_previewJob->kill(); } const QUrl itemUrl = item.url(); const bool isSearchUrl = itemUrl.scheme().contains(QStringLiteral("search")) && item.localPath().isEmpty(); if (!applyPlace(itemUrl)) { setNameLabelText(item.text()); if (isSearchUrl) { // in the case of a search-URL the URL is not readable for humans // (at least not useful to show in the Information Panel) m_preview->setPixmap( QIcon::fromTheme(QStringLiteral("nepomuk")).pixmap(KIconLoader::SizeEnormous, KIconLoader::SizeEnormous) ); } else { // try to get a preview pixmap from the item... // Mark the currently shown preview as outdated. This is done // with a small delay to prevent a flickering when the next preview // can be shown within a short timeframe. This timer is not started // for directories, as directory previews might fail and return the // same icon. if (!item.isDir()) { m_outdatedPreviewTimer->start(); } m_previewJob = new KIO::PreviewJob(KFileItemList() << item, QSize(m_preview->width(), m_preview->height())); m_previewJob->setScaleType(KIO::PreviewJob::Unscaled); m_previewJob->setIgnoreMaximumSize(item.isLocalFile()); if (m_previewJob->uiDelegate()) { KJobWidgets::setWindow(m_previewJob, this); } connect(m_previewJob.data(), &KIO::PreviewJob::gotPreview, this, &InformationPanelContent::showPreview); connect(m_previewJob.data(), &KIO::PreviewJob::failed, this, &InformationPanelContent::showIcon); } } if (m_metaDataWidget) { m_metaDataWidget->show(); m_metaDataWidget->setItems(KFileItemList() << item); } if (InformationPanelSettings::previewsShown()) { const QString mimeType = item.mimetype(); const bool usePhonon = mimeType.startsWith(QLatin1String("audio/")) || mimeType.startsWith(QLatin1String("video/")); if (usePhonon) { m_phononWidget->show(); m_phononWidget->setUrl(item.targetUrl()); if (m_preview->isVisible()) { m_phononWidget->setVideoSize(m_preview->size()); } } else { m_phononWidget->hide(); m_preview->setVisible(true); } } else { m_phononWidget->hide(); } m_item = item; } void InformationPanelContent::showItems(const KFileItemList& items) { // If there is a preview job, kill it to prevent that we have jobs for // multiple items running, and thus a race condition (bug 250787). if (m_previewJob) { m_previewJob->kill(); } m_preview->setPixmap( QIcon::fromTheme(QStringLiteral("dialog-information")).pixmap(KIconLoader::SizeEnormous, KIconLoader::SizeEnormous) ); setNameLabelText(i18ncp("@label", "%1 item selected", "%1 items selected", items.count())); if (m_metaDataWidget) { m_metaDataWidget->setItems(items); } m_phononWidget->hide(); m_item = KFileItem(); } bool InformationPanelContent::eventFilter(QObject* obj, QEvent* event) { switch (event->type()) { case QEvent::Resize: { QResizeEvent* resizeEvent = static_cast(event); if (obj == m_metaDataArea->viewport()) { // The size of the meta text area has changed. Adjust the fixed // width in a way that no horizontal scrollbar needs to be shown. m_metaDataWidget->setFixedWidth(resizeEvent->size().width()); } else if (obj == parent()) { adjustWidgetSizes(resizeEvent->size().width()); } break; } case QEvent::Polish: adjustWidgetSizes(parentWidget()->width()); break; case QEvent::FontChange: m_metaDataWidget->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); break; default: break; } return QWidget::eventFilter(obj, event); } void InformationPanelContent::configureSettings(const QList& customContextMenuActions) { QMenu popup(this); QAction* previewAction = popup.addAction(i18nc("@action:inmenu", "Preview")); previewAction->setIcon(QIcon::fromTheme(QStringLiteral("view-preview"))); previewAction->setCheckable(true); previewAction->setChecked(InformationPanelSettings::previewsShown()); QAction* configureAction = popup.addAction(i18nc("@action:inmenu", "Configure...")); configureAction->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); popup.addSeparator(); foreach (QAction* action, customContextMenuActions) { popup.addAction(action); } // Open the popup and adjust the settings for the // selected action. QAction* action = popup.exec(QCursor::pos()); if (!action) { return; } const bool isChecked = action->isChecked(); if (action == previewAction) { m_preview->setVisible(isChecked); InformationPanelSettings::setPreviewsShown(isChecked); } else if (action == configureAction) { FileMetaDataConfigurationDialog* dialog = new FileMetaDataConfigurationDialog(this); dialog->setDescription(i18nc("@label::textbox", "Select which data should be shown in the information panel:")); dialog->setItems(m_metaDataWidget->items()); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); connect(dialog, &FileMetaDataConfigurationDialog::destroyed, this, &InformationPanelContent::refreshMetaData); } } void InformationPanelContent::showIcon(const KFileItem& item) { m_outdatedPreviewTimer->stop(); if (!applyPlace(item.targetUrl())) { QPixmap pixmap = QIcon::fromTheme(item.iconName()).pixmap(KIconLoader::SizeEnormous, KIconLoader::SizeEnormous); KIconLoader::global()->drawOverlays(item.overlays(), pixmap, KIconLoader::Desktop); m_preview->setPixmap(pixmap); } } void InformationPanelContent::showPreview(const KFileItem& item, const QPixmap& pixmap) { m_outdatedPreviewTimer->stop(); Q_UNUSED(item); QPixmap p = pixmap; KIconLoader::global()->drawOverlays(item.overlays(), p, KIconLoader::Desktop); m_preview->setPixmap(p); } void InformationPanelContent::markOutdatedPreview() { KIconEffect *iconEffect = KIconLoader::global()->iconEffect(); QPixmap disabledPixmap = iconEffect->apply(m_preview->pixmap(), KIconLoader::Desktop, KIconLoader::DisabledState); m_preview->setPixmap(disabledPixmap); } void InformationPanelContent::slotHasVideoChanged(bool hasVideo) { m_preview->setVisible(!hasVideo); } void InformationPanelContent::refreshMetaData() { if (!m_item.isNull()) { showItem(m_item); } } bool InformationPanelContent::applyPlace(const QUrl& url) { const int count = m_placesItemModel->count(); for (int i = 0; i < count; ++i) { const PlacesItem* item = m_placesItemModel->placesItem(i); if (item->url().matches(url, QUrl::StripTrailingSlash)) { setNameLabelText(item->text()); m_preview->setPixmap(QIcon::fromTheme(item->icon()).pixmap(128, 128)); return true; } } return false; } void InformationPanelContent::setNameLabelText(const QString& text) { QTextOption textOption; textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); const QString processedText = Qt::mightBeRichText(text) ? text : KStringHandler::preProcessWrap(text); QTextLayout textLayout(processedText); textLayout.setFont(m_nameLabel->font()); textLayout.setTextOption(textOption); QString wrappedText; wrappedText.reserve(processedText.length()); // wrap the text to fit into the width of m_nameLabel textLayout.beginLayout(); QTextLine line = textLayout.createLine(); while (line.isValid()) { line.setLineWidth(m_nameLabel->width()); wrappedText += processedText.midRef(line.textStart(), line.textLength()); line = textLayout.createLine(); if (line.isValid()) { wrappedText += QChar::LineSeparator; } } textLayout.endLayout(); m_nameLabel->setText(wrappedText); } void InformationPanelContent::adjustWidgetSizes(int width) { // If the text inside the name label or the info label cannot // get wrapped, then the maximum width of the label is increased // so that the width of the information panel gets increased. // To prevent this, the maximum width is adjusted to // the current width of the panel. const int maxWidth = width - style()->layoutSpacing(QSizePolicy::DefaultType, QSizePolicy::DefaultType, Qt::Horizontal) * 4; m_nameLabel->setMaximumWidth(maxWidth); // The metadata widget also contains a text widget which may return // a large preferred width. if (m_metaDataWidget) { m_metaDataWidget->setMaximumWidth(maxWidth); } // try to increase the preview as large as possible m_preview->setSizeHint(QSize(maxWidth, maxWidth)); if (m_phononWidget->isVisible()) { // assure that the size of the video player is the same as the preview size m_phononWidget->setVideoSize(QSize(maxWidth, maxWidth)); } } diff --git a/src/panels/information/phononwidget.cpp b/src/panels/information/phononwidget.cpp index a71a77897..b8d3d6ef6 100644 --- a/src/panels/information/phononwidget.cpp +++ b/src/panels/information/phononwidget.cpp @@ -1,237 +1,232 @@ /* This file is part of the KDE project Copyright (C) 2007 Matthias Kretz 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 "phononwidget.h" #include -#include #include #include #include #include -#include #include #include -#include -#include #include -#include #include class EmbeddedVideoPlayer : public Phonon::VideoWidget { Q_OBJECT public: EmbeddedVideoPlayer(QWidget *parent = nullptr) : Phonon::VideoWidget(parent) { } void setSizeHint(const QSize& size) { m_sizeHint = size; updateGeometry(); } QSize sizeHint() const override { return m_sizeHint.isValid() ? m_sizeHint : Phonon::VideoWidget::sizeHint(); } private: QSize m_sizeHint; }; PhononWidget::PhononWidget(QWidget *parent) : QWidget(parent), m_url(), m_playButton(nullptr), m_stopButton(nullptr), m_topLayout(nullptr), m_media(nullptr), m_seekSlider(nullptr), m_audioOutput(nullptr), m_videoPlayer(nullptr) { } void PhononWidget::setUrl(const QUrl &url) { if (m_url != url) { stop(); // emits playingStopped() signal m_url = url; } } QUrl PhononWidget::url() const { return m_url; } void PhononWidget::setVideoSize(const QSize& size) { if (m_videoSize != size) { m_videoSize = size; applyVideoSize(); } } QSize PhononWidget::videoSize() const { return m_videoSize; } void PhononWidget::showEvent(QShowEvent *event) { if (event->spontaneous()) { QWidget::showEvent(event); return; } if (!m_topLayout) { m_topLayout = new QVBoxLayout(this); m_topLayout->setMargin(0); QHBoxLayout *controlsLayout = new QHBoxLayout(this); controlsLayout->setMargin(0); controlsLayout->setSpacing(0); m_playButton = new QToolButton(this); m_stopButton = new QToolButton(this); m_seekSlider = new Phonon::SeekSlider(this); controlsLayout->addWidget(m_playButton); controlsLayout->addWidget(m_stopButton); controlsLayout->addWidget(m_seekSlider); m_topLayout->addLayout(controlsLayout); const int smallIconSize = IconSize(KIconLoader::Small); const QSize buttonSize(smallIconSize, smallIconSize); m_playButton->setToolTip(i18n("play")); m_playButton->setIconSize(buttonSize); m_playButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); m_playButton->setAutoRaise(true); connect(m_playButton, &QToolButton::clicked, this, &PhononWidget::play); m_stopButton->setToolTip(i18n("stop")); m_stopButton->setIconSize(buttonSize); m_stopButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-stop"))); m_stopButton->setAutoRaise(true); m_stopButton->hide(); connect(m_stopButton, &QToolButton::clicked, this, &PhononWidget::stop); m_seekSlider->setIconVisible(false); // Creating an audio player or video player instance might take up to // 2 seconds when doing it the first time. To prevent that the user // interface gets noticeable blocked, the creation is delayed until // the play button has been pressed (see PhononWidget::play()). } } void PhononWidget::hideEvent(QHideEvent *event) { QWidget::hideEvent(event); if (!event->spontaneous()) { stop(); } } void PhononWidget::stateChanged(Phonon::State newstate) { setUpdatesEnabled(false); switch (newstate) { case Phonon::PlayingState: case Phonon::BufferingState: m_stopButton->show(); m_playButton->hide(); break; case Phonon::StoppedState: if (m_videoPlayer) { m_videoPlayer->hide(); } emit hasVideoChanged(false); // fall through default: m_stopButton->hide(); m_playButton->show(); break; } setUpdatesEnabled(true); } void PhononWidget::play() { if (!m_media) { m_media = new Phonon::MediaObject(this); connect(m_media, &Phonon::MediaObject::stateChanged, this, &PhononWidget::stateChanged); connect(m_media, &Phonon::MediaObject::hasVideoChanged, this, &PhononWidget::slotHasVideoChanged); m_seekSlider->setMediaObject(m_media); } if (!m_videoPlayer) { m_videoPlayer = new EmbeddedVideoPlayer(this); m_topLayout->insertWidget(0, m_videoPlayer); Phonon::createPath(m_media, m_videoPlayer); applyVideoSize(); } if (!m_audioOutput) { m_audioOutput = new Phonon::AudioOutput(Phonon::MusicCategory, this); Phonon::createPath(m_media, m_audioOutput); } emit hasVideoChanged(false); m_media->setCurrentSource(m_url); m_media->hasVideo(); m_media->play(); } void PhononWidget::stop() { if (m_media) { m_media->stop(); } } void PhononWidget::slotHasVideoChanged(bool hasVideo) { emit hasVideoChanged(hasVideo); if (hasVideo) { m_videoPlayer->show(); } } void PhononWidget::applyVideoSize() { if ((m_videoPlayer) && m_videoSize.isValid()) { m_videoPlayer->setSizeHint(m_videoSize); } } #include "phononwidget.moc" diff --git a/src/panels/information/pixmapviewer.cpp b/src/panels/information/pixmapviewer.cpp index 0874f2b39..24a629569 100644 --- a/src/panels/information/pixmapviewer.cpp +++ b/src/panels/information/pixmapviewer.cpp @@ -1,126 +1,125 @@ /*************************************************************************** * Copyright (C) 2006 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 "pixmapviewer.h" #include #include -#include #include PixmapViewer::PixmapViewer(QWidget* parent, Transition transition) : QWidget(parent), m_transition(transition), m_animationStep(0), m_sizeHint() { setMinimumWidth(KIconLoader::SizeEnormous); setMinimumHeight(KIconLoader::SizeEnormous); m_animation.setDuration(150); m_animation.setCurveShape(QTimeLine::LinearCurve); if (m_transition != NoTransition) { connect(&m_animation, &QTimeLine::valueChanged, this, static_cast(&PixmapViewer::update)); connect(&m_animation, &QTimeLine::finished, this, &PixmapViewer::checkPendingPixmaps); } } PixmapViewer::~PixmapViewer() { } void PixmapViewer::setPixmap(const QPixmap& pixmap) { if (pixmap.isNull()) { return; } if ((m_transition != NoTransition) && (m_animation.state() == QTimeLine::Running)) { m_pendingPixmaps.enqueue(pixmap); if (m_pendingPixmaps.count() > 5) { // don't queue more than 5 pixmaps m_pendingPixmaps.takeFirst(); } return; } m_oldPixmap = m_pixmap.isNull() ? pixmap : m_pixmap; m_pixmap = pixmap; update(); const bool animate = (m_transition != NoTransition) && (m_pixmap.size() != m_oldPixmap.size()); if (animate) { m_animation.start(); } } void PixmapViewer::setSizeHint(const QSize& size) { m_sizeHint = size; updateGeometry(); } QSize PixmapViewer::sizeHint() const { return m_sizeHint; } void PixmapViewer::paintEvent(QPaintEvent* event) { QWidget::paintEvent(event); QPainter painter(this); if (m_transition != NoTransition) { const float value = m_animation.currentValue(); const int scaledWidth = static_cast((m_oldPixmap.width() * (1.0 - value)) + (m_pixmap.width() * value)); const int scaledHeight = static_cast((m_oldPixmap.height() * (1.0 - value)) + (m_pixmap.height() * value)); const bool useOldPixmap = (m_transition == SizeTransition) && (m_oldPixmap.width() > m_pixmap.width()); const QPixmap& largePixmap = useOldPixmap ? m_oldPixmap : m_pixmap; if (!largePixmap.isNull()) { const QPixmap scaledPixmap = largePixmap.scaled(scaledWidth, scaledHeight, Qt::IgnoreAspectRatio, Qt::FastTransformation); style()->drawItemPixmap(&painter, rect(), Qt::AlignCenter, scaledPixmap); } } else { style()->drawItemPixmap(&painter, rect(), Qt::AlignCenter, m_pixmap); } } void PixmapViewer::checkPendingPixmaps() { if (m_pendingPixmaps.count() > 0) { QPixmap pixmap = m_pendingPixmaps.dequeue(); m_oldPixmap = m_pixmap.isNull() ? pixmap : m_pixmap; m_pixmap = pixmap; update(); m_animation.start(); } else { m_oldPixmap = m_pixmap; } } diff --git a/src/panels/panel.cpp b/src/panels/panel.cpp index 348191c98..30cff509d 100644 --- a/src/panels/panel.cpp +++ b/src/panels/panel.cpp @@ -1,78 +1,77 @@ /*************************************************************************** * Copyright (C) 2006 by Cvetoslav Ludmiloff * * Copyright (C) 2006-2010 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 "panel.h" -#include Panel::Panel(QWidget* parent) : QWidget(parent), m_url(), m_customContextMenuActions() { } Panel::~Panel() { } QUrl Panel::url() const { return m_url; } void Panel::setCustomContextMenuActions(const QList& actions) { m_customContextMenuActions = actions; } QList Panel::customContextMenuActions() const { return m_customContextMenuActions; } QSize Panel::sizeHint() const { // The size hint will be requested already when starting Dolphin even // if the panel is invisible. For performance reasons most panels delay // the creation and initialization of widgets until a showEvent() is called. // Because of this the size-hint of the embedded widgets cannot be used // and a default size is provided: return QSize(180, 180); } void Panel::setUrl(const QUrl& url) { if (url.matches(m_url, QUrl::StripTrailingSlash)) { return; } const QUrl oldUrl = m_url; m_url = url; const bool accepted = urlChanged(); if (!accepted) { m_url = oldUrl; } } void Panel::readSettings() { } diff --git a/src/panels/places/placesitem.cpp b/src/panels/places/placesitem.cpp index 3c6023ba8..a1e4611a5 100644 --- a/src/panels/places/placesitem.cpp +++ b/src/panels/places/placesitem.cpp @@ -1,291 +1,289 @@ /*************************************************************************** * Copyright (C) 2012 by Peter Penz * * * * Based on KFilePlacesItem from kdelibs: * * Copyright (C) 2007 Kevin Ottens * * * * 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 "placesitem.h" -#include #include "dolphindebug.h" #include #include #include "placesitemsignalhandler.h" -#include #include PlacesItem::PlacesItem(const KBookmark& bookmark, PlacesItem* parent) : KStandardItem(parent), m_device(), m_access(), m_volume(), m_disc(), m_mtp(), m_signalHandler(nullptr), m_trashDirLister(nullptr), m_bookmark() { m_signalHandler = new PlacesItemSignalHandler(this); setBookmark(bookmark); } PlacesItem::~PlacesItem() { delete m_signalHandler; delete m_trashDirLister; } void PlacesItem::setUrl(const QUrl &url) { // The default check in KStandardItem::setDataValue() // for equal values does not work with a custom value // like QUrl. Hence do a manual check to prevent that // setting an equal URL results in an itemsChanged() // signal. if (dataValue("url").toUrl() != url) { delete m_trashDirLister; if (url.scheme() == QLatin1String("trash")) { // The trash icon must always be updated dependent on whether // the trash is empty or not. We use a KDirLister that automatically // watches for changes if the number of items has been changed. // The update of the icon is handled in onTrashDirListerCompleted(). m_trashDirLister = new KDirLister(); m_trashDirLister->setAutoErrorHandlingEnabled(false, nullptr); m_trashDirLister->setDelayedMimeTypes(true); QObject::connect(m_trashDirLister.data(), static_cast(&KDirLister::completed), m_signalHandler.data(), &PlacesItemSignalHandler::onTrashDirListerCompleted); m_trashDirLister->openUrl(url); } setDataValue("url", url); } } QUrl PlacesItem::url() const { return dataValue("url").toUrl(); } void PlacesItem::setUdi(const QString& udi) { setDataValue("udi", udi); } QString PlacesItem::udi() const { return dataValue("udi").toString(); } void PlacesItem::setHidden(bool hidden) { setDataValue("isHidden", hidden); } bool PlacesItem::isHidden() const { return dataValue("isHidden").toBool(); } bool PlacesItem::isGroupHidden() const { return dataValue("isGroupHidden").toBool(); } void PlacesItem::setGroupHidden(bool hidden) { setDataValue("isGroupHidden", hidden); } void PlacesItem::setSystemItem(bool isSystemItem) { setDataValue("isSystemItem", isSystemItem); } bool PlacesItem::isSystemItem() const { return dataValue("isSystemItem").toBool(); } Solid::Device PlacesItem::device() const { return m_device; } void PlacesItem::setBookmark(const KBookmark& bookmark) { const bool bookmarkDataChanged = !(bookmark == m_bookmark); // bookmark object must be updated to keep in sync with source model m_bookmark = bookmark; if (!bookmarkDataChanged) { return; } delete m_access; delete m_volume; delete m_disc; delete m_mtp; const QString udi = bookmark.metaDataItem(QStringLiteral("UDI")); if (udi.isEmpty()) { setIcon(bookmark.icon()); setText(i18nc("KFile System Bookmarks", bookmark.text().toUtf8().constData())); setUrl(bookmark.url()); setSystemItem(bookmark.metaDataItem(QStringLiteral("isSystemItem")) == QLatin1String("true")); } else { initializeDevice(udi); } setHidden(bookmark.metaDataItem(QStringLiteral("IsHidden")) == QLatin1String("true")); } KBookmark PlacesItem::bookmark() const { return m_bookmark; } bool PlacesItem::storageSetupNeeded() const { return m_access ? !m_access->isAccessible() : false; } bool PlacesItem::isSearchOrTimelineUrl() const { const QString urlScheme = url().scheme(); return (urlScheme.contains("search") || urlScheme.contains("timeline")); } void PlacesItem::onDataValueChanged(const QByteArray& role, const QVariant& current, const QVariant& previous) { Q_UNUSED(current); Q_UNUSED(previous); if (!m_bookmark.isNull()) { updateBookmarkForRole(role); } } void PlacesItem::onDataChanged(const QHash& current, const QHash& previous) { Q_UNUSED(previous); if (!m_bookmark.isNull()) { QHashIterator it(current); while (it.hasNext()) { it.next(); updateBookmarkForRole(it.key()); } } } void PlacesItem::initializeDevice(const QString& udi) { m_device = Solid::Device(udi); if (!m_device.isValid()) { return; } m_access = m_device.as(); m_volume = m_device.as(); m_disc = m_device.as(); m_mtp = m_device.as(); setText(m_device.description()); setIcon(m_device.icon()); setIconOverlays(m_device.emblems()); setUdi(udi); if (m_access) { setUrl(QUrl::fromLocalFile(m_access->filePath())); QObject::connect(m_access.data(), &Solid::StorageAccess::accessibilityChanged, m_signalHandler.data(), &PlacesItemSignalHandler::onAccessibilityChanged); QObject::connect(m_access.data(), &Solid::StorageAccess::teardownRequested, m_signalHandler.data(), &PlacesItemSignalHandler::onTearDownRequested); } else if (m_disc && (m_disc->availableContent() & Solid::OpticalDisc::Audio) != 0) { Solid::Block *block = m_device.as(); if (block) { const QString device = block->device(); setUrl(QUrl(QStringLiteral("audiocd:/?device=%1").arg(device))); } else { setUrl(QUrl(QStringLiteral("audiocd:/"))); } } else if (m_mtp) { setUrl(QUrl(QStringLiteral("mtp:udi=%1").arg(m_device.udi()))); } } void PlacesItem::onAccessibilityChanged() { setIconOverlays(m_device.emblems()); setUrl(QUrl::fromLocalFile(m_access->filePath())); } void PlacesItem::onTrashDirListerCompleted() { Q_ASSERT(url().scheme() == QLatin1String("trash")); const bool isTrashEmpty = m_trashDirLister->items().isEmpty(); setIcon(isTrashEmpty ? QStringLiteral("user-trash") : QStringLiteral("user-trash-full")); } void PlacesItem::updateBookmarkForRole(const QByteArray& role) { Q_ASSERT(!m_bookmark.isNull()); if (role == "iconName") { m_bookmark.setIcon(icon()); } else if (role == "text") { // Only store the text in the KBookmark if it is not the translation of // the current text. This makes sure that the text is re-translated if // the user chooses another language, or the translation itself changes. // // NOTE: It is important to use "KFile System Bookmarks" as context // (see PlacesItemModel::createSystemBookmarks()). if (text() != i18nc("KFile System Bookmarks", m_bookmark.text().toUtf8().data())) { m_bookmark.setFullText(text()); } } else if (role == "url") { m_bookmark.setUrl(url()); } else if (role == "udi") { m_bookmark.setMetaDataItem(QStringLiteral("UDI"), udi()); } else if (role == "isSystemItem") { m_bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), isSystemItem() ? QStringLiteral("true") : QStringLiteral("false")); } else if (role == "isHidden") { m_bookmark.setMetaDataItem(QStringLiteral("IsHidden"), isHidden() ? QStringLiteral("true") : QStringLiteral("false")); } } QString PlacesItem::generateNewId() { // The ID-generation must be different as done in KFilePlacesItem from kdelibs // to prevent identical IDs, because 'count' is of course not shared. We append a // " (V2)" to indicate that the ID has been generated by // a new version of the places view. static int count = 0; return QString::number(QDateTime::currentDateTimeUtc().toTime_t()) + '/' + QString::number(count++) + " (V2)"; } PlacesItemSignalHandler *PlacesItem::signalHandler() const { return m_signalHandler.data(); } diff --git a/src/panels/places/placesitemeditdialog.cpp b/src/panels/places/placesitemeditdialog.cpp index 4a56ce908..9722eb586 100644 --- a/src/panels/places/placesitemeditdialog.cpp +++ b/src/panels/places/placesitemeditdialog.cpp @@ -1,172 +1,170 @@ /*************************************************************************** * Copyright (C) 2012 by Peter Penz * * * * Based on KFilePlaceEditDialog from kdelibs: * * Copyright (C) 2001,2002,2003 Carsten Pfeiffer * * Copyright (C) 2007 Kevin Ottens * * * * * 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 "placesitemeditdialog.h" #include #include "dolphindebug.h" #include #include #include #include #include #include #include #include -#include #include -#include #include PlacesItemEditDialog::PlacesItemEditDialog(QWidget* parent) : QDialog(parent), m_icon(), m_text(), m_url(), m_allowGlobal(false), m_urlEdit(nullptr), m_textEdit(nullptr), m_iconButton(nullptr), m_appLocal(nullptr), m_buttonBox(nullptr) { } void PlacesItemEditDialog::setIcon(const QString& icon) { m_icon = icon; } QString PlacesItemEditDialog::icon() const { return m_iconButton->icon(); } void PlacesItemEditDialog::setText(const QString& text) { m_text = text; } QString PlacesItemEditDialog::text() const { QString text = m_textEdit->text(); if (text.isEmpty()) { const QUrl url = m_urlEdit->url(); text = url.fileName().isEmpty() ? url.toDisplayString(QUrl::PreferLocalFile) : url.fileName(); } return text; } void PlacesItemEditDialog::setUrl(const QUrl& url) { m_url = url; } QUrl PlacesItemEditDialog::url() const { return m_urlEdit->url(); } void PlacesItemEditDialog::setAllowGlobal(bool allow) { m_allowGlobal = allow; } bool PlacesItemEditDialog::allowGlobal() const { return m_allowGlobal; } bool PlacesItemEditDialog::event(QEvent* event) { if (event->type() == QEvent::Polish) { initialize(); } return QWidget::event(event); } void PlacesItemEditDialog::slotUrlChanged(const QString& text) { m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty()); } PlacesItemEditDialog::~PlacesItemEditDialog() { } void PlacesItemEditDialog::initialize() { m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, this); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &PlacesItemEditDialog::accept); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &PlacesItemEditDialog::reject); setModal(true); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); QWidget* mainWidget = new QWidget(this); mainLayout->addWidget(mainWidget); mainLayout->addWidget(m_buttonBox); QVBoxLayout* vBox = new QVBoxLayout(mainWidget); QFormLayout* formLayout = new QFormLayout(); vBox->addLayout( formLayout ); m_textEdit = new QLineEdit(mainWidget); formLayout->addRow(i18nc("@label", "Label:"), m_textEdit); m_textEdit->setText(m_text); m_textEdit->setPlaceholderText(i18n("Enter descriptive label here")); m_urlEdit = new KUrlRequester(m_url, mainWidget); m_urlEdit->setMode(KFile::Directory); formLayout->addRow(i18nc("@label", "Location:"), m_urlEdit); // Provide room for at least 40 chars (average char width is half of height) m_urlEdit->setMinimumWidth(m_urlEdit->fontMetrics().height() * (40 / 2)); connect(m_urlEdit, &KUrlRequester::textChanged, this, &PlacesItemEditDialog::slotUrlChanged); m_iconButton = new KIconButton(mainWidget); formLayout->addRow(i18nc("@label", "Choose an icon:"), m_iconButton); m_iconButton->setIconSize(IconSize(KIconLoader::Desktop)); m_iconButton->setIconType(KIconLoader::NoGroup, KIconLoader::Place); if (m_icon.isEmpty()) { QMimeDatabase db; m_iconButton->setIcon(db.mimeTypeForUrl(m_url).iconName()); } else { m_iconButton->setIcon(m_icon); } if (m_allowGlobal) { const QString appName = KAboutData::applicationData().displayName(); m_appLocal = new QCheckBox( i18n("&Only show when using this application (%1)", appName ), mainWidget ); m_appLocal->setChecked(false); vBox->addWidget(m_appLocal); } if (m_text.isEmpty()) { m_urlEdit->setFocus(); } else { m_textEdit->setFocus(); } } diff --git a/src/panels/places/placesitemmodel.cpp b/src/panels/places/placesitemmodel.cpp index 077c9044c..76b22f1fd 100644 --- a/src/panels/places/placesitemmodel.cpp +++ b/src/panels/places/placesitemmodel.cpp @@ -1,844 +1,834 @@ /*************************************************************************** * Copyright (C) 2012 by Peter Penz * * * * Based on KFilePlacesModel from kdelibs: * * Copyright (C) 2007 Kevin Ottens * * Copyright (C) 2007 David Faure * * * * 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 "placesitemmodel.h" #include "placesitemsignalhandler.h" #include "dolphin_generalsettings.h" -#include -#include #include "dolphindebug.h" #include -#include #include -#include #include #include "placesitem.h" #include -#include #include #include #include -#include -#include #include -#include #include -#include -#include #include #include namespace { // A suffix to the application-name of the stored bookmarks is // added, which is only read by PlacesItemModel. const QString AppNameSuffix = QStringLiteral("-places-panel"); static QList balooURLs = { QUrl(QStringLiteral("timeline:/today")), QUrl(QStringLiteral("timeline:/yesterday")), QUrl(QStringLiteral("timeline:/thismonth")), QUrl(QStringLiteral("timeline:/lastmonth")), QUrl(QStringLiteral("search:/documents")), QUrl(QStringLiteral("search:/images")), QUrl(QStringLiteral("search:/audio")), QUrl(QStringLiteral("search:/videos")) }; } PlacesItemModel::PlacesItemModel(QObject* parent) : KStandardItemModel(parent), m_hiddenItemsShown(false), m_deviceToTearDown(nullptr), m_storageSetupInProgress(), m_sourceModel(new KFilePlacesModel(KAboutData::applicationData().componentName() + AppNameSuffix, this)) { cleanupBookmarks(); loadBookmarks(); initializeDefaultViewProperties(); connect(m_sourceModel.data(), &KFilePlacesModel::rowsInserted, this, &PlacesItemModel::onSourceModelRowsInserted); connect(m_sourceModel.data(), &KFilePlacesModel::rowsAboutToBeRemoved, this, &PlacesItemModel::onSourceModelRowsAboutToBeRemoved); connect(m_sourceModel.data(), &KFilePlacesModel::dataChanged, this, &PlacesItemModel::onSourceModelDataChanged); connect(m_sourceModel.data(), &KFilePlacesModel::rowsAboutToBeMoved, this, &PlacesItemModel::onSourceModelRowsAboutToBeMoved); connect(m_sourceModel.data(), &KFilePlacesModel::rowsMoved, this, &PlacesItemModel::onSourceModelRowsMoved); connect(m_sourceModel.data(), &KFilePlacesModel::groupHiddenChanged, this, &PlacesItemModel::onSourceModelGroupHiddenChanged); } PlacesItemModel::~PlacesItemModel() { } void PlacesItemModel::createPlacesItem(const QString& text, const QUrl& url, const QString& iconName, int after) { m_sourceModel->addPlace(text, url, iconName, {}, mapToSource(after)); } PlacesItem* PlacesItemModel::placesItem(int index) const { return dynamic_cast(item(index)); } int PlacesItemModel::hiddenCount() const { return m_sourceModel->hiddenCount(); } void PlacesItemModel::setHiddenItemsShown(bool show) { if (m_hiddenItemsShown == show) { return; } m_hiddenItemsShown = show; if (show) { for (int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) { const QModelIndex index = m_sourceModel->index(r, 0); if (!m_sourceModel->isHidden(index)) { continue; } addItemFromSourceModel(index); } } else { for (int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) { const QModelIndex index = m_sourceModel->index(r, 0); if (m_sourceModel->isHidden(index)) { removeItemByIndex(index); } } } #ifdef PLACESITEMMODEL_DEBUG qCDebug(DolphinDebug) << "Changed visibility of hidden items"; showModelState(); #endif } bool PlacesItemModel::hiddenItemsShown() const { return m_hiddenItemsShown; } int PlacesItemModel::closestItem(const QUrl& url) const { return mapFromSource(m_sourceModel->closestItem(url)); } // look for the correct position for the item based on source model void PlacesItemModel::insertSortedItem(PlacesItem* item) { if (!item) { return; } const KBookmark iBookmark = item->bookmark(); const QString iBookmarkId = bookmarkId(iBookmark); QModelIndex sourceIndex; int pos = 0; for(int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) { sourceIndex = m_sourceModel->index(r, 0); const KBookmark sourceBookmark = m_sourceModel->bookmarkForIndex(sourceIndex); if (bookmarkId(sourceBookmark) == iBookmarkId) { break; } if (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex)) { pos++; } } m_indexMap.insert(pos, sourceIndex); insertItem(pos, item); } void PlacesItemModel::onItemInserted(int index) { KStandardItemModel::onItemInserted(index); #ifdef PLACESITEMMODEL_DEBUG qCDebug(DolphinDebug) << "Inserted item" << index; showModelState(); #endif } void PlacesItemModel::onItemRemoved(int index, KStandardItem* removedItem) { m_indexMap.removeAt(index); KStandardItemModel::onItemRemoved(index, removedItem); #ifdef PLACESITEMMODEL_DEBUG qCDebug(DolphinDebug) << "Removed item" << index; showModelState(); #endif } void PlacesItemModel::onItemChanged(int index, const QSet& changedRoles) { const QModelIndex sourceIndex = mapToSource(index); const PlacesItem *changedItem = placesItem(mapFromSource(sourceIndex)); if (!changedItem || !sourceIndex.isValid()) { qWarning() << "invalid item changed signal"; return; } if (changedRoles.contains("isHidden")) { if (m_sourceModel->isHidden(sourceIndex) != changedItem->isHidden()) { m_sourceModel->setPlaceHidden(sourceIndex, changedItem->isHidden()); } else { m_sourceModel->refresh(); } } KStandardItemModel::onItemChanged(index, changedRoles); } QAction* PlacesItemModel::ejectAction(int index) const { const PlacesItem* item = placesItem(index); if (item && item->device().is()) { return new QAction(QIcon::fromTheme(QStringLiteral("media-eject")), i18nc("@item", "Eject"), nullptr); } return nullptr; } QAction* PlacesItemModel::teardownAction(int index) const { const PlacesItem* item = placesItem(index); if (!item) { return nullptr; } Solid::Device device = item->device(); const bool providesTearDown = device.is() && device.as()->isAccessible(); if (!providesTearDown) { return nullptr; } Solid::StorageDrive* drive = device.as(); if (!drive) { drive = device.parent().as(); } bool hotPluggable = false; bool removable = false; if (drive) { hotPluggable = drive->isHotpluggable(); removable = drive->isRemovable(); } QString iconName; QString text; if (device.is()) { text = i18nc("@item", "Release"); } else if (removable || hotPluggable) { text = i18nc("@item", "Safely Remove"); iconName = QStringLiteral("media-eject"); } else { text = i18nc("@item", "Unmount"); iconName = QStringLiteral("media-eject"); } if (iconName.isEmpty()) { return new QAction(text, nullptr); } return new QAction(QIcon::fromTheme(iconName), text, nullptr); } void PlacesItemModel::requestEject(int index) { const PlacesItem* item = placesItem(index); if (item) { Solid::OpticalDrive* drive = item->device().parent().as(); if (drive) { connect(drive, &Solid::OpticalDrive::ejectDone, this, &PlacesItemModel::slotStorageTearDownDone); drive->eject(); } else { const QString label = item->text(); const QString message = i18nc("@info", "The device '%1' is not a disk and cannot be ejected.", label); emit errorMessage(message); } } } void PlacesItemModel::requestTearDown(int index) { const PlacesItem* item = placesItem(index); if (item) { Solid::StorageAccess *tmp = item->device().as(); if (tmp) { m_deviceToTearDown = tmp; // disconnect the Solid::StorageAccess::teardownRequested // to prevent emitting PlacesItemModel::storageTearDownExternallyRequested // after we have emitted PlacesItemModel::storageTearDownRequested disconnect(tmp, &Solid::StorageAccess::teardownRequested, item->signalHandler(), &PlacesItemSignalHandler::onTearDownRequested); emit storageTearDownRequested(tmp->filePath()); } } } bool PlacesItemModel::storageSetupNeeded(int index) const { const PlacesItem* item = placesItem(index); return item ? item->storageSetupNeeded() : false; } void PlacesItemModel::requestStorageSetup(int index) { const PlacesItem* item = placesItem(index); if (!item) { return; } Solid::Device device = item->device(); const bool setup = device.is() && !m_storageSetupInProgress.contains(device.as()) && !device.as()->isAccessible(); if (setup) { Solid::StorageAccess* access = device.as(); m_storageSetupInProgress[access] = index; connect(access, &Solid::StorageAccess::setupDone, this, &PlacesItemModel::slotStorageSetupDone); access->setup(); } } QMimeData* PlacesItemModel::createMimeData(const KItemSet& indexes) const { QList urls; QByteArray itemData; QDataStream stream(&itemData, QIODevice::WriteOnly); for (int index : indexes) { const QUrl itemUrl = placesItem(index)->url(); if (itemUrl.isValid()) { urls << itemUrl; } stream << index; } QMimeData* mimeData = new QMimeData(); if (!urls.isEmpty()) { mimeData->setUrls(urls); } else { // #378954: prevent itemDropEvent() drops if there isn't a source url. mimeData->setData(blacklistItemDropEventMimeType(), QByteArrayLiteral("true")); } mimeData->setData(internalMimeType(), itemData); return mimeData; } bool PlacesItemModel::supportsDropping(int index) const { return index >= 0 && index < count(); } void PlacesItemModel::dropMimeDataBefore(int index, const QMimeData* mimeData) { if (mimeData->hasFormat(internalMimeType())) { // The item has been moved inside the view QByteArray itemData = mimeData->data(internalMimeType()); QDataStream stream(&itemData, QIODevice::ReadOnly); int oldIndex; stream >> oldIndex; m_sourceModel->movePlace(oldIndex, index); } else if (mimeData->hasFormat(QStringLiteral("text/uri-list"))) { // One or more items must be added to the model const QList urls = KUrlMimeData::urlsFromMimeData(mimeData); for (int i = urls.count() - 1; i >= 0; --i) { const QUrl& url = urls[i]; QString text = url.fileName(); if (text.isEmpty()) { text = url.host(); } if ((url.isLocalFile() && !QFileInfo(url.toLocalFile()).isDir()) || url.scheme() == QLatin1String("trash")) { // Only directories outside the trash are allowed continue; } createPlacesItem(text, url, KIO::iconNameForUrl(url), qMax(0, index - 1)); } } // will save bookmark alteration and fix sort if that is broken by the drag/drop operation refresh(); } void PlacesItemModel::addItemFromSourceModel(const QModelIndex &index) { if (!m_hiddenItemsShown && m_sourceModel->isHidden(index)) { return; } const KBookmark bookmark = m_sourceModel->bookmarkForIndex(index); Q_ASSERT(!bookmark.isNull()); PlacesItem *item = itemFromBookmark(bookmark); if (!item) { item = new PlacesItem(bookmark); } updateItem(item, index); insertSortedItem(item); if (m_sourceModel->isDevice(index)) { connect(item->signalHandler(), &PlacesItemSignalHandler::tearDownExternallyRequested, this, &PlacesItemModel::storageTearDownExternallyRequested); } } void PlacesItemModel::removeItemByIndex(const QModelIndex &sourceIndex) { QString id = bookmarkId(m_sourceModel->bookmarkForIndex(sourceIndex)); for (int i = 0, iMax = count(); i < iMax; ++i) { if (bookmarkId(placesItem(i)->bookmark()) == id) { removeItem(i); return; } } } QString PlacesItemModel::bookmarkId(const KBookmark &bookmark) const { QString id = bookmark.metaDataItem(QStringLiteral("UDI")); if (id.isEmpty()) { id = bookmark.metaDataItem(QStringLiteral("ID")); } return id; } void PlacesItemModel::initializeDefaultViewProperties() const { for(int i = 0, iMax = m_sourceModel->rowCount(); i < iMax; i++) { const QModelIndex index = m_sourceModel->index(i, 0); const PlacesItem *item = placesItem(mapFromSource(index)); if (!item) { continue; } // Create default view-properties for all "Search For" and "Recently Saved" bookmarks // in case the user has not already created custom view-properties for a corresponding // query yet. const bool createDefaultViewProperties = item->isSearchOrTimelineUrl() && !GeneralSettings::self()->globalViewProps(); if (createDefaultViewProperties) { const QUrl itemUrl = item->url(); ViewProperties props(KFilePlacesModel::convertedUrl(itemUrl)); if (!props.exist()) { const QString path = itemUrl.path(); if (path == QLatin1String("/documents")) { props.setViewMode(DolphinView::DetailsView); props.setPreviewsShown(false); props.setVisibleRoles({"text", "path"}); } else if (path == QLatin1String("/images")) { props.setViewMode(DolphinView::IconsView); props.setPreviewsShown(true); props.setVisibleRoles({"text", "imageSize"}); } else if (path == QLatin1String("/audio")) { props.setViewMode(DolphinView::DetailsView); props.setPreviewsShown(false); props.setVisibleRoles({"text", "artist", "album"}); } else if (path == QLatin1String("/videos")) { props.setViewMode(DolphinView::IconsView); props.setPreviewsShown(true); props.setVisibleRoles({"text"}); } else if (itemUrl.scheme() == QLatin1String("timeline")) { props.setViewMode(DolphinView::DetailsView); props.setVisibleRoles({"text", "modificationtime"}); } props.save(); } } } } void PlacesItemModel::updateItem(PlacesItem *item, const QModelIndex &index) { item->setGroup(index.data(KFilePlacesModel::GroupRole).toString()); item->setIcon(index.data(KFilePlacesModel::IconNameRole).toString()); item->setGroupHidden(index.data(KFilePlacesModel::GroupHiddenRole).toBool()); } void PlacesItemModel::slotStorageTearDownDone(Solid::ErrorType error, const QVariant& errorData) { if (error && errorData.isValid()) { emit errorMessage(errorData.toString()); } m_deviceToTearDown->disconnect(); m_deviceToTearDown = nullptr; } void PlacesItemModel::slotStorageSetupDone(Solid::ErrorType error, const QVariant& errorData, const QString& udi) { Q_UNUSED(udi); const int index = m_storageSetupInProgress.take(sender()); const PlacesItem* item = placesItem(index); if (!item) { return; } if (error != Solid::NoError) { if (errorData.isValid()) { emit errorMessage(i18nc("@info", "An error occurred while accessing '%1', the system responded: %2", item->text(), errorData.toString())); } else { emit errorMessage(i18nc("@info", "An error occurred while accessing '%1'", item->text())); } emit storageSetupDone(index, false); } else { emit storageSetupDone(index, true); } } void PlacesItemModel::onSourceModelRowsInserted(const QModelIndex &parent, int first, int last) { for (int i = first; i <= last; i++) { const QModelIndex index = m_sourceModel->index(i, 0, parent); addItemFromSourceModel(index); } } void PlacesItemModel::onSourceModelRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last) { for(int r = first; r <= last; r++) { const QModelIndex index = m_sourceModel->index(r, 0, parent); int oldIndex = mapFromSource(index); if (oldIndex != -1) { removeItem(oldIndex); } } } void PlacesItemModel::onSourceModelRowsAboutToBeMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row) { Q_UNUSED(destination); Q_UNUSED(row); for(int r = start; r <= end; r++) { const QModelIndex sourceIndex = m_sourceModel->index(r, 0, parent); // remove moved item removeItem(mapFromSource(sourceIndex)); } } void PlacesItemModel::onSourceModelRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row) { Q_UNUSED(destination); Q_UNUSED(parent); const int blockSize = (end - start) + 1; for (int r = start; r <= end; r++) { // insert the moved item in the new position const int targetRow = row + (start - r) - (r < row ? blockSize : 0); const QModelIndex targetIndex = m_sourceModel->index(targetRow, 0, destination); addItemFromSourceModel(targetIndex); } } void PlacesItemModel::onSourceModelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { Q_UNUSED(roles); for (int r = topLeft.row(); r <= bottomRight.row(); r++) { const QModelIndex sourceIndex = m_sourceModel->index(r, 0); const KBookmark bookmark = m_sourceModel->bookmarkForIndex(sourceIndex); PlacesItem *placeItem = itemFromBookmark(bookmark); if (placeItem && (!m_hiddenItemsShown && m_sourceModel->isHidden(sourceIndex))) { //hide item if it became invisible removeItem(index(placeItem)); return; } if (!placeItem && (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex))) { //show item if it became visible addItemFromSourceModel(sourceIndex); return; } if (placeItem && !m_sourceModel->isDevice(sourceIndex)) { placeItem->setText(bookmark.text()); placeItem->setIcon(sourceIndex.data(KFilePlacesModel::IconNameRole).toString()); placeItem->setUrl(m_sourceModel->url(sourceIndex)); placeItem->bookmark().setMetaDataItem(QStringLiteral("OnlyInApp"), bookmark.metaDataItem(QStringLiteral("OnlyInApp"))); // must update the bookmark object placeItem->setBookmark(bookmark); } } } void PlacesItemModel::onSourceModelGroupHiddenChanged(KFilePlacesModel::GroupType group, bool hidden) { for(const QModelIndex &sourceIndex : m_sourceModel->groupIndexes(group)) { PlacesItem *item = placesItem(mapFromSource(sourceIndex)); if (item) { item->setGroupHidden(hidden); } } } void PlacesItemModel::cleanupBookmarks() { // KIO model now provides support for baloo urls, and because of that we // need to remove old URLs that were visible only in Dolphin to avoid duplication int row = 0; do { const QModelIndex sourceIndex = m_sourceModel->index(row, 0); const KBookmark bookmark = m_sourceModel->bookmarkForIndex(sourceIndex); const QUrl url = bookmark.url(); const QString appName = bookmark.metaDataItem(QStringLiteral("OnlyInApp")); if ((appName == KAboutData::applicationData().componentName() || appName == KAboutData::applicationData().componentName() + AppNameSuffix) && balooURLs.contains(url)) { qCDebug(DolphinDebug) << "Removing old baloo url:" << url; m_sourceModel->removePlace(sourceIndex); } else { row++; } } while (row < m_sourceModel->rowCount()); } void PlacesItemModel::loadBookmarks() { for(int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) { const QModelIndex sourceIndex = m_sourceModel->index(r, 0); if (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex)) { addItemFromSourceModel(sourceIndex); } } #ifdef PLACESITEMMODEL_DEBUG qCDebug(DolphinDebug) << "Loaded bookmarks"; showModelState(); #endif } void PlacesItemModel::clear() { KStandardItemModel::clear(); } void PlacesItemModel::proceedWithTearDown() { Q_ASSERT(m_deviceToTearDown); connect(m_deviceToTearDown, &Solid::StorageAccess::teardownDone, this, &PlacesItemModel::slotStorageTearDownDone); m_deviceToTearDown->teardown(); } void PlacesItemModel::deleteItem(int index) { QModelIndex sourceIndex = mapToSource(index); Q_ASSERT(sourceIndex.isValid()); m_sourceModel->removePlace(sourceIndex); } void PlacesItemModel::refresh() { m_sourceModel->refresh(); } void PlacesItemModel::hideItem(int index) { PlacesItem* shownItem = placesItem(index); if (!shownItem) { return; } shownItem->setHidden(true); } QString PlacesItemModel::internalMimeType() const { return "application/x-dolphinplacesmodel-" + QString::number((qptrdiff)this); } int PlacesItemModel::groupedDropIndex(int index, const PlacesItem* item) const { Q_ASSERT(item); int dropIndex = index; const QString group = item->group(); const int itemCount = count(); if (index < 0) { dropIndex = itemCount; } // Search nearest previous item with the same group int previousIndex = -1; for (int i = dropIndex - 1; i >= 0; --i) { if (placesItem(i)->group() == group) { previousIndex = i; break; } } // Search nearest next item with the same group int nextIndex = -1; for (int i = dropIndex; i < count(); ++i) { if (placesItem(i)->group() == group) { nextIndex = i; break; } } // Adjust the drop-index to be inserted to the // nearest item with the same group. if (previousIndex >= 0 && nextIndex >= 0) { dropIndex = (dropIndex - previousIndex < nextIndex - dropIndex) ? previousIndex + 1 : nextIndex; } else if (previousIndex >= 0) { dropIndex = previousIndex + 1; } else if (nextIndex >= 0) { dropIndex = nextIndex; } return dropIndex; } bool PlacesItemModel::equalBookmarkIdentifiers(const KBookmark& b1, const KBookmark& b2) { const QString udi1 = b1.metaDataItem(QStringLiteral("UDI")); const QString udi2 = b2.metaDataItem(QStringLiteral("UDI")); if (!udi1.isEmpty() && !udi2.isEmpty()) { return udi1 == udi2; } else { return b1.metaDataItem(QStringLiteral("ID")) == b2.metaDataItem(QStringLiteral("ID")); } } int PlacesItemModel::mapFromSource(const QModelIndex &index) const { if (!index.isValid()) { return -1; } return m_indexMap.indexOf(index); } bool PlacesItemModel::isDir(int index) const { Q_UNUSED(index); return true; } KFilePlacesModel::GroupType PlacesItemModel::groupType(int row) const { return m_sourceModel->groupType(mapToSource(row)); } bool PlacesItemModel::isGroupHidden(KFilePlacesModel::GroupType type) const { return m_sourceModel->isGroupHidden(type); } void PlacesItemModel::setGroupHidden(KFilePlacesModel::GroupType type, bool hidden) { return m_sourceModel->setGroupHidden(type, hidden); } QModelIndex PlacesItemModel::mapToSource(int row) const { return m_indexMap.value(row); } PlacesItem *PlacesItemModel::itemFromBookmark(const KBookmark &bookmark) const { const QString id = bookmarkId(bookmark); for (int i = 0, iMax = count(); i < iMax; i++) { PlacesItem *item = placesItem(i); const KBookmark itemBookmark = item->bookmark(); if (bookmarkId(itemBookmark) == id) { return item; } } return nullptr; } #ifdef PLACESITEMMODEL_DEBUG void PlacesItemModel::showModelState() { qCDebug(DolphinDebug) << "================================="; qCDebug(DolphinDebug) << "Model:"; qCDebug(DolphinDebug) << "hidden-index model-index text"; int modelIndex = 0; for (int i = 0; i < m_bookmarkedItems.count(); ++i) { if (m_bookmarkedItems[i]) { qCDebug(DolphinDebug) << i << "(Hidden) " << " " << m_bookmarkedItems[i]->dataValue("text").toString(); } else { if (item(modelIndex)) { qCDebug(DolphinDebug) << i << " " << modelIndex << " " << item(modelIndex)->dataValue("text").toString(); } else { qCDebug(DolphinDebug) << i << " " << modelIndex << " " << "(not available yet)"; } ++modelIndex; } } qCDebug(DolphinDebug); qCDebug(DolphinDebug) << "Bookmarks:"; int bookmarkIndex = 0; KBookmarkGroup root = m_bookmarkManager->root(); KBookmark bookmark = root.first(); while (!bookmark.isNull()) { const QString udi = bookmark.metaDataItem("UDI"); const QString text = udi.isEmpty() ? bookmark.text() : udi; if (bookmark.metaDataItem("IsHidden") == QLatin1String("true")) { qCDebug(DolphinDebug) << bookmarkIndex << "(Hidden)" << text; } else { qCDebug(DolphinDebug) << bookmarkIndex << " " << text; } bookmark = root.next(bookmark); ++bookmarkIndex; } } #endif diff --git a/src/panels/places/placesitemsignalhandler.cpp b/src/panels/places/placesitemsignalhandler.cpp index bd3fc9f72..5531d1b63 100644 --- a/src/panels/places/placesitemsignalhandler.cpp +++ b/src/panels/places/placesitemsignalhandler.cpp @@ -1,61 +1,60 @@ /*************************************************************************** * Copyright (C) 2012 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 "placesitemsignalhandler.h" #include "placesitem.h" -#include PlacesItemSignalHandler::PlacesItemSignalHandler(PlacesItem* item, QObject* parent) : QObject(parent), m_item(item) { } PlacesItemSignalHandler::~PlacesItemSignalHandler() { } void PlacesItemSignalHandler::onAccessibilityChanged() { if (m_item) { m_item->onAccessibilityChanged(); } } void PlacesItemSignalHandler::onTrashDirListerCompleted() { if (m_item) { m_item->onTrashDirListerCompleted(); } } void PlacesItemSignalHandler::onTearDownRequested(const QString& udi) { Q_UNUSED(udi) if (m_item) { Solid::StorageAccess *tmp = m_item->device().as(); if (tmp) { QString mountPath = tmp->filePath(); emit tearDownExternallyRequested(mountPath); } } } diff --git a/src/panels/places/placespanel.cpp b/src/panels/places/placespanel.cpp index f7ca7d010..e2bdeaafa 100644 --- a/src/panels/places/placespanel.cpp +++ b/src/panels/places/placespanel.cpp @@ -1,544 +1,541 @@ /*************************************************************************** * Copyright (C) 2008-2012 by Peter Penz * * * * Based on KFilePlacesView from kdelibs: * * Copyright (C) 2007 Kevin Ottens * * Copyright (C) 2007 David Faure * * * * 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 "placespanel.h" #include "dolphin_generalsettings.h" #include "global.h" #include -#include "dolphindebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "placesitem.h" #include "placesitemeditdialog.h" #include "placesitemlistgroupheader.h" #include "placesitemlistwidget.h" #include "placesitemmodel.h" #include "placesview.h" #include #include #include -#include -#include PlacesPanel::PlacesPanel(QWidget* parent) : Panel(parent), m_controller(nullptr), m_model(nullptr), m_view(nullptr), m_storageSetupFailedUrl(), m_triggerStorageSetupButton(), m_itemDropEventIndex(-1), m_itemDropEventMimeData(nullptr), m_itemDropEvent(nullptr) { } PlacesPanel::~PlacesPanel() { } void PlacesPanel::proceedWithTearDown() { m_model->proceedWithTearDown(); } bool PlacesPanel::urlChanged() { if (!url().isValid() || url().scheme().contains(QStringLiteral("search"))) { // Skip results shown by a search, as possible identical // directory names are useless without parent-path information. return false; } if (m_controller) { selectClosestItem(); } return true; } void PlacesPanel::readSettings() { if (m_controller) { const int delay = GeneralSettings::autoExpandFolders() ? 750 : -1; m_controller->setAutoActivationDelay(delay); } } void PlacesPanel::showEvent(QShowEvent* event) { if (event->spontaneous()) { Panel::showEvent(event); return; } if (!m_controller) { // Postpone the creating of the controller to the first show event. // This assures that no performance and memory overhead is given when the folders panel is not // used at all and stays invisible. m_model = new PlacesItemModel(this); m_model->setGroupedSorting(true); connect(m_model, &PlacesItemModel::errorMessage, this, &PlacesPanel::errorMessage); connect(m_model, &PlacesItemModel::storageTearDownRequested, this, &PlacesPanel::storageTearDownRequested); connect(m_model, &PlacesItemModel::storageTearDownExternallyRequested, this, &PlacesPanel::storageTearDownExternallyRequested); m_view = new PlacesView(); m_view->setWidgetCreator(new KItemListWidgetCreator()); m_view->setGroupHeaderCreator(new KItemListGroupHeaderCreator()); m_controller = new KItemListController(m_model, m_view, this); m_controller->setSelectionBehavior(KItemListController::SingleSelection); m_controller->setSingleClickActivationEnforced(true); readSettings(); connect(m_controller, &KItemListController::itemActivated, this, &PlacesPanel::slotItemActivated); connect(m_controller, &KItemListController::itemMiddleClicked, this, &PlacesPanel::slotItemMiddleClicked); connect(m_controller, &KItemListController::itemContextMenuRequested, this, &PlacesPanel::slotItemContextMenuRequested); connect(m_controller, &KItemListController::viewContextMenuRequested, this, &PlacesPanel::slotViewContextMenuRequested); connect(m_controller, &KItemListController::itemDropEvent, this, &PlacesPanel::slotItemDropEvent); connect(m_controller, &KItemListController::aboveItemDropEvent, this, &PlacesPanel::slotAboveItemDropEvent); KItemListContainer* container = new KItemListContainer(m_controller, this); container->setEnabledFrame(false); QVBoxLayout* layout = new QVBoxLayout(this); layout->setMargin(0); layout->addWidget(container); selectClosestItem(); } Panel::showEvent(event); } void PlacesPanel::slotItemActivated(int index) { triggerItem(index, Qt::LeftButton); } void PlacesPanel::slotItemMiddleClicked(int index) { triggerItem(index, Qt::MiddleButton); } void PlacesPanel::slotItemContextMenuRequested(int index, const QPointF& pos) { PlacesItem* item = m_model->placesItem(index); if (!item) { return; } QMenu menu(this); QAction* emptyTrashAction = nullptr; QAction* editAction = nullptr; QAction* teardownAction = nullptr; QAction* ejectAction = nullptr; const QString label = item->text(); const bool isDevice = !item->udi().isEmpty(); const bool isTrash = (item->url().scheme() == QLatin1String("trash")); if (isDevice) { ejectAction = m_model->ejectAction(index); if (ejectAction) { ejectAction->setParent(&menu); menu.addAction(ejectAction); } teardownAction = m_model->teardownAction(index); if (teardownAction) { teardownAction->setParent(&menu); menu.addAction(teardownAction); } if (teardownAction || ejectAction) { menu.addSeparator(); } } else { if (isTrash) { emptyTrashAction = menu.addAction(QIcon::fromTheme(QStringLiteral("trash-empty")), i18nc("@action:inmenu", "Empty Trash")); emptyTrashAction->setEnabled(item->icon() == QLatin1String("user-trash-full")); menu.addSeparator(); } } QAction* openInNewWindowAction = menu.addAction(QIcon::fromTheme("window-new"), i18nc("@item:inmenu", "Open in New Window")); QAction* openInNewTabAction = menu.addAction(QIcon::fromTheme("tab-new"), i18nc("@item:inmenu", "Open in New Tab")); if (!isDevice && !isTrash) { menu.addSeparator(); } if (!isDevice) { editAction = menu.addAction(QIcon::fromTheme("document-properties"), i18nc("@item:inmenu", "Edit...")); } QAction* removeAction = nullptr; if (!isDevice && !item->isSystemItem()) { removeAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@item:inmenu", "Remove")); } QAction* hideAction = menu.addAction(i18nc("@item:inmenu", "Hide")); hideAction->setCheckable(true); hideAction->setChecked(item->isHidden()); buildGroupContextMenu(&menu, index); QAction* action = menu.exec(pos.toPoint()); if (action) { if (action == emptyTrashAction) { emptyTrash(); } else { // The index might have changed if devices were added/removed while // the context menu was open. index = m_model->index(item); if (index < 0) { // The item is not in the model any more, probably because it was an // external device that has been removed while the context menu was open. return; } if (action == editAction) { editEntry(index); } else if (action == removeAction) { m_model->deleteItem(index); } else if (action == hideAction) { item->setHidden(hideAction->isChecked()); } else if (action == openInNewWindowAction) { Dolphin::openNewWindow({KFilePlacesModel::convertedUrl(m_model->data(index).value("url").toUrl())}, this); } else if (action == openInNewTabAction) { // TriggerItem does set up the storage first and then it will // emit the slotItemMiddleClicked signal, because of Qt::MiddleButton. triggerItem(index, Qt::MiddleButton); } else if (action == teardownAction) { m_model->requestTearDown(index); } else if (action == ejectAction) { m_model->requestEject(index); } } } selectClosestItem(); } void PlacesPanel::slotViewContextMenuRequested(const QPointF& pos) { QMenu menu(this); QAction* addAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18nc("@item:inmenu", "Add Entry...")); QAction* showAllAction = nullptr; if (m_model->hiddenCount() > 0) { showAllAction = menu.addAction(i18nc("@item:inmenu", "Show All Entries")); showAllAction->setCheckable(true); showAllAction->setChecked(m_model->hiddenItemsShown()); } buildGroupContextMenu(&menu, m_controller->indexCloseToMousePressedPosition()); QMenu* iconSizeSubMenu = new QMenu(i18nc("@item:inmenu", "Icon Size"), &menu); struct IconSizeInfo { int size; const char* context; const char* text; }; const int iconSizeCount = 4; static const IconSizeInfo iconSizes[iconSizeCount] = { {KIconLoader::SizeSmall, I18N_NOOP2_NOSTRIP("Small icon size", "Small (%1x%2)")}, {KIconLoader::SizeSmallMedium, I18N_NOOP2_NOSTRIP("Medium icon size", "Medium (%1x%2)")}, {KIconLoader::SizeMedium, I18N_NOOP2_NOSTRIP("Large icon size", "Large (%1x%2)")}, {KIconLoader::SizeLarge, I18N_NOOP2_NOSTRIP("Huge icon size", "Huge (%1x%2)")} }; QMap iconSizeActionMap; QActionGroup* iconSizeGroup = new QActionGroup(iconSizeSubMenu); for (int i = 0; i < iconSizeCount; ++i) { const int size = iconSizes[i].size; const QString text = i18nc(iconSizes[i].context, iconSizes[i].text, size, size); QAction* action = iconSizeSubMenu->addAction(text); iconSizeActionMap.insert(action, size); action->setActionGroup(iconSizeGroup); action->setCheckable(true); action->setChecked(m_view->iconSize() == size); } menu.addMenu(iconSizeSubMenu); menu.addSeparator(); foreach (QAction* action, customContextMenuActions()) { menu.addAction(action); } QAction* action = menu.exec(pos.toPoint()); if (action) { if (action == addAction) { addEntry(); } else if (action == showAllAction) { m_model->setHiddenItemsShown(showAllAction->isChecked()); } else if (iconSizeActionMap.contains(action)) { m_view->setIconSize(iconSizeActionMap.value(action)); } } selectClosestItem(); } QAction *PlacesPanel::buildGroupContextMenu(QMenu *menu, int index) { if (index == -1) { return nullptr; } KFilePlacesModel::GroupType groupType = m_model->groupType(index); QAction *hideGroupAction = menu->addAction(i18nc("@item:inmenu", "Hide Section '%1'", m_model->item(index)->group())); hideGroupAction->setCheckable(true); hideGroupAction->setChecked(m_model->isGroupHidden(groupType)); connect(hideGroupAction, &QAction::triggered, this, [this, groupType, hideGroupAction]{ m_model->setGroupHidden(groupType, hideGroupAction->isChecked()); }); return hideGroupAction; } void PlacesPanel::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event) { if (index < 0) { return; } const PlacesItem* destItem = m_model->placesItem(index); if (destItem->isSearchOrTimelineUrl()) { return; } if (m_model->storageSetupNeeded(index)) { connect(m_model, &PlacesItemModel::storageSetupDone, this, &PlacesPanel::slotItemDropEventStorageSetupDone); m_itemDropEventIndex = index; // Make a full copy of the Mime-Data m_itemDropEventMimeData = new QMimeData; m_itemDropEventMimeData->setText(event->mimeData()->text()); m_itemDropEventMimeData->setHtml(event->mimeData()->html()); m_itemDropEventMimeData->setUrls(event->mimeData()->urls()); m_itemDropEventMimeData->setImageData(event->mimeData()->imageData()); m_itemDropEventMimeData->setColorData(event->mimeData()->colorData()); m_itemDropEvent = new QDropEvent(event->pos().toPoint(), event->possibleActions(), m_itemDropEventMimeData, event->buttons(), event->modifiers()); m_model->requestStorageSetup(index); return; } QUrl destUrl = destItem->url(); QDropEvent dropEvent(event->pos().toPoint(), event->possibleActions(), event->mimeData(), event->buttons(), event->modifiers()); slotUrlsDropped(destUrl, &dropEvent, this); } void PlacesPanel::slotItemDropEventStorageSetupDone(int index, bool success) { disconnect(m_model, &PlacesItemModel::storageSetupDone, this, &PlacesPanel::slotItemDropEventStorageSetupDone); if ((index == m_itemDropEventIndex) && m_itemDropEvent && m_itemDropEventMimeData) { if (success) { QUrl destUrl = m_model->placesItem(index)->url(); slotUrlsDropped(destUrl, m_itemDropEvent, this); } delete m_itemDropEventMimeData; delete m_itemDropEvent; m_itemDropEventIndex = -1; m_itemDropEventMimeData = nullptr; m_itemDropEvent = nullptr; } } void PlacesPanel::slotAboveItemDropEvent(int index, QGraphicsSceneDragDropEvent* event) { m_model->dropMimeDataBefore(index, event->mimeData()); } void PlacesPanel::slotUrlsDropped(const QUrl& dest, QDropEvent* event, QWidget* parent) { KIO::DropJob *job = DragAndDropHelper::dropUrls(dest, event, parent); if (job) { connect(job, &KIO::DropJob::result, this, [this](KJob *job) { if (job->error()) emit errorMessage(job->errorString()); }); } } void PlacesPanel::slotTrashUpdated(KJob* job) { if (job->error()) { emit errorMessage(job->errorString()); } // as long as KIO doesn't do this, do it ourselves KNotification::event(QStringLiteral("Trash: emptied"), QString(), QPixmap(), nullptr, KNotification::DefaultEvent); } void PlacesPanel::slotStorageSetupDone(int index, bool success) { disconnect(m_model, &PlacesItemModel::storageSetupDone, this, &PlacesPanel::slotStorageSetupDone); if (m_triggerStorageSetupButton == Qt::NoButton) { return; } if (success) { Q_ASSERT(!m_model->storageSetupNeeded(index)); triggerItem(index, m_triggerStorageSetupButton); m_triggerStorageSetupButton = Qt::NoButton; } else { setUrl(m_storageSetupFailedUrl); m_storageSetupFailedUrl = QUrl(); } } void PlacesPanel::emptyTrash() { KIO::JobUiDelegate uiDelegate; uiDelegate.setWindow(window()); if (uiDelegate.askDeleteConfirmation(QList(), KIO::JobUiDelegate::EmptyTrash, KIO::JobUiDelegate::DefaultConfirmation)) { KIO::Job* job = KIO::emptyTrash(); KJobWidgets::setWindow(job, window()); connect(job, &KIO::Job::result, this, &PlacesPanel::slotTrashUpdated); } } void PlacesPanel::addEntry() { const int index = m_controller->selectionManager()->currentItem(); const QUrl url = m_model->data(index).value("url").toUrl(); QPointer dialog = new PlacesItemEditDialog(this); dialog->setWindowTitle(i18nc("@title:window", "Add Places Entry")); dialog->setAllowGlobal(true); dialog->setUrl(url); if (dialog->exec() == QDialog::Accepted) { m_model->createPlacesItem(dialog->text(), dialog->url(), dialog->icon()); } delete dialog; } void PlacesPanel::editEntry(int index) { QHash data = m_model->data(index); QPointer dialog = new PlacesItemEditDialog(this); dialog->setWindowTitle(i18nc("@title:window", "Edit Places Entry")); dialog->setIcon(data.value("iconName").toString()); dialog->setText(data.value("text").toString()); dialog->setUrl(data.value("url").toUrl()); dialog->setAllowGlobal(true); if (dialog->exec() == QDialog::Accepted) { PlacesItem* oldItem = m_model->placesItem(index); if (oldItem) { oldItem->setText(dialog->text()); oldItem->setUrl(dialog->url()); oldItem->setIcon(dialog->icon()); m_model->refresh(); } } delete dialog; } void PlacesPanel::selectClosestItem() { const int index = m_model->closestItem(url()); KItemListSelectionManager* selectionManager = m_controller->selectionManager(); selectionManager->setCurrentItem(index); selectionManager->clearSelection(); selectionManager->setSelected(index); } void PlacesPanel::triggerItem(int index, Qt::MouseButton button) { const PlacesItem* item = m_model->placesItem(index); if (!item) { return; } if (m_model->storageSetupNeeded(index)) { m_triggerStorageSetupButton = button; m_storageSetupFailedUrl = url(); connect(m_model, &PlacesItemModel::storageSetupDone, this, &PlacesPanel::slotStorageSetupDone); m_model->requestStorageSetup(index); } else { m_triggerStorageSetupButton = Qt::NoButton; const QUrl url = m_model->data(index).value("url").toUrl(); if (!url.isEmpty()) { if (button == Qt::MiddleButton) { emit placeMiddleClicked(KFilePlacesModel::convertedUrl(url)); } else { emit placeActivated(KFilePlacesModel::convertedUrl(url)); } } } } diff --git a/src/search/dolphinfacetswidget.cpp b/src/search/dolphinfacetswidget.cpp index cf0134f4e..21a45cd9b 100644 --- a/src/search/dolphinfacetswidget.cpp +++ b/src/search/dolphinfacetswidget.cpp @@ -1,283 +1,282 @@ /*************************************************************************** * Copyright (C) 2012 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 "dolphinfacetswidget.h" #include #include #include #include #include #include -#include DolphinFacetsWidget::DolphinFacetsWidget(QWidget* parent) : QWidget(parent), m_folders(nullptr), m_documents(nullptr), m_images(nullptr), m_audio(nullptr), m_videos(nullptr), m_anytime(nullptr), m_today(nullptr), m_yesterday(nullptr), m_thisWeek(nullptr), m_thisMonth(nullptr), m_thisYear(nullptr), m_anyRating(nullptr), m_oneOrMore(nullptr), m_twoOrMore(nullptr), m_threeOrMore(nullptr), m_fourOrMore(nullptr), m_maxRating(nullptr) { QButtonGroup* filetypeGroup = new QButtonGroup(this); m_anyType = createRadioButton(i18nc("@option:check", "Any"), filetypeGroup); m_folders = createRadioButton(i18nc("@option:check", "Folders"), filetypeGroup); m_documents = createRadioButton(i18nc("@option:check", "Documents"), filetypeGroup); m_images = createRadioButton(i18nc("@option:check", "Images"), filetypeGroup); m_audio = createRadioButton(i18nc("@option:check", "Audio Files"), filetypeGroup); m_videos = createRadioButton(i18nc("@option:check", "Videos"), filetypeGroup); QVBoxLayout* typeLayout = new QVBoxLayout(); typeLayout->setSpacing(0); typeLayout->addWidget(m_anyType); typeLayout->addWidget(m_folders); typeLayout->addWidget(m_documents); typeLayout->addWidget(m_images); typeLayout->addWidget(m_audio); typeLayout->addWidget(m_videos); typeLayout->addStretch(); QButtonGroup* timespanGroup = new QButtonGroup(this); m_anytime = createRadioButton(i18nc("@option:option", "Anytime"), timespanGroup); m_today = createRadioButton(i18nc("@option:option", "Today"), timespanGroup); m_yesterday = createRadioButton(i18nc("@option:option", "Yesterday"), timespanGroup); m_thisWeek = createRadioButton(i18nc("@option:option", "This Week"), timespanGroup); m_thisMonth = createRadioButton(i18nc("@option:option", "This Month"), timespanGroup); m_thisYear = createRadioButton(i18nc("@option:option", "This Year"), timespanGroup); QVBoxLayout* timespanLayout = new QVBoxLayout(); timespanLayout->setSpacing(0); timespanLayout->addWidget(m_anytime); timespanLayout->addWidget(m_today); timespanLayout->addWidget(m_yesterday); timespanLayout->addWidget(m_thisWeek); timespanLayout->addWidget(m_thisMonth); timespanLayout->addWidget(m_thisYear); timespanLayout->addStretch(); QButtonGroup* ratingGroup = new QButtonGroup(this); m_anyRating = createRadioButton(i18nc("@option:option", "Any Rating"), ratingGroup); m_oneOrMore = createRadioButton(i18nc("@option:option", "1 or more"), ratingGroup); m_twoOrMore = createRadioButton(i18nc("@option:option", "2 or more"), ratingGroup); m_threeOrMore = createRadioButton(i18nc("@option:option", "3 or more"), ratingGroup); m_fourOrMore = createRadioButton(i18nc("@option:option", "4 or more"), ratingGroup); m_maxRating = createRadioButton(i18nc("@option:option", "Highest Rating"), ratingGroup); QVBoxLayout* ratingLayout = new QVBoxLayout(); ratingLayout->setSpacing(0); ratingLayout->addWidget(m_anyRating); ratingLayout->addWidget(m_oneOrMore); ratingLayout->addWidget(m_twoOrMore); ratingLayout->addWidget(m_threeOrMore); ratingLayout->addWidget(m_fourOrMore); ratingLayout->addWidget(m_maxRating); QHBoxLayout* topLayout = new QHBoxLayout(this); topLayout->addLayout(typeLayout); topLayout->addLayout(timespanLayout); topLayout->addLayout(ratingLayout); topLayout->addStretch(); m_anyType->setChecked(true); m_anytime->setChecked(true); m_anyRating->setChecked(true); } DolphinFacetsWidget::~DolphinFacetsWidget() { } QString DolphinFacetsWidget::ratingTerm() const { QStringList terms; if (!m_anyRating->isChecked()) { int stars = 1; // represents m_oneOrMore if (m_twoOrMore->isChecked()) { stars = 2; } else if (m_threeOrMore->isChecked()) { stars = 3; } else if (m_fourOrMore->isChecked()) { stars = 4; } else if (m_maxRating->isChecked()) { stars = 5; } const int rating = stars * 2; terms << QStringLiteral("rating>=%1").arg(rating); } if (!m_anytime->isChecked()) { QDate date = QDate::currentDate(); // represents m_today if (m_yesterday->isChecked()) { date = date.addDays(-1); } else if (m_thisWeek->isChecked()) { date = date.addDays(1 - date.dayOfWeek()); } else if (m_thisMonth->isChecked()) { date = date.addDays(1 - date.day()); } else if (m_thisYear->isChecked()) { date = date.addDays(1 - date.dayOfYear()); } terms << QStringLiteral("modified>=%1").arg(date.toString(Qt::ISODate)); } return terms.join(QStringLiteral(" AND ")); } QString DolphinFacetsWidget::facetType() const { if (m_folders->isChecked()) { return QStringLiteral("Folder"); } else if (m_documents->isChecked()) { return QStringLiteral("Document"); } else if (m_images->isChecked()) { return QStringLiteral("Image"); } else if (m_audio->isChecked()) { return QStringLiteral("Audio"); } else if (m_videos->isChecked()) { return QStringLiteral("Video"); } return QString(); } bool DolphinFacetsWidget::isRatingTerm(const QString& term) const { const QStringList subTerms = term.split(' ', QString::SkipEmptyParts); // If term has sub terms, then sone of the sub terms are always "rating" and "modified" terms. bool containsRating = false; bool containsModified = false; foreach (const QString& subTerm, subTerms) { if (subTerm.startsWith(QLatin1String("rating>="))) { containsRating = true; } else if (subTerm.startsWith(QLatin1String("modified>="))) { containsModified = true; } } return containsModified || containsRating; } void DolphinFacetsWidget::setRatingTerm(const QString& term) { // If term has sub terms, then the sub terms are always "rating" and "modified" terms. // If term has no sub terms, then the term itself is either a "rating" term or a "modified" // term. To avoid code duplication we add term to subTerms list, if the list is empty. QStringList subTerms = term.split(' ', QString::SkipEmptyParts); foreach (const QString& subTerm, subTerms) { if (subTerm.startsWith(QLatin1String("modified>="))) { const QString value = subTerm.mid(10); const QDate date = QDate::fromString(value, Qt::ISODate); setTimespan(date); } else if (subTerm.startsWith(QLatin1String("rating>="))) { const QString value = subTerm.mid(8); const int stars = value.toInt() / 2; setRating(stars); } } } void DolphinFacetsWidget::setFacetType(const QString& type) { if (type == QLatin1String("Document")) { m_documents->setChecked(true); } else if (type == QLatin1String("Image")) { m_images->setChecked(true); } else if (type == QLatin1String("Audio")) { m_audio->setChecked(true); } else if (type == QLatin1String("Video")) { m_videos->setChecked(true); } else { m_anyType->setChecked(true); } } void DolphinFacetsWidget::setRating(const int stars) { switch (stars) { case 5: m_maxRating->setChecked(true); break; case 4: m_fourOrMore->setChecked(true); break; case 3: m_threeOrMore->setChecked(true); break; case 2: m_twoOrMore->setChecked(true); break; case 1: m_oneOrMore->setChecked(true); break; default: m_anyRating->setChecked(true); } } void DolphinFacetsWidget::setTimespan(const QDate& date) { const QDate currentDate = QDate::currentDate(); const int days = date.daysTo(currentDate); if (days <= 0) { m_today->setChecked(true); } else if (days <= 1) { m_yesterday->setChecked(true); } else if (days <= currentDate.dayOfWeek()) { m_thisWeek->setChecked(true); } else if (days <= currentDate.day()) { m_thisMonth->setChecked(true); } else if (days <= currentDate.dayOfYear()) { m_thisYear->setChecked(true); } else { m_anytime->setChecked(true); } } QRadioButton* DolphinFacetsWidget::createRadioButton(const QString& text, QButtonGroup* group) { QRadioButton* button = new QRadioButton(text); connect(button, &QRadioButton::clicked, this, &DolphinFacetsWidget::facetChanged); group->addButton(button); return button; } diff --git a/src/search/dolphinsearchbox.cpp b/src/search/dolphinsearchbox.cpp index 44de97aa0..6cae37e19 100644 --- a/src/search/dolphinsearchbox.cpp +++ b/src/search/dolphinsearchbox.cpp @@ -1,569 +1,566 @@ /*************************************************************************** * Copyright (C) 2010 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 "dolphinsearchbox.h" #include "dolphin_searchsettings.h" #include "dolphinfacetswidget.h" #include #include #include #include #include #include #include #include -#include #include -#include #include #include #include #include -#include #include #include #ifdef HAVE_BALOO #include #include #endif #include DolphinSearchBox::DolphinSearchBox(QWidget* parent) : QWidget(parent), m_startedSearching(false), m_active(true), m_topLayout(nullptr), m_searchLabel(nullptr), m_searchInput(nullptr), m_saveSearchAction(nullptr), m_optionsScrollArea(nullptr), m_fileNameButton(nullptr), m_contentButton(nullptr), m_separator(nullptr), m_fromHereButton(nullptr), m_everywhereButton(nullptr), m_facetsToggleButton(nullptr), m_facetsWidget(nullptr), m_searchPath(), m_startSearchTimer(nullptr) { } DolphinSearchBox::~DolphinSearchBox() { saveSettings(); } void DolphinSearchBox::setText(const QString& text) { m_searchInput->setText(text); } QString DolphinSearchBox::text() const { return m_searchInput->text(); } void DolphinSearchBox::setSearchPath(const QUrl& url) { m_searchPath = url; QFontMetrics metrics(m_fromHereButton->font()); const int maxWidth = metrics.height() * 8; QString location = url.fileName(); if (location.isEmpty()) { if (url.isLocalFile()) { location = QStringLiteral("/"); } else { location = url.scheme() + QLatin1String(" - ") + url.host(); } } const QString elidedLocation = metrics.elidedText(location, Qt::ElideMiddle, maxWidth); m_fromHereButton->setText(i18nc("action:button", "From Here (%1)", elidedLocation)); const bool showSearchFromButtons = url.isLocalFile(); m_separator->setVisible(showSearchFromButtons); m_fromHereButton->setVisible(showSearchFromButtons); m_everywhereButton->setVisible(showSearchFromButtons); bool hasFacetsSupport = false; #ifdef HAVE_BALOO const Baloo::IndexerConfig searchInfo; hasFacetsSupport = searchInfo.fileIndexingEnabled() && searchInfo.shouldBeIndexed(m_searchPath.toLocalFile()); #endif m_facetsWidget->setEnabled(hasFacetsSupport); } QUrl DolphinSearchBox::searchPath() const { return m_searchPath; } QUrl DolphinSearchBox::urlForSearching() const { QUrl url; bool useBalooSearch = false; #ifdef HAVE_BALOO const Baloo::IndexerConfig searchInfo; useBalooSearch = searchInfo.fileIndexingEnabled() && searchInfo.shouldBeIndexed(m_searchPath.toLocalFile()); #endif if (useBalooSearch) { url = balooUrlForSearching(); } else { url.setScheme(QStringLiteral("filenamesearch")); QUrlQuery query; query.addQueryItem(QStringLiteral("search"), m_searchInput->text()); if (m_contentButton->isChecked()) { query.addQueryItem(QStringLiteral("checkContent"), QStringLiteral("yes")); } QString encodedUrl; if (m_everywhereButton->isChecked()) { // It is very unlikely, that the majority of Dolphins target users // mean "the whole harddisk" instead of "my home folder" when // selecting the "Everywhere" button. encodedUrl = QDir::homePath(); } else { encodedUrl = m_searchPath.url(); } query.addQueryItem(QStringLiteral("url"), encodedUrl); url.setQuery(query); } return url; } void DolphinSearchBox::fromSearchUrl(const QUrl& url) { if (url.scheme() == QLatin1String("baloosearch")) { fromBalooSearchUrl(url); } else if (url.scheme() == QLatin1String("filenamesearch")) { const QUrlQuery query(url); setText(query.queryItemValue(QStringLiteral("search"))); setSearchPath(QUrl::fromUserInput(query.queryItemValue(QStringLiteral("url")), QString(), QUrl::AssumeLocalFile)); m_contentButton->setChecked(query.queryItemValue(QStringLiteral("checkContent")) == QLatin1String("yes")); } else { setText(QString()); setSearchPath(url); } } void DolphinSearchBox::selectAll() { m_searchInput->selectAll(); } void DolphinSearchBox::setActive(bool active) { if (active != m_active) { m_active = active; if (active) { emit activated(); } } } bool DolphinSearchBox::isActive() const { return m_active; } bool DolphinSearchBox::event(QEvent* event) { if (event->type() == QEvent::Polish) { init(); } return QWidget::event(event); } void DolphinSearchBox::showEvent(QShowEvent* event) { if (!event->spontaneous()) { m_searchInput->setFocus(); m_startedSearching = false; } } void DolphinSearchBox::hideEvent(QHideEvent* event) { Q_UNUSED(event); m_startedSearching = false; m_startSearchTimer->stop(); } void DolphinSearchBox::keyReleaseEvent(QKeyEvent* event) { QWidget::keyReleaseEvent(event); if (event->key() == Qt::Key_Escape) { if (m_searchInput->text().isEmpty()) { emit closeRequest(); } else { m_searchInput->clear(); } } } bool DolphinSearchBox::eventFilter(QObject* obj, QEvent* event) { switch (event->type()) { case QEvent::FocusIn: // #379135: we get the FocusIn event when we close a tab but we don't want to emit // the activated() signal before the removeTab() call in DolphinTabWidget::closeTab() returns. // To avoid this issue, we delay the activation of the search box. QTimer::singleShot(0, this, [this] { setActive(true); setFocus(); }); break; default: break; } return QObject::eventFilter(obj, event); } void DolphinSearchBox::emitSearchRequest() { m_startSearchTimer->stop(); m_startedSearching = true; m_saveSearchAction->setEnabled(true); emit searchRequest(); } void DolphinSearchBox::emitCloseRequest() { m_startSearchTimer->stop(); m_startedSearching = false; m_saveSearchAction->setEnabled(false); emit closeRequest(); } void DolphinSearchBox::slotConfigurationChanged() { saveSettings(); if (m_startedSearching) { emitSearchRequest(); } } void DolphinSearchBox::slotSearchTextChanged(const QString& text) { if (text.isEmpty()) { m_startSearchTimer->stop(); } else { m_startSearchTimer->start(); } emit searchTextChanged(text); } void DolphinSearchBox::slotReturnPressed() { emitSearchRequest(); emit returnPressed(); } void DolphinSearchBox::slotFacetsButtonToggled() { const bool facetsIsVisible = !m_facetsWidget->isVisible(); m_facetsWidget->setVisible(facetsIsVisible); updateFacetsToggleButton(); } void DolphinSearchBox::slotFacetChanged() { m_startedSearching = true; m_startSearchTimer->stop(); emit searchRequest(); } void DolphinSearchBox::slotSearchSaved() { const QUrl searchURL = urlForSearching(); if (searchURL.isValid()) { PlacesItemModel model; const QString label = i18n("Search for %1 in %2", text(), searchPath().fileName()); model.createPlacesItem(label, searchURL, QStringLiteral("folder-saved-search-symbolic")); } } void DolphinSearchBox::initButton(QToolButton* button) { button->installEventFilter(this); button->setAutoExclusive(true); button->setAutoRaise(true); button->setCheckable(true); connect(button, &QToolButton::clicked, this, &DolphinSearchBox::slotConfigurationChanged); } void DolphinSearchBox::loadSettings() { if (SearchSettings::location() == QLatin1String("Everywhere")) { m_everywhereButton->setChecked(true); } else { m_fromHereButton->setChecked(true); } if (SearchSettings::what() == QLatin1String("Content")) { m_contentButton->setChecked(true); } else { m_fileNameButton->setChecked(true); } m_facetsWidget->setVisible(SearchSettings::showFacetsWidget()); } void DolphinSearchBox::saveSettings() { SearchSettings::setLocation(m_fromHereButton->isChecked() ? QStringLiteral("FromHere") : QStringLiteral("Everywhere")); SearchSettings::setWhat(m_fileNameButton->isChecked() ? QStringLiteral("FileName") : QStringLiteral("Content")); SearchSettings::setShowFacetsWidget(m_facetsToggleButton->isChecked()); SearchSettings::self()->save(); } void DolphinSearchBox::init() { // Create close button QToolButton* closeButton = new QToolButton(this); closeButton->setAutoRaise(true); closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); closeButton->setToolTip(i18nc("@info:tooltip", "Quit searching")); connect(closeButton, &QToolButton::clicked, this, &DolphinSearchBox::emitCloseRequest); // Create search label m_searchLabel = new QLabel(this); // Create search box m_searchInput = new QLineEdit(this); m_searchInput->installEventFilter(this); m_searchInput->setClearButtonEnabled(true); m_searchInput->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); connect(m_searchInput, &QLineEdit::returnPressed, this, &DolphinSearchBox::slotReturnPressed); connect(m_searchInput, &QLineEdit::textChanged, this, &DolphinSearchBox::slotSearchTextChanged); setFocusProxy(m_searchInput); // Add "Save search" button inside search box m_saveSearchAction = new QAction(this); m_saveSearchAction->setIcon (QIcon::fromTheme(QStringLiteral("document-save-symbolic"))); m_saveSearchAction->setText(i18nc("action:button", "Save this search to quickly access it again in the future")); m_saveSearchAction->setEnabled(false); m_searchInput->addAction(m_saveSearchAction, QLineEdit::TrailingPosition); connect(m_saveSearchAction, &QAction::triggered, this, &DolphinSearchBox::slotSearchSaved); // Apply layout for the search input QHBoxLayout* searchInputLayout = new QHBoxLayout(); searchInputLayout->setMargin(0); searchInputLayout->addWidget(closeButton); searchInputLayout->addWidget(m_searchLabel); searchInputLayout->addWidget(m_searchInput); // Create "Filename" and "Content" button m_fileNameButton = new QToolButton(this); m_fileNameButton->setText(i18nc("action:button", "Filename")); initButton(m_fileNameButton); m_contentButton = new QToolButton(); m_contentButton->setText(i18nc("action:button", "Content")); initButton(m_contentButton); QButtonGroup* searchWhatGroup = new QButtonGroup(this); searchWhatGroup->addButton(m_fileNameButton); searchWhatGroup->addButton(m_contentButton); m_separator = new KSeparator(Qt::Vertical, this); // Create "From Here" and "Everywhere"button m_fromHereButton = new QToolButton(this); m_fromHereButton->setText(i18nc("action:button", "From Here")); initButton(m_fromHereButton); m_everywhereButton = new QToolButton(this); m_everywhereButton->setText(i18nc("action:button", "Everywhere")); initButton(m_everywhereButton); QButtonGroup* searchLocationGroup = new QButtonGroup(this); searchLocationGroup->addButton(m_fromHereButton); searchLocationGroup->addButton(m_everywhereButton); auto moreSearchToolsButton = new QToolButton(this); moreSearchToolsButton->setAutoRaise(true); moreSearchToolsButton->setPopupMode(QToolButton::InstantPopup); moreSearchToolsButton->setIcon(QIcon::fromTheme("arrow-down-double")); moreSearchToolsButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); moreSearchToolsButton->setText(i18n("More Search Tools")); moreSearchToolsButton->setMenu(new QMenu(this)); connect(moreSearchToolsButton->menu(), &QMenu::aboutToShow, moreSearchToolsButton->menu(), [this, moreSearchToolsButton]() { m_menuFactory.reset(new KMoreToolsMenuFactory("dolphin/search-tools")); moreSearchToolsButton->menu()->clear(); m_menuFactory->fillMenuFromGroupingNames(moreSearchToolsButton->menu(), { "files-find" }, this->m_searchPath); } ); // Create "Facets" widgets m_facetsToggleButton = new QToolButton(this); m_facetsToggleButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); initButton(m_facetsToggleButton); connect(m_facetsToggleButton, &QToolButton::clicked, this, &DolphinSearchBox::slotFacetsButtonToggled); m_facetsWidget = new DolphinFacetsWidget(this); m_facetsWidget->installEventFilter(this); m_facetsWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); connect(m_facetsWidget, &DolphinFacetsWidget::facetChanged, this, &DolphinSearchBox::slotFacetChanged); // Apply layout for the options QHBoxLayout* optionsLayout = new QHBoxLayout(); optionsLayout->setMargin(0); optionsLayout->addWidget(m_fileNameButton); optionsLayout->addWidget(m_contentButton); optionsLayout->addWidget(m_separator); optionsLayout->addWidget(m_fromHereButton); optionsLayout->addWidget(m_everywhereButton); optionsLayout->addWidget(new KSeparator(Qt::Vertical, this)); optionsLayout->addWidget(m_facetsToggleButton); optionsLayout->addWidget(moreSearchToolsButton); optionsLayout->addStretch(1); // Put the options into a QScrollArea. This prevents increasing the view width // in case that not enough width for the options is available. QWidget* optionsContainer = new QWidget(this); optionsContainer->setLayout(optionsLayout); m_optionsScrollArea = new QScrollArea(this); m_optionsScrollArea->setFrameShape(QFrame::NoFrame); m_optionsScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_optionsScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_optionsScrollArea->setMaximumHeight(optionsContainer->sizeHint().height()); m_optionsScrollArea->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); m_optionsScrollArea->setWidget(optionsContainer); m_optionsScrollArea->setWidgetResizable(true); m_topLayout = new QVBoxLayout(this); m_topLayout->setMargin(0); m_topLayout->addLayout(searchInputLayout); m_topLayout->addWidget(m_optionsScrollArea); m_topLayout->addWidget(m_facetsWidget); loadSettings(); // The searching should be started automatically after the user did not change // the text within one second m_startSearchTimer = new QTimer(this); m_startSearchTimer->setSingleShot(true); m_startSearchTimer->setInterval(1000); connect(m_startSearchTimer, &QTimer::timeout, this, &DolphinSearchBox::emitSearchRequest); updateFacetsToggleButton(); } QUrl DolphinSearchBox::balooUrlForSearching() const { #ifdef HAVE_BALOO const QString text = m_searchInput->text(); Baloo::Query query; query.addType(m_facetsWidget->facetType()); QStringList queryStrings; QString ratingQuery = m_facetsWidget->ratingTerm(); if (!ratingQuery.isEmpty()) { queryStrings << ratingQuery; } if (m_contentButton->isChecked()) { queryStrings << text; } else if (!text.isEmpty()) { queryStrings << QStringLiteral("filename:\"%1\"").arg(text); } if (m_fromHereButton->isChecked()) { query.setIncludeFolder(m_searchPath.toLocalFile()); } query.setSearchString(queryStrings.join(QStringLiteral(" "))); return query.toSearchUrl(i18nc("@title UDS_DISPLAY_NAME for a KIO directory listing. %1 is the query the user entered.", "Query Results from '%1'", text)); #else return QUrl(); #endif } void DolphinSearchBox::fromBalooSearchUrl(const QUrl& url) { #ifdef HAVE_BALOO const Baloo::Query query = Baloo::Query::fromSearchUrl(url); // Block all signals to avoid unnecessary "searchRequest" signals // while we adjust the search text and the facet widget. blockSignals(true); const QString customDir = query.includeFolder(); if (!customDir.isEmpty()) { setSearchPath(QUrl::fromLocalFile(customDir)); } else { setSearchPath(QUrl::fromLocalFile(QDir::homePath())); } setText(query.searchString()); QStringList types = query.types(); if (!types.isEmpty()) { m_facetsWidget->setFacetType(types.first()); } const QStringList subTerms = query.searchString().split(' ', QString::SkipEmptyParts); foreach (const QString& subTerm, subTerms) { if (subTerm.startsWith(QLatin1String("filename:"))) { const QString value = subTerm.mid(9); setText(value); } else if (m_facetsWidget->isRatingTerm(subTerm)) { m_facetsWidget->setRatingTerm(subTerm); } } m_startSearchTimer->stop(); blockSignals(false); #else Q_UNUSED(url); #endif } void DolphinSearchBox::updateFacetsToggleButton() { const bool facetsIsVisible = SearchSettings::showFacetsWidget(); m_facetsToggleButton->setChecked(facetsIsVisible ? true : false); m_facetsToggleButton->setIcon(QIcon::fromTheme(facetsIsVisible ? QStringLiteral("arrow-up-double") : QStringLiteral("arrow-down-double"))); m_facetsToggleButton->setText(facetsIsVisible ? i18nc("action:button", "Fewer Options") : i18nc("action:button", "More Options")); } diff --git a/src/settings/dolphinsettingsdialog.cpp b/src/settings/dolphinsettingsdialog.cpp index 50fd41e4c..ffed870f1 100644 --- a/src/settings/dolphinsettingsdialog.cpp +++ b/src/settings/dolphinsettingsdialog.cpp @@ -1,160 +1,158 @@ /*************************************************************************** * Copyright (C) 2006 by Peter Penz * * peter.penz@gmx.at * * * * 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 "dolphinsettingsdialog.h" #include #include "dolphin_generalsettings.h" #include "general/generalsettingspage.h" #include "navigation/navigationsettingspage.h" #include "services/servicessettingspage.h" #include "startup/startupsettingspage.h" #include "viewmodes/viewsettingspage.h" #include "trash/trashsettingspage.h" #include #include #include -#include #include -#include DolphinSettingsDialog::DolphinSettingsDialog(const QUrl& url, QWidget* parent) : KPageDialog(parent), m_pages() { const QSize minSize = minimumSize(); setMinimumSize(QSize(512, minSize.height())); setFaceType(List); setWindowTitle(i18nc("@title:window", "Dolphin Preferences")); QDialogButtonBox* box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); box->button(QDialogButtonBox::Apply)->setEnabled(false); box->button(QDialogButtonBox::Ok)->setDefault(true); setButtonBox(box); connect(box->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, this, &DolphinSettingsDialog::applySettings); connect(box->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, &DolphinSettingsDialog::applySettings); connect(box->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, this, &DolphinSettingsDialog::restoreDefaults); // Startup StartupSettingsPage* startupSettingsPage = new StartupSettingsPage(url, this); KPageWidgetItem* startupSettingsFrame = addPage(startupSettingsPage, i18nc("@title:group", "Startup")); startupSettingsFrame->setIcon(QIcon::fromTheme(QStringLiteral("go-home"))); connect(startupSettingsPage, &StartupSettingsPage::changed, this, &DolphinSettingsDialog::enableApply); // View Modes ViewSettingsPage* viewSettingsPage = new ViewSettingsPage(this); KPageWidgetItem* viewSettingsFrame = addPage(viewSettingsPage, i18nc("@title:group", "View Modes")); viewSettingsFrame->setIcon(QIcon::fromTheme(QStringLiteral("view-choose"))); connect(viewSettingsPage, &ViewSettingsPage::changed, this, &DolphinSettingsDialog::enableApply); // Navigation NavigationSettingsPage* navigationSettingsPage = new NavigationSettingsPage(this); KPageWidgetItem* navigationSettingsFrame = addPage(navigationSettingsPage, i18nc("@title:group", "Navigation")); navigationSettingsFrame->setIcon(QIcon::fromTheme(QStringLiteral("edit-select"))); connect(navigationSettingsPage, &NavigationSettingsPage::changed, this, &DolphinSettingsDialog::enableApply); // Services ServicesSettingsPage* servicesSettingsPage = new ServicesSettingsPage(this); KPageWidgetItem* servicesSettingsFrame = addPage(servicesSettingsPage, i18nc("@title:group", "Services")); servicesSettingsFrame->setIcon(QIcon::fromTheme(QStringLiteral("flag"))); connect(servicesSettingsPage, &ServicesSettingsPage::changed, this, &DolphinSettingsDialog::enableApply); // Trash auto* trashSettingsPage = createTrashSettingsPage(this); if (trashSettingsPage) { KPageWidgetItem* trashSettingsFrame = addPage(trashSettingsPage, i18nc("@title:group", "Trash")); trashSettingsFrame->setIcon(QIcon::fromTheme(QStringLiteral("trash-empty"))); connect(trashSettingsPage, &TrashSettingsPage::changed, this, &DolphinSettingsDialog::enableApply); } // General GeneralSettingsPage* generalSettingsPage = new GeneralSettingsPage(url, this); KPageWidgetItem* generalSettingsFrame = addPage(generalSettingsPage, i18nc("@title:group General settings", "General")); generalSettingsFrame->setIcon(QIcon::fromTheme(QStringLiteral("view-preview"))); connect(generalSettingsPage, &GeneralSettingsPage::changed, this, &DolphinSettingsDialog::enableApply); m_pages.append(startupSettingsPage); m_pages.append(viewSettingsPage); m_pages.append(navigationSettingsPage); m_pages.append(servicesSettingsPage); if (trashSettingsPage) { m_pages.append(trashSettingsPage); } m_pages.append(generalSettingsPage); const KConfigGroup dialogConfig(KSharedConfig::openConfig(QStringLiteral("dolphinrc")), "SettingsDialog"); KWindowConfig::restoreWindowSize(windowHandle(), dialogConfig); } DolphinSettingsDialog::~DolphinSettingsDialog() { KConfigGroup dialogConfig(KSharedConfig::openConfig(QStringLiteral("dolphinrc")), "SettingsDialog"); KWindowConfig::saveWindowSize(windowHandle(), dialogConfig); } void DolphinSettingsDialog::enableApply() { buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(true); } void DolphinSettingsDialog::applySettings() { foreach (SettingsPageBase* page, m_pages) { page->applySettings(); } emit settingsChanged(); GeneralSettings* settings = GeneralSettings::self(); if (settings->modifiedStartupSettings()) { // Reset the modified startup settings hint. The changed startup settings // have been applied already due to emitting settingsChanged(). settings->setModifiedStartupSettings(false); settings->save(); } buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(false); } void DolphinSettingsDialog::restoreDefaults() { foreach (SettingsPageBase* page, m_pages) { page->restoreDefaults(); } } SettingsPageBase *DolphinSettingsDialog::createTrashSettingsPage(QWidget *parent) { if (!KAuthorized::authorizeControlModule(QStringLiteral("kcmtrash.desktop"))) { return nullptr; } return new TrashSettingsPage(parent); } diff --git a/src/settings/general/behaviorsettingspage.cpp b/src/settings/general/behaviorsettingspage.cpp index b600c2be6..fb3f77eba 100644 --- a/src/settings/general/behaviorsettingspage.cpp +++ b/src/settings/general/behaviorsettingspage.cpp @@ -1,184 +1,182 @@ /*************************************************************************** * Copyright (C) 2006 by Peter Penz (peter.penz@gmx.at) and * * and Patrice Tremblay * * * * 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 "behaviorsettingspage.h" -#include "dolphin_generalsettings.h" - #include #include #include #include #include #include BehaviorSettingsPage::BehaviorSettingsPage(const QUrl& url, QWidget* parent) : SettingsPageBase(parent), m_url(url), m_localViewProps(nullptr), m_globalViewProps(nullptr), m_showToolTips(nullptr), m_showSelectionToggle(nullptr), m_naturalSorting(nullptr), m_caseSensitiveSorting(nullptr), m_caseInsensitiveSorting(nullptr), m_renameInline(nullptr), m_useTabForSplitViewSwitch(nullptr) { QVBoxLayout* topLayout = new QVBoxLayout(this); // View properties QGroupBox* viewPropsBox = new QGroupBox(i18nc("@title:group", "View"), this); viewPropsBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); m_localViewProps = new QRadioButton(i18nc("@option:radio", "Remember properties for each folder"), viewPropsBox); m_globalViewProps = new QRadioButton(i18nc("@option:radio", "Use common properties for all folders"), viewPropsBox); QVBoxLayout* viewPropsLayout = new QVBoxLayout(viewPropsBox); viewPropsLayout->addWidget(m_localViewProps); viewPropsLayout->addWidget(m_globalViewProps); // Sorting properties QGroupBox* sortingPropsBox = new QGroupBox(i18nc("@title:group", "Sorting Mode"), this); sortingPropsBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); m_naturalSorting = new QRadioButton(i18nc("option:radio", "Natural sorting"), sortingPropsBox); m_caseInsensitiveSorting = new QRadioButton(i18nc("option:radio", "Alphabetical sorting, case insensitive"), sortingPropsBox); m_caseSensitiveSorting = new QRadioButton(i18nc("option:radio", "Alphabetical sorting, case sensitive"), sortingPropsBox); QVBoxLayout* sortingPropsLayout = new QVBoxLayout(sortingPropsBox); sortingPropsLayout->addWidget(m_naturalSorting); sortingPropsLayout->addWidget(m_caseInsensitiveSorting); sortingPropsLayout->addWidget(m_caseSensitiveSorting); // 'Show tooltips' m_showToolTips = new QCheckBox(i18nc("@option:check", "Show tooltips"), this); // 'Show selection marker' m_showSelectionToggle = new QCheckBox(i18nc("@option:check", "Show selection marker"), this); // 'Inline renaming of items' m_renameInline = new QCheckBox(i18nc("option:check", "Rename inline"), this); // 'Use tab for switching between right and left split' m_useTabForSplitViewSwitch = new QCheckBox(i18nc("option:check", "Use tab for switching between right and left split view"), this); topLayout->addWidget(viewPropsBox); topLayout->addWidget(sortingPropsBox); topLayout->addWidget(m_showToolTips); topLayout->addWidget(m_showSelectionToggle); topLayout->addWidget(m_renameInline); topLayout->addWidget(m_useTabForSplitViewSwitch); topLayout->addStretch(); loadSettings(); connect(m_localViewProps, &QRadioButton::toggled, this, &BehaviorSettingsPage::changed); connect(m_globalViewProps, &QRadioButton::toggled, this, &BehaviorSettingsPage::changed); connect(m_showToolTips, &QCheckBox::toggled, this, &BehaviorSettingsPage::changed); connect(m_showSelectionToggle, &QCheckBox::toggled, this, &BehaviorSettingsPage::changed); connect(m_naturalSorting, &QRadioButton::toggled, this, &BehaviorSettingsPage::changed); connect(m_caseInsensitiveSorting, &QRadioButton::toggled, this, &BehaviorSettingsPage::changed); connect(m_caseSensitiveSorting, &QRadioButton::toggled, this, &BehaviorSettingsPage::changed); connect(m_renameInline, &QCheckBox::toggled, this, &BehaviorSettingsPage::changed); connect(m_useTabForSplitViewSwitch, &QCheckBox::toggled, this, &BehaviorSettingsPage::changed); } BehaviorSettingsPage::~BehaviorSettingsPage() { } void BehaviorSettingsPage::applySettings() { GeneralSettings* settings = GeneralSettings::self(); ViewProperties props(m_url); // read current view properties const bool useGlobalViewProps = m_globalViewProps->isChecked(); settings->setGlobalViewProps(useGlobalViewProps); settings->setShowToolTips(m_showToolTips->isChecked()); settings->setShowSelectionToggle(m_showSelectionToggle->isChecked()); setSortingChoiceValue(settings); settings->setRenameInline(m_renameInline->isChecked()); settings->setUseTabForSwitchingSplitView(m_useTabForSplitViewSwitch->isChecked()); settings->save(); if (useGlobalViewProps) { // Remember the global view properties by applying the current view properties. // It is important that GeneralSettings::globalViewProps() is set before // the class ViewProperties is used, as ViewProperties uses this setting // to find the destination folder for storing the view properties. ViewProperties globalProps(m_url); globalProps.setDirProperties(props); } } void BehaviorSettingsPage::restoreDefaults() { GeneralSettings* settings = GeneralSettings::self(); settings->useDefaults(true); loadSettings(); settings->useDefaults(false); } void BehaviorSettingsPage::loadSettings() { const bool useGlobalViewProps = GeneralSettings::globalViewProps(); m_localViewProps->setChecked(!useGlobalViewProps); m_globalViewProps->setChecked(useGlobalViewProps); m_showToolTips->setChecked(GeneralSettings::showToolTips()); m_showSelectionToggle->setChecked(GeneralSettings::showSelectionToggle()); m_renameInline->setChecked(GeneralSettings::renameInline()); m_useTabForSplitViewSwitch->setChecked(GeneralSettings::useTabForSwitchingSplitView()); loadSortingChoiceSettings(); } void BehaviorSettingsPage::setSortingChoiceValue(GeneralSettings *settings) { using Choice = GeneralSettings::EnumSortingChoice; if (m_naturalSorting->isChecked()) { settings->setSortingChoice(Choice::NaturalSorting); } else if (m_caseInsensitiveSorting->isChecked()) { settings->setSortingChoice(Choice::CaseInsensitiveSorting); } else if (m_caseSensitiveSorting->isChecked()) { settings->setSortingChoice(Choice::CaseSensitiveSorting); } } void BehaviorSettingsPage::loadSortingChoiceSettings() { using Choice = GeneralSettings::EnumSortingChoice; switch (GeneralSettings::sortingChoice()) { case Choice::NaturalSorting: m_naturalSorting->setChecked(true); break; case Choice::CaseInsensitiveSorting: m_caseInsensitiveSorting->setChecked(true); break; case Choice::CaseSensitiveSorting: m_caseSensitiveSorting->setChecked(true); break; default: Q_UNREACHABLE(); } } diff --git a/src/settings/general/configurepreviewplugindialog.cpp b/src/settings/general/configurepreviewplugindialog.cpp index 629718705..6a4f3769a 100644 --- a/src/settings/general/configurepreviewplugindialog.cpp +++ b/src/settings/general/configurepreviewplugindialog.cpp @@ -1,84 +1,83 @@ /*************************************************************************** * 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 "configurepreviewplugindialog.h" #include #include #include #include #include #include #include -#include #include #include #include #include ConfigurePreviewPluginDialog::ConfigurePreviewPluginDialog(const QString& pluginName, const QString& desktopEntryName, QWidget* parent) : QDialog(parent) { QSharedPointer previewPlugin; const QString pluginPath = KPluginLoader::findPlugin(desktopEntryName); if (!pluginPath.isEmpty()) { newCreator create = (newCreator)QLibrary::resolve(pluginPath, "new_creator"); if (create) { previewPlugin.reset(dynamic_cast(create())); } } setWindowTitle(i18nc("@title:window", "Configure Preview for %1", pluginName)); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); setMinimumWidth(400); auto layout = new QVBoxLayout(this); setLayout(layout); if (previewPlugin) { auto configurationWidget = previewPlugin->createConfigurationWidget(); configurationWidget->setParent(this); layout->addWidget(configurationWidget); layout->addStretch(); connect(this, &ConfigurePreviewPluginDialog::accepted, this, [=] { // TODO: It would be great having a mechanism to tell PreviewJob that only previews // for a specific MIME-type should be regenerated. As this is not available yet we // delete the whole thumbnails directory. previewPlugin->writeConfiguration(configurationWidget); // http://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html#DIRECTORY const QString thumbnailsPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/thumbnails/"); KIO::del(QUrl::fromLocalFile(thumbnailsPath), KIO::HideProgressInfo); }); } auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); connect(buttonBox, &QDialogButtonBox::accepted, this, &ConfigurePreviewPluginDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &ConfigurePreviewPluginDialog::reject); layout->addWidget(buttonBox); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setShortcut(Qt::CTRL + Qt::Key_Return); okButton->setDefault(true); } diff --git a/src/settings/general/generalsettingspage.cpp b/src/settings/general/generalsettingspage.cpp index f9a2c67d0..3793fcdb5 100644 --- a/src/settings/general/generalsettingspage.cpp +++ b/src/settings/general/generalsettingspage.cpp @@ -1,88 +1,87 @@ /*************************************************************************** * Copyright (C) 2006 by Peter Penz * * peter.penz@gmx.at * * * * 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 "generalsettingspage.h" #include "behaviorsettingspage.h" #include "confirmationssettingspage.h" #include "previewssettingspage.h" -#include #include "statusbarsettingspage.h" #include #include #include GeneralSettingsPage::GeneralSettingsPage(const QUrl& url, QWidget* parent) : SettingsPageBase(parent), m_pages() { QVBoxLayout* topLayout = new QVBoxLayout(this); topLayout->setMargin(0); QTabWidget* tabWidget = new QTabWidget(this); // initialize 'Behavior' tab BehaviorSettingsPage* behaviorPage = new BehaviorSettingsPage(url, tabWidget); tabWidget->addTab(behaviorPage, i18nc("@title:tab Behavior settings", "Behavior")); connect(behaviorPage, &BehaviorSettingsPage::changed, this, &GeneralSettingsPage::changed); // initialize 'Previews' tab PreviewsSettingsPage* previewsPage = new PreviewsSettingsPage(tabWidget); tabWidget->addTab(previewsPage, i18nc("@title:tab Previews settings", "Previews")); connect(previewsPage, &PreviewsSettingsPage::changed, this, &GeneralSettingsPage::changed); // initialize 'Context Menu' tab ConfirmationsSettingsPage* confirmationsPage = new ConfirmationsSettingsPage(tabWidget); tabWidget->addTab(confirmationsPage, i18nc("@title:tab Confirmations settings", "Confirmations")); connect(confirmationsPage, &ConfirmationsSettingsPage::changed, this, &GeneralSettingsPage::changed); // initialize 'Status Bar' tab StatusBarSettingsPage* statusBarPage = new StatusBarSettingsPage(tabWidget); tabWidget->addTab(statusBarPage, i18nc("@title:tab Status Bar settings", "Status Bar")); connect(statusBarPage, &StatusBarSettingsPage::changed, this, &GeneralSettingsPage::changed); m_pages.append(behaviorPage); m_pages.append(previewsPage); m_pages.append(confirmationsPage); m_pages.append(statusBarPage); topLayout->addWidget(tabWidget, 0, nullptr); } GeneralSettingsPage::~GeneralSettingsPage() { } void GeneralSettingsPage::applySettings() { foreach (SettingsPageBase* page, m_pages) { page->applySettings(); } } void GeneralSettingsPage::restoreDefaults() { foreach (SettingsPageBase* page, m_pages) { page->restoreDefaults(); } } diff --git a/src/settings/general/previewssettingspage.cpp b/src/settings/general/previewssettingspage.cpp index e579a572d..672bb722d 100644 --- a/src/settings/general/previewssettingspage.cpp +++ b/src/settings/general/previewssettingspage.cpp @@ -1,184 +1,176 @@ /*************************************************************************** * Copyright (C) 2006 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 "previewssettingspage.h" #include "dolphin_generalsettings.h" #include "configurepreviewplugindialog.h" -#include #include #include -#include #include #include #include -#include -#include #include #include #include #include -#include -#include -#include #include #include -#include // default settings namespace { const int MaxRemotePreviewSize = 0; // 0 MB } PreviewsSettingsPage::PreviewsSettingsPage(QWidget* parent) : SettingsPageBase(parent), m_initialized(false), m_listView(nullptr), m_enabledPreviewPlugins(), m_remoteFileSizeBox(nullptr) { QVBoxLayout* topLayout = new QVBoxLayout(this); QLabel* showPreviewsLabel = new QLabel(i18nc("@title:group", "Show previews for:"), this); m_listView = new QListView(this); ServiceItemDelegate* delegate = new ServiceItemDelegate(m_listView, m_listView); connect(delegate, &ServiceItemDelegate::requestServiceConfiguration, this, &PreviewsSettingsPage::configureService); ServiceModel* serviceModel = new ServiceModel(this); QSortFilterProxyModel* proxyModel = new QSortFilterProxyModel(this); proxyModel->setSourceModel(serviceModel); proxyModel->setSortRole(Qt::DisplayRole); m_listView->setModel(proxyModel); m_listView->setItemDelegate(delegate); m_listView->setVerticalScrollMode(QListView::ScrollPerPixel); QLabel* remoteFileSizeLabel = new QLabel(i18nc("@label", "Skip previews for remote files above:"), this); m_remoteFileSizeBox = new QSpinBox(this); m_remoteFileSizeBox->setSingleStep(1); m_remoteFileSizeBox->setSuffix(QStringLiteral(" MB")); m_remoteFileSizeBox->setRange(0, 9999999); /* MB */ QHBoxLayout* fileSizeBoxLayout = new QHBoxLayout(); fileSizeBoxLayout->addWidget(remoteFileSizeLabel, 0, Qt::AlignRight); fileSizeBoxLayout->addWidget(m_remoteFileSizeBox); topLayout->addWidget(showPreviewsLabel); topLayout->addWidget(m_listView); topLayout->addLayout(fileSizeBoxLayout); loadSettings(); connect(m_listView, &QListView::clicked, this, &PreviewsSettingsPage::changed); connect(m_remoteFileSizeBox, static_cast(&QSpinBox::valueChanged), this, &PreviewsSettingsPage::changed); } PreviewsSettingsPage::~PreviewsSettingsPage() { } void PreviewsSettingsPage::applySettings() { const QAbstractItemModel* model = m_listView->model(); const int rowCount = model->rowCount(); if (rowCount > 0) { m_enabledPreviewPlugins.clear(); for (int i = 0; i < rowCount; ++i) { const QModelIndex index = model->index(i, 0); const bool checked = model->data(index, Qt::CheckStateRole).toBool(); if (checked) { const QString enabledPlugin = model->data(index, Qt::UserRole).toString(); m_enabledPreviewPlugins.append(enabledPlugin); } } } KConfigGroup globalConfig(KSharedConfig::openConfig(), QStringLiteral("PreviewSettings")); globalConfig.writeEntry("Plugins", m_enabledPreviewPlugins); const qulonglong maximumRemoteSize = static_cast(m_remoteFileSizeBox->value()) * 1024 * 1024; globalConfig.writeEntry("MaximumRemoteSize", maximumRemoteSize, KConfigBase::Normal | KConfigBase::Global); globalConfig.sync(); } void PreviewsSettingsPage::restoreDefaults() { m_remoteFileSizeBox->setValue(MaxRemotePreviewSize); } void PreviewsSettingsPage::showEvent(QShowEvent* event) { if (!event->spontaneous() && !m_initialized) { loadPreviewPlugins(); m_initialized = true; } SettingsPageBase::showEvent(event); } void PreviewsSettingsPage::configureService(const QModelIndex& index) { const QAbstractItemModel* model = index.model(); const QString pluginName = model->data(index).toString(); const QString desktopEntryName = model->data(index, ServiceModel::DesktopEntryNameRole).toString(); ConfigurePreviewPluginDialog* dialog = new ConfigurePreviewPluginDialog(pluginName, desktopEntryName, this); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); } void PreviewsSettingsPage::loadPreviewPlugins() { QAbstractItemModel* model = m_listView->model(); const KService::List plugins = KServiceTypeTrader::self()->query(QStringLiteral("ThumbCreator")); foreach (const KService::Ptr& service, plugins) { const bool configurable = service->property(QStringLiteral("Configurable"), QVariant::Bool).toBool(); const bool show = m_enabledPreviewPlugins.contains(service->desktopEntryName()); model->insertRow(0); const QModelIndex index = model->index(0, 0); model->setData(index, show, Qt::CheckStateRole); model->setData(index, configurable, ServiceModel::ConfigurableRole); model->setData(index, service->name(), Qt::DisplayRole); model->setData(index, service->desktopEntryName(), ServiceModel::DesktopEntryNameRole); } model->sort(Qt::DisplayRole); } void PreviewsSettingsPage::loadSettings() { const KConfigGroup globalConfig(KSharedConfig::openConfig(), QStringLiteral("PreviewSettings")); m_enabledPreviewPlugins = globalConfig.readEntry("Plugins", KIO::PreviewJob::defaultPlugins()); const qulonglong defaultRemotePreview = static_cast(MaxRemotePreviewSize) * 1024 * 1024; const qulonglong maxRemoteByteSize = globalConfig.readEntry("MaximumRemoteSize", defaultRemotePreview); const int maxRemoteMByteSize = maxRemoteByteSize / (1024 * 1024); m_remoteFileSizeBox->setValue(maxRemoteMByteSize); } diff --git a/src/settings/kcm/kcmdolphingeneral.cpp b/src/settings/kcm/kcmdolphingeneral.cpp index 3aedf5075..2ae88ffd7 100644 --- a/src/settings/kcm/kcmdolphingeneral.cpp +++ b/src/settings/kcm/kcmdolphingeneral.cpp @@ -1,89 +1,88 @@ /*************************************************************************** * Copyright (C) 2009 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 "kcmdolphingeneral.h" #include #include #include #include #include #include -#include #include #include K_PLUGIN_FACTORY(KCMDolphinGeneralConfigFactory, registerPlugin(QStringLiteral("dolphingeneral"));) DolphinGeneralConfigModule::DolphinGeneralConfigModule(QWidget* parent, const QVariantList& args) : KCModule(parent), m_pages() { Q_UNUSED(args); setButtons(KCModule::Default | KCModule::Help); QVBoxLayout* topLayout = new QVBoxLayout(this); topLayout->setMargin(0); QTabWidget* tabWidget = new QTabWidget(this); // initialize 'Behavior' tab BehaviorSettingsPage* behaviorPage = new BehaviorSettingsPage(QUrl::fromLocalFile(QDir::homePath()), tabWidget); tabWidget->addTab(behaviorPage, i18nc("@title:tab Behavior settings", "Behavior")); connect(behaviorPage, &BehaviorSettingsPage::changed, this, static_cast(&DolphinGeneralConfigModule::changed)); // initialize 'Previews' tab PreviewsSettingsPage* previewsPage = new PreviewsSettingsPage(tabWidget); tabWidget->addTab(previewsPage, i18nc("@title:tab Previews settings", "Previews")); connect(previewsPage, &PreviewsSettingsPage::changed, this, static_cast(&DolphinGeneralConfigModule::changed)); // initialize 'Confirmations' tab ConfirmationsSettingsPage* confirmationsPage = new ConfirmationsSettingsPage(tabWidget); tabWidget->addTab(confirmationsPage, i18nc("@title:tab Confirmations settings", "Confirmations")); connect(confirmationsPage, &ConfirmationsSettingsPage::changed, this, static_cast(&DolphinGeneralConfigModule::changed)); m_pages.append(behaviorPage); m_pages.append(previewsPage); m_pages.append(confirmationsPage); topLayout->addWidget(tabWidget, 0, nullptr); } DolphinGeneralConfigModule::~DolphinGeneralConfigModule() { } void DolphinGeneralConfigModule::save() { foreach (SettingsPageBase* page, m_pages) { page->applySettings(); } } void DolphinGeneralConfigModule::defaults() { foreach (SettingsPageBase* page, m_pages) { page->applySettings(); } } #include "kcmdolphingeneral.moc" diff --git a/src/settings/kcm/kcmdolphinviewmodes.cpp b/src/settings/kcm/kcmdolphinviewmodes.cpp index af9b49026..71364bf5c 100644 --- a/src/settings/kcm/kcmdolphinviewmodes.cpp +++ b/src/settings/kcm/kcmdolphinviewmodes.cpp @@ -1,103 +1,102 @@ /*************************************************************************** * Copyright (C) 2008 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 "kcmdolphinviewmodes.h" #include #include #include #include #include #include #include -#include #include #include K_PLUGIN_FACTORY(KCMDolphinViewModesConfigFactory, registerPlugin(QStringLiteral("dolphinviewmodes"));) DolphinViewModesConfigModule::DolphinViewModesConfigModule(QWidget* parent, const QVariantList& args) : KCModule(parent), m_tabs() { Q_UNUSED(args); setButtons(KCModule::Default | KCModule::Help); QVBoxLayout* topLayout = new QVBoxLayout(this); topLayout->setMargin(0); QTabWidget* tabWidget = new QTabWidget(this); // Initialize 'Icons' tab ViewSettingsTab* iconsTab = new ViewSettingsTab(ViewSettingsTab::IconsMode, tabWidget); tabWidget->addTab(iconsTab, QIcon::fromTheme(QStringLiteral("view-list-icons")), i18nc("@title:tab", "Icons")); connect(iconsTab, &ViewSettingsTab::changed, this, &DolphinViewModesConfigModule::viewModeChanged); // Initialize 'Compact' tab ViewSettingsTab* compactTab = new ViewSettingsTab(ViewSettingsTab::CompactMode, tabWidget); tabWidget->addTab(compactTab, QIcon::fromTheme(QStringLiteral("view-list-details")), i18nc("@title:tab", "Compact")); connect(compactTab, &ViewSettingsTab::changed, this, &DolphinViewModesConfigModule::viewModeChanged); // Initialize 'Details' tab ViewSettingsTab* detailsTab = new ViewSettingsTab(ViewSettingsTab::DetailsMode, tabWidget); tabWidget->addTab(detailsTab, QIcon::fromTheme(QStringLiteral("view-list-tree")), i18nc("@title:tab", "Details")); connect(detailsTab, &ViewSettingsTab::changed, this, &DolphinViewModesConfigModule::viewModeChanged); m_tabs.append(iconsTab); m_tabs.append(compactTab); m_tabs.append(detailsTab); topLayout->addWidget(tabWidget, 0, nullptr); } DolphinViewModesConfigModule::~DolphinViewModesConfigModule() { } void DolphinViewModesConfigModule::save() { foreach (ViewSettingsTab* tab, m_tabs) { tab->applySettings(); } reparseConfiguration(); } void DolphinViewModesConfigModule::defaults() { foreach (ViewSettingsTab* tab, m_tabs) { tab->restoreDefaultSettings(); } reparseConfiguration(); } void DolphinViewModesConfigModule::reparseConfiguration() { QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KonqMain"), QStringLiteral("org.kde.Konqueror.Main"), QStringLiteral("reparseConfiguration")); QDBusConnection::sessionBus().send(message); } void DolphinViewModesConfigModule::viewModeChanged() { emit changed(true); } #include "kcmdolphinviewmodes.moc" diff --git a/src/settings/serviceitemdelegate.cpp b/src/settings/serviceitemdelegate.cpp index e2398d7fb..3350061b6 100644 --- a/src/settings/serviceitemdelegate.cpp +++ b/src/settings/serviceitemdelegate.cpp @@ -1,131 +1,128 @@ /*************************************************************************** * 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 "serviceitemdelegate.h" -#include "dolphindebug.h" #include -#include #include "servicemodel.h" #include #include -#include #include ServiceItemDelegate::ServiceItemDelegate(QAbstractItemView* itemView, QObject* parent) : KWidgetItemDelegate(itemView, parent) { } ServiceItemDelegate::~ServiceItemDelegate() { } QSize ServiceItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); const QStyle *style = itemView()->style(); const int buttonHeight = style->pixelMetric(QStyle::PM_ButtonMargin) * 2 + style->pixelMetric(QStyle::PM_ButtonIconSize); const int fontHeight = option.fontMetrics.height(); return QSize(100, qMax(buttonHeight, fontHeight)); } void ServiceItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(index); painter->save(); itemView()->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter); if (option.state & QStyle::State_Selected) { painter->setPen(option.palette.highlightedText().color()); } painter->restore(); } QList ServiceItemDelegate::createItemWidgets(const QModelIndex&) const { QCheckBox* checkBox = new QCheckBox(); QPalette palette = checkBox->palette(); palette.setColor(QPalette::WindowText, palette.color(QPalette::Text)); checkBox->setPalette(palette); connect(checkBox, &QCheckBox::clicked, this, &ServiceItemDelegate::slotCheckBoxClicked); QPushButton* configureButton = new QPushButton(); connect(configureButton, &QPushButton::clicked, this, &ServiceItemDelegate::slotConfigureButtonClicked); return {checkBox, configureButton}; } void ServiceItemDelegate::updateItemWidgets(const QList widgets, const QStyleOptionViewItem& option, const QPersistentModelIndex& index) const { QCheckBox* checkBox = static_cast(widgets[0]); QPushButton *configureButton = static_cast(widgets[1]); const int itemHeight = sizeHint(option, index).height(); // Update the checkbox showing the service name and icon const QAbstractItemModel* model = index.model(); checkBox->setText(model->data(index).toString()); const QString iconName = model->data(index, Qt::DecorationRole).toString(); if (!iconName.isEmpty()) { checkBox->setIcon(QIcon::fromTheme(iconName)); } checkBox->setChecked(model->data(index, Qt::CheckStateRole).toBool()); const bool configurable = model->data(index, ServiceModel::ConfigurableRole).toBool(); int checkBoxWidth = option.rect.width(); if (configurable) { checkBoxWidth -= configureButton->sizeHint().width(); } checkBox->resize(checkBoxWidth, checkBox->sizeHint().height()); checkBox->move(0, (itemHeight - checkBox->height()) / 2); // Update the configuration button if (configurable) { configureButton->setEnabled(checkBox->isChecked()); configureButton->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); configureButton->resize(configureButton->sizeHint()); configureButton->move(option.rect.right() - configureButton->width(), (itemHeight - configureButton->height()) / 2); } configureButton->setVisible(configurable); } void ServiceItemDelegate::slotCheckBoxClicked(bool checked) { QAbstractItemModel* model = const_cast(focusedIndex().model()); model->setData(focusedIndex(), checked, Qt::CheckStateRole); } void ServiceItemDelegate::slotConfigureButtonClicked() { emit requestServiceConfiguration(focusedIndex()); } diff --git a/src/settings/services/servicessettingspage.cpp b/src/settings/services/servicessettingspage.cpp index a0d9effc8..63f0c323b 100644 --- a/src/settings/services/servicessettingspage.cpp +++ b/src/settings/services/servicessettingspage.cpp @@ -1,289 +1,282 @@ /*************************************************************************** * Copyright (C) 2009-2010 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 "servicessettingspage.h" #include "dolphin_generalsettings.h" #include "dolphin_versioncontrolsettings.h" -#include -#include #include #include #include #include #include #include #include #include #include -#include #include #include -#include #include -#include #include #include -#include #include -#include namespace { const bool ShowDeleteDefault = false; const char VersionControlServicePrefix[] = "_version_control_"; const char DeleteService[] = "_delete"; const char CopyToMoveToService[] ="_copy_to_move_to"; } ServicesSettingsPage::ServicesSettingsPage(QWidget* parent) : SettingsPageBase(parent), m_initialized(false), m_serviceModel(nullptr), m_sortModel(nullptr), m_listView(nullptr), m_enabledVcsPlugins() { QVBoxLayout* topLayout = new QVBoxLayout(this); QLabel* label = new QLabel(i18nc("@label:textbox", "Select which services should " "be shown in the context menu:"), this); label->setWordWrap(true); m_listView = new QListView(this); ServiceItemDelegate* delegate = new ServiceItemDelegate(m_listView, m_listView); m_serviceModel = new ServiceModel(this); m_sortModel = new QSortFilterProxyModel(this); m_sortModel->setSourceModel(m_serviceModel); m_sortModel->setSortRole(Qt::DisplayRole); m_listView->setModel(m_sortModel); m_listView->setItemDelegate(delegate); m_listView->setVerticalScrollMode(QListView::ScrollPerPixel); connect(m_listView, &QListView::clicked, this, &ServicesSettingsPage::changed); KNS3::Button* downloadButton = new KNS3::Button(i18nc("@action:button", "Download New Services..."), QStringLiteral("servicemenu.knsrc"), this); connect(downloadButton, &KNS3::Button::dialogFinished, this, &ServicesSettingsPage::loadServices); topLayout->addWidget(label); topLayout->addWidget(m_listView); topLayout->addWidget(downloadButton); m_enabledVcsPlugins = VersionControlSettings::enabledPlugins(); qSort(m_enabledVcsPlugins); } ServicesSettingsPage::~ServicesSettingsPage() { } void ServicesSettingsPage::applySettings() { if (!m_initialized) { return; } KConfig config(QStringLiteral("kservicemenurc"), KConfig::NoGlobals); KConfigGroup showGroup = config.group("Show"); QStringList enabledPlugins; const QAbstractItemModel* model = m_listView->model(); for (int i = 0; i < model->rowCount(); ++i) { const QModelIndex index = model->index(i, 0); const QString service = model->data(index, ServiceModel::DesktopEntryNameRole).toString(); const bool checked = model->data(index, Qt::CheckStateRole).toBool(); if (service.startsWith(VersionControlServicePrefix)) { if (checked) { enabledPlugins.append(model->data(index, Qt::DisplayRole).toString()); } } else if (service == QLatin1String(DeleteService)) { KSharedConfig::Ptr globalConfig = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::NoGlobals); KConfigGroup configGroup(globalConfig, "KDE"); configGroup.writeEntry("ShowDeleteCommand", checked); configGroup.sync(); } else if (service == QLatin1String(CopyToMoveToService)) { GeneralSettings::setShowCopyMoveMenu(checked); GeneralSettings::self()->save(); } else { showGroup.writeEntry(service, checked); } } showGroup.sync(); if (m_enabledVcsPlugins != enabledPlugins) { VersionControlSettings::setEnabledPlugins(enabledPlugins); VersionControlSettings::self()->save(); KMessageBox::information(window(), i18nc("@info", "Dolphin must be restarted to apply the " "updated version control systems settings."), QString(), // default title QStringLiteral("ShowVcsRestartInformation")); } } void ServicesSettingsPage::restoreDefaults() { QAbstractItemModel* model = m_listView->model(); for (int i = 0; i < model->rowCount(); ++i) { const QModelIndex index = model->index(i, 0); const QString service = model->data(index, ServiceModel::DesktopEntryNameRole).toString(); const bool checked = !service.startsWith(VersionControlServicePrefix) && service != QLatin1String(DeleteService) && service != QLatin1String(CopyToMoveToService); model->setData(index, checked, Qt::CheckStateRole); } } void ServicesSettingsPage::showEvent(QShowEvent* event) { if (!event->spontaneous() && !m_initialized) { loadServices(); loadVersionControlSystems(); // Add "Show 'Delete' command" as service KSharedConfig::Ptr globalConfig = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::IncludeGlobals); KConfigGroup configGroup(globalConfig, "KDE"); addRow(QStringLiteral("edit-delete"), i18nc("@option:check", "Delete"), DeleteService, configGroup.readEntry("ShowDeleteCommand", ShowDeleteDefault)); // Add "Show 'Copy To' and 'Move To' commands" as service addRow(QStringLiteral("edit-copy"), i18nc("@option:check", "'Copy To' and 'Move To' commands"), CopyToMoveToService, GeneralSettings::showCopyMoveMenu()); m_sortModel->sort(Qt::DisplayRole); m_initialized = true; } SettingsPageBase::showEvent(event); } void ServicesSettingsPage::loadServices() { const KConfig config(QStringLiteral("kservicemenurc"), KConfig::NoGlobals); const KConfigGroup showGroup = config.group("Show"); // Load generic services const KService::List entries = KServiceTypeTrader::self()->query(QStringLiteral("KonqPopupMenu/Plugin")); foreach (const KService::Ptr& service, entries) { const QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kservices5/" % service->entryPath()); const QList serviceActions = KDesktopFileActions::userDefinedServices(file, true); KDesktopFile desktopFile(file); const QString subMenuName = desktopFile.desktopGroup().readEntry("X-KDE-Submenu"); foreach (const KServiceAction& action, serviceActions) { const QString serviceName = action.name(); const bool addService = !action.noDisplay() && !action.isSeparator() && !isInServicesList(serviceName); if (addService) { const QString itemName = subMenuName.isEmpty() ? action.text() : i18nc("@item:inmenu", "%1: %2", subMenuName, action.text()); const bool checked = showGroup.readEntry(serviceName, true); addRow(action.icon(), itemName, serviceName, checked); } } } // Load service plugins that implement the KFileItemActionPlugin interface const KService::List pluginServices = KServiceTypeTrader::self()->query(QStringLiteral("KFileItemAction/Plugin")); foreach (const KService::Ptr& service, pluginServices) { const QString desktopEntryName = service->desktopEntryName(); if (!isInServicesList(desktopEntryName)) { const bool checked = showGroup.readEntry(desktopEntryName, true); addRow(service->icon(), service->name(), desktopEntryName, checked); } } // Load JSON-based plugins that implement the KFileItemActionPlugin interface const auto jsonPlugins = KPluginLoader::findPlugins(QStringLiteral("kf5/kfileitemaction"), [](const KPluginMetaData& metaData) { return metaData.serviceTypes().contains(QStringLiteral("KFileItemAction/Plugin")); }); foreach (const auto& jsonMetadata, jsonPlugins) { const QString desktopEntryName = jsonMetadata.pluginId(); if (!isInServicesList(desktopEntryName)) { const bool checked = showGroup.readEntry(desktopEntryName, true); addRow(jsonMetadata.iconName(), jsonMetadata.name(), desktopEntryName, checked); } } m_sortModel->sort(Qt::DisplayRole); } void ServicesSettingsPage::loadVersionControlSystems() { const QStringList enabledPlugins = VersionControlSettings::enabledPlugins(); // Create a checkbox for each available version control plugin const KService::List pluginServices = KServiceTypeTrader::self()->query(QStringLiteral("FileViewVersionControlPlugin")); for (KService::List::ConstIterator it = pluginServices.constBegin(); it != pluginServices.constEnd(); ++it) { const QString pluginName = (*it)->name(); addRow(QStringLiteral("code-class"), pluginName, VersionControlServicePrefix + pluginName, enabledPlugins.contains(pluginName)); } m_sortModel->sort(Qt::DisplayRole); } bool ServicesSettingsPage::isInServicesList(const QString& service) const { for (int i = 0; i < m_serviceModel->rowCount(); ++i) { const QModelIndex index = m_serviceModel->index(i, 0); if (m_serviceModel->data(index, ServiceModel::DesktopEntryNameRole).toString() == service) { return true; } } return false; } void ServicesSettingsPage::addRow(const QString& icon, const QString& text, const QString& value, bool checked) { m_serviceModel->insertRow(0); const QModelIndex index = m_serviceModel->index(0, 0); m_serviceModel->setData(index, icon, Qt::DecorationRole); m_serviceModel->setData(index, text, Qt::DisplayRole); m_serviceModel->setData(index, value, ServiceModel::DesktopEntryNameRole); m_serviceModel->setData(index, checked, Qt::CheckStateRole); } diff --git a/src/settings/startup/startupsettingspage.cpp b/src/settings/startup/startupsettingspage.cpp index b216181d8..3e1fe7c4e 100644 --- a/src/settings/startup/startupsettingspage.cpp +++ b/src/settings/startup/startupsettingspage.cpp @@ -1,199 +1,196 @@ /*************************************************************************** * Copyright (C) 2008 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 "startupsettingspage.h" #include "global.h" #include "dolphinmainwindow.h" #include "dolphinviewcontainer.h" #include "dolphin_generalsettings.h" #include #include #include #include #include #include #include #include -#include #include -#include "views/dolphinview.h" - StartupSettingsPage::StartupSettingsPage(const QUrl& url, QWidget* parent) : SettingsPageBase(parent), m_url(url), m_homeUrl(nullptr), m_splitView(nullptr), m_editableUrl(nullptr), m_showFullPath(nullptr), m_filterBar(nullptr), m_showFullPathInTitlebar(nullptr) { QVBoxLayout* topLayout = new QVBoxLayout(this); QWidget* vBox = new QWidget(this); QVBoxLayout *vBoxLayout = new QVBoxLayout(vBox); vBoxLayout->setMargin(0); vBoxLayout->setAlignment(Qt::AlignTop); // create 'Home URL' editor QGroupBox* homeBox = new QGroupBox(i18nc("@title:group", "Home Folder"), vBox); vBoxLayout->addWidget(homeBox); QWidget* homeUrlBox = new QWidget(homeBox); QHBoxLayout *homeUrlBoxLayout = new QHBoxLayout(homeUrlBox); homeUrlBoxLayout->setMargin(0); QLabel* homeUrlLabel = new QLabel(i18nc("@label:textbox", "Location:"), homeUrlBox); homeUrlBoxLayout->addWidget(homeUrlLabel); m_homeUrl = new QLineEdit(homeUrlBox); homeUrlBoxLayout->addWidget(m_homeUrl); m_homeUrl->setClearButtonEnabled(true); QPushButton* selectHomeUrlButton = new QPushButton(QIcon::fromTheme(QStringLiteral("folder-open")), QString(), homeUrlBox); homeUrlBoxLayout->addWidget(selectHomeUrlButton); #ifndef QT_NO_ACCESSIBILITY selectHomeUrlButton->setAccessibleName(i18nc("@action:button", "Select Home Location")); #endif connect(selectHomeUrlButton, &QPushButton::clicked, this, &StartupSettingsPage::selectHomeUrl); QWidget* buttonBox = new QWidget(homeBox); QHBoxLayout *buttonBoxLayout = new QHBoxLayout(buttonBox); buttonBoxLayout->setMargin(0); QPushButton* useCurrentButton = new QPushButton(i18nc("@action:button", "Use Current Location"), buttonBox); buttonBoxLayout->addWidget(useCurrentButton); connect(useCurrentButton, &QPushButton::clicked, this, &StartupSettingsPage::useCurrentLocation); QPushButton* useDefaultButton = new QPushButton(i18nc("@action:button", "Use Default Location"), buttonBox); buttonBoxLayout->addWidget(useDefaultButton); connect(useDefaultButton, &QPushButton::clicked, this, &StartupSettingsPage::useDefaultLocation); QVBoxLayout* homeBoxLayout = new QVBoxLayout(homeBox); homeBoxLayout->addWidget(homeUrlBox); homeBoxLayout->addWidget(buttonBox); // create 'Split view', 'Show full path', 'Editable location' and 'Filter bar' checkboxes m_splitView = new QCheckBox(i18nc("@option:check Startup Settings", "Split view mode"), vBox); vBoxLayout->addWidget(m_splitView); m_editableUrl = new QCheckBox(i18nc("@option:check Startup Settings", "Editable location bar"), vBox); vBoxLayout->addWidget(m_editableUrl); m_showFullPath = new QCheckBox(i18nc("@option:check Startup Settings", "Show full path inside location bar"), vBox); vBoxLayout->addWidget(m_showFullPath); m_filterBar = new QCheckBox(i18nc("@option:check Startup Settings", "Show filter bar"), vBox); vBoxLayout->addWidget(m_filterBar); m_showFullPathInTitlebar = new QCheckBox(i18nc("@option:check Startup Settings", "Show full path in title bar"), vBox); vBoxLayout->addWidget(m_showFullPathInTitlebar); // Add a dummy widget with no restriction regarding // a vertical resizing. This assures that the dialog layout // is not stretched vertically. new QWidget(vBox); topLayout->addWidget(vBox); loadSettings(); connect(m_homeUrl, &QLineEdit::textChanged, this, &StartupSettingsPage::slotSettingsChanged); connect(m_splitView, &QCheckBox::toggled, this, &StartupSettingsPage::slotSettingsChanged); connect(m_editableUrl, &QCheckBox::toggled, this, &StartupSettingsPage::slotSettingsChanged); connect(m_showFullPath, &QCheckBox::toggled, this, &StartupSettingsPage::slotSettingsChanged); connect(m_filterBar, &QCheckBox::toggled, this, &StartupSettingsPage::slotSettingsChanged); connect(m_showFullPathInTitlebar, &QCheckBox::toggled, this, &StartupSettingsPage::slotSettingsChanged); } StartupSettingsPage::~StartupSettingsPage() { } void StartupSettingsPage::applySettings() { GeneralSettings* settings = GeneralSettings::self(); const QUrl url(QUrl::fromUserInput(m_homeUrl->text(), QString(), QUrl::AssumeLocalFile)); KFileItem fileItem(url); if ((url.isValid() && fileItem.isDir()) || (url.scheme() == QLatin1String("timeline"))) { settings->setHomeUrl(url.toDisplayString(QUrl::PreferLocalFile)); } else { KMessageBox::error(this, i18nc("@info", "The location for the home folder is invalid or does not exist, it will not be applied.")); } settings->setSplitView(m_splitView->isChecked()); settings->setEditableUrl(m_editableUrl->isChecked()); settings->setShowFullPath(m_showFullPath->isChecked()); settings->setFilterBar(m_filterBar->isChecked()); settings->setShowFullPathInTitlebar(m_showFullPathInTitlebar->isChecked()); settings->save(); } void StartupSettingsPage::restoreDefaults() { GeneralSettings* settings = GeneralSettings::self(); settings->useDefaults(true); loadSettings(); settings->useDefaults(false); } void StartupSettingsPage::slotSettingsChanged() { // Provide a hint that the startup settings have been changed. This allows the views // to apply the startup settings only if they have been explicitly changed by the user // (see bug #254947). GeneralSettings::setModifiedStartupSettings(true); emit changed(); } void StartupSettingsPage::selectHomeUrl() { const QUrl homeUrl(QUrl::fromUserInput(m_homeUrl->text(), QString(), QUrl::AssumeLocalFile)); QUrl url = QFileDialog::getExistingDirectoryUrl(this, QString(), homeUrl); if (!url.isEmpty()) { m_homeUrl->setText(url.toDisplayString(QUrl::PreferLocalFile)); slotSettingsChanged(); } } void StartupSettingsPage::useCurrentLocation() { m_homeUrl->setText(m_url.toDisplayString(QUrl::PreferLocalFile)); } void StartupSettingsPage::useDefaultLocation() { m_homeUrl->setText(QDir::homePath()); } void StartupSettingsPage::loadSettings() { const QUrl url(Dolphin::homeUrl()); m_homeUrl->setText(url.toDisplayString(QUrl::PreferLocalFile)); m_splitView->setChecked(GeneralSettings::splitView()); m_editableUrl->setChecked(GeneralSettings::editableUrl()); m_showFullPath->setChecked(GeneralSettings::showFullPath()); m_filterBar->setChecked(GeneralSettings::filterBar()); m_showFullPathInTitlebar->setChecked(GeneralSettings::showFullPathInTitlebar()); } diff --git a/src/settings/viewmodes/viewsettingstab.cpp b/src/settings/viewmodes/viewsettingstab.cpp index 98f818c96..e79ac0f4c 100644 --- a/src/settings/viewmodes/viewsettingstab.cpp +++ b/src/settings/viewmodes/viewsettingstab.cpp @@ -1,293 +1,292 @@ /*************************************************************************** * Copyright (C) 2008-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 "viewsettingstab.h" #include "dolphinfontrequester.h" #include "dolphin_compactmodesettings.h" #include "dolphin_detailsmodesettings.h" #include "dolphin_iconsmodesettings.h" #include #include #include #include #include -#include #include #include #include #include ViewSettingsTab::ViewSettingsTab(Mode mode, QWidget* parent) : QWidget(parent), m_mode(mode), m_defaultSizeSlider(nullptr), m_previewSizeSlider(nullptr), m_fontRequester(nullptr), m_widthBox(nullptr), m_maxLinesBox(nullptr), m_expandableFolders(nullptr) { QVBoxLayout* topLayout = new QVBoxLayout(this); // Create "Icon Size" group QGroupBox* iconSizeGroup = new QGroupBox(this); iconSizeGroup->setTitle(i18nc("@title:group", "Icon Size")); const int minRange = ZoomLevelInfo::minimumLevel(); const int maxRange = ZoomLevelInfo::maximumLevel(); QLabel* defaultLabel = new QLabel(i18nc("@label:listbox", "Default:"), this); m_defaultSizeSlider = new QSlider(Qt::Horizontal, this); m_defaultSizeSlider->setPageStep(1); m_defaultSizeSlider->setTickPosition(QSlider::TicksBelow); m_defaultSizeSlider->setRange(minRange, maxRange); connect(m_defaultSizeSlider, &QSlider::valueChanged, this, &ViewSettingsTab::slotDefaultSliderMoved); QLabel* previewLabel = new QLabel(i18nc("@label:listbox", "Preview:"), this); m_previewSizeSlider = new QSlider(Qt::Horizontal, this); m_previewSizeSlider->setPageStep(1); m_previewSizeSlider->setTickPosition(QSlider::TicksBelow); m_previewSizeSlider->setRange(minRange, maxRange); connect(m_previewSizeSlider, &QSlider::valueChanged, this, &ViewSettingsTab::slotPreviewSliderMoved); QGridLayout* layout = new QGridLayout(iconSizeGroup); layout->addWidget(defaultLabel, 0, 0, Qt::AlignRight); layout->addWidget(m_defaultSizeSlider, 0, 1); layout->addWidget(previewLabel, 1, 0, Qt::AlignRight); layout->addWidget(m_previewSizeSlider, 1, 1); // Create "Text" group QGroupBox* textGroup = new QGroupBox(i18nc("@title:group", "Text"), this); QLabel* fontLabel = new QLabel(i18nc("@label:listbox", "Font:"), textGroup); m_fontRequester = new DolphinFontRequester(textGroup); QGridLayout* textGroupLayout = new QGridLayout(textGroup); textGroupLayout->addWidget(fontLabel, 0, 0, Qt::AlignRight); textGroupLayout->addWidget(m_fontRequester, 0, 1); switch (m_mode) { case IconsMode: { QLabel* widthLabel = new QLabel(i18nc("@label:listbox", "Width:"), textGroup); m_widthBox = new KComboBox(textGroup); m_widthBox->addItem(i18nc("@item:inlistbox Text width", "Small")); m_widthBox->addItem(i18nc("@item:inlistbox Text width", "Medium")); m_widthBox->addItem(i18nc("@item:inlistbox Text width", "Large")); m_widthBox->addItem(i18nc("@item:inlistbox Text width", "Huge")); QLabel* maxLinesLabel = new QLabel(i18nc("@label:listbox", "Maximum lines:"), textGroup); m_maxLinesBox = new KComboBox(textGroup); m_maxLinesBox->addItem(i18nc("@item:inlistbox Maximum lines", "Unlimited")); m_maxLinesBox->addItem(i18nc("@item:inlistbox Maximum lines", "1")); m_maxLinesBox->addItem(i18nc("@item:inlistbox Maximum lines", "2")); m_maxLinesBox->addItem(i18nc("@item:inlistbox Maximum lines", "3")); m_maxLinesBox->addItem(i18nc("@item:inlistbox Maximum lines", "4")); m_maxLinesBox->addItem(i18nc("@item:inlistbox Maximum lines", "5")); textGroupLayout->addWidget(widthLabel, 2, 0, Qt::AlignRight); textGroupLayout->addWidget(m_widthBox, 2, 1); textGroupLayout->addWidget(maxLinesLabel, 3, 0, Qt::AlignRight); textGroupLayout->addWidget(m_maxLinesBox, 3, 1); break; } case CompactMode: { QLabel* maxWidthLabel = new QLabel(i18nc("@label:listbox", "Maximum width:"), textGroup); m_widthBox = new KComboBox(textGroup); m_widthBox->addItem(i18nc("@item:inlistbox Maximum width", "Unlimited")); m_widthBox->addItem(i18nc("@item:inlistbox Maximum width", "Small")); m_widthBox->addItem(i18nc("@item:inlistbox Maximum width", "Medium")); m_widthBox->addItem(i18nc("@item:inlistbox Maximum width", "Large")); textGroupLayout->addWidget(maxWidthLabel, 2, 0, Qt::AlignRight); textGroupLayout->addWidget(m_widthBox, 2, 1); break; } case DetailsMode: m_expandableFolders = new QCheckBox(i18nc("@option:check", "Expandable folders"), this); break; default: break; } topLayout->addWidget(iconSizeGroup); topLayout->addWidget(textGroup); if (m_expandableFolders) { topLayout->addWidget(m_expandableFolders); } topLayout->addStretch(1); loadSettings(); connect(m_defaultSizeSlider, &QSlider::valueChanged, this, &ViewSettingsTab::changed); connect(m_previewSizeSlider, &QSlider::valueChanged, this, &ViewSettingsTab::changed); connect(m_fontRequester, &DolphinFontRequester::changed, this, &ViewSettingsTab::changed); switch (m_mode) { case IconsMode: connect(m_widthBox, static_cast(&KComboBox::currentIndexChanged), this, &ViewSettingsTab::changed); connect(m_maxLinesBox, static_cast(&KComboBox::currentIndexChanged), this, &ViewSettingsTab::changed); break; case CompactMode: connect(m_widthBox, static_cast(&KComboBox::currentIndexChanged), this, &ViewSettingsTab::changed); break; case DetailsMode: connect(m_expandableFolders, &QCheckBox::toggled, this, &ViewSettingsTab::changed); break; default: break; } } ViewSettingsTab::~ViewSettingsTab() { } void ViewSettingsTab::applySettings() { const QFont font = m_fontRequester->currentFont(); const bool useSystemFont = (m_fontRequester->mode() == DolphinFontRequester::SystemFont); switch (m_mode) { case IconsMode: IconsModeSettings::setTextWidthIndex(m_widthBox->currentIndex()); IconsModeSettings::setMaximumTextLines(m_maxLinesBox->currentIndex()); break; case CompactMode: CompactModeSettings::setMaximumTextWidthIndex(m_widthBox->currentIndex()); break; case DetailsMode: DetailsModeSettings::setExpandableFolders(m_expandableFolders->isChecked()); break; default: break; } ViewModeSettings settings(viewMode()); const int iconSize = ZoomLevelInfo::iconSizeForZoomLevel(m_defaultSizeSlider->value()); const int previewSize = ZoomLevelInfo::iconSizeForZoomLevel(m_previewSizeSlider->value()); settings.setIconSize(iconSize); settings.setPreviewSize(previewSize); settings.setUseSystemFont(useSystemFont); settings.setFontFamily(font.family()); settings.setFontSize(font.pointSizeF()); settings.setItalicFont(font.italic()); settings.setFontWeight(font.weight()); settings.save(); } void ViewSettingsTab::restoreDefaultSettings() { KConfigSkeleton* settings = nullptr; switch (m_mode) { case IconsMode: settings = IconsModeSettings::self(); break; case CompactMode: settings = CompactModeSettings::self(); break; case DetailsMode: settings = DetailsModeSettings::self(); break; default: Q_ASSERT(false); break; } settings->useDefaults(true); loadSettings(); settings->useDefaults(false); } void ViewSettingsTab::loadSettings() { switch (m_mode) { case IconsMode: m_widthBox->setCurrentIndex(IconsModeSettings::textWidthIndex()); m_maxLinesBox->setCurrentIndex(IconsModeSettings::maximumTextLines()); break; case CompactMode: m_widthBox->setCurrentIndex(CompactModeSettings::maximumTextWidthIndex()); break; case DetailsMode: m_expandableFolders->setChecked(DetailsModeSettings::expandableFolders()); break; default: break; } const ViewModeSettings settings(viewMode()); const QSize iconSize(settings.iconSize(), settings.iconSize()); m_defaultSizeSlider->setValue(ZoomLevelInfo::zoomLevelForIconSize(iconSize)); const QSize previewSize(settings.previewSize(), settings.previewSize()); m_previewSizeSlider->setValue(ZoomLevelInfo::zoomLevelForIconSize(previewSize)); m_fontRequester->setMode(settings.useSystemFont() ? DolphinFontRequester::SystemFont : DolphinFontRequester::CustomFont); QFont font(settings.fontFamily(), qRound(settings.fontSize())); font.setItalic(settings.italicFont()); font.setWeight(settings.fontWeight()); font.setPointSizeF(settings.fontSize()); m_fontRequester->setCustomFont(font); } ViewModeSettings::ViewMode ViewSettingsTab::viewMode() const { ViewModeSettings::ViewMode mode; switch (m_mode) { case ViewSettingsTab::IconsMode: mode = ViewModeSettings::IconsMode; break; case ViewSettingsTab::CompactMode: mode = ViewModeSettings::CompactMode; break; case ViewSettingsTab::DetailsMode: mode = ViewModeSettings::DetailsMode; break; default: mode = ViewModeSettings::IconsMode; Q_ASSERT(false); break; } return mode; } void ViewSettingsTab::slotDefaultSliderMoved(int value) { showToolTip(m_defaultSizeSlider, value); } void ViewSettingsTab::slotPreviewSliderMoved(int value) { showToolTip(m_previewSizeSlider, value); } void ViewSettingsTab::showToolTip(QSlider* slider, int value) { const int size = ZoomLevelInfo::iconSizeForZoomLevel(value); slider->setToolTip(i18ncp("@info:tooltip", "Size: 1 pixel", "Size: %1 pixels", size)); if (!slider->isVisible()) { return; } QPoint global = slider->rect().topLeft(); global.ry() += slider->height() / 2; QHelpEvent toolTipEvent(QEvent::ToolTip, QPoint(0, 0), slider->mapToGlobal(global)); QApplication::sendEvent(slider, &toolTipEvent); } diff --git a/src/settings/viewpropertiesdialog.cpp b/src/settings/viewpropertiesdialog.cpp index 8c5fccdef..4f005e0da 100644 --- a/src/settings/viewpropertiesdialog.cpp +++ b/src/settings/viewpropertiesdialog.cpp @@ -1,417 +1,411 @@ /*************************************************************************** * Copyright (C) 2006 by Peter Penz * * peter.penz@gmx.at * * * * 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 "viewpropertiesdialog.h" #include "additionalinfodialog.h" #include "kitemviews/kfileitemmodel.h" #include "views/dolphinview.h" #include "dolphin_generalsettings.h" #include "dolphin_iconsmodesettings.h" #include "viewpropsprogressinfo.h" -#include - #include #include -#include #include -#include #include #include #include #include #include #include #include #include -#include -#include #include ViewPropertiesDialog::ViewPropertiesDialog(DolphinView* dolphinView) : QDialog(dolphinView), m_isDirty(false), m_dolphinView(dolphinView), m_viewProps(nullptr), m_viewMode(nullptr), m_sortOrder(nullptr), m_sorting(nullptr), m_sortFoldersFirst(nullptr), m_previewsShown(nullptr), m_showInGroups(nullptr), m_showHiddenFiles(nullptr), m_additionalInfo(nullptr), m_applyToCurrentFolder(nullptr), m_applyToSubFolders(nullptr), m_applyToAllFolders(nullptr), m_useAsDefault(nullptr) { Q_ASSERT(dolphinView); const bool useGlobalViewProps = GeneralSettings::globalViewProps(); setWindowTitle(i18nc("@title:window", "View Properties")); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); const QUrl& url = dolphinView->url(); m_viewProps = new ViewProperties(url); m_viewProps->setAutoSaveEnabled(false); auto layout = new QVBoxLayout(this); setLayout(layout); auto propsGrid = new QWidget(this); layout->addWidget(propsGrid); // create 'Properties' group containing view mode, sorting, sort order and show hidden files QWidget* propsBox = this; if (!useGlobalViewProps) { propsBox = new QGroupBox(i18nc("@title:group", "Properties"), this); layout->addWidget(propsBox); } QLabel* viewModeLabel = new QLabel(i18nc("@label:listbox", "View mode:"), propsGrid); m_viewMode = new KComboBox(propsGrid); m_viewMode->addItem(QIcon::fromTheme(QStringLiteral("view-list-icons")), i18nc("@item:inlistbox", "Icons"), DolphinView::IconsView); m_viewMode->addItem(QIcon::fromTheme(QStringLiteral("view-list-details")), i18nc("@item:inlistbox", "Compact"), DolphinView::CompactView); m_viewMode->addItem(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18nc("@item:inlistbox", "Details"), DolphinView::DetailsView); QLabel* sortingLabel = new QLabel(i18nc("@label:listbox", "Sorting:"), propsGrid); QWidget* sortingBox = new QWidget(propsGrid); m_sortOrder = new KComboBox(sortingBox); m_sortOrder->addItem(i18nc("@item:inlistbox Sort", "Ascending")); m_sortOrder->addItem(i18nc("@item:inlistbox Sort", "Descending")); m_sorting = new KComboBox(sortingBox); const QList rolesInfo = KFileItemModel::rolesInformation(); foreach (const KFileItemModel::RoleInfo& info, rolesInfo) { m_sorting->addItem(info.translation, info.role); } m_sortFoldersFirst = new QCheckBox(i18nc("@option:check", "Show folders first")); m_previewsShown = new QCheckBox(i18nc("@option:check", "Show preview")); m_showInGroups = new QCheckBox(i18nc("@option:check", "Show in groups")); m_showHiddenFiles = new QCheckBox(i18nc("@option:check", "Show hidden files")); m_additionalInfo = new QPushButton(i18nc("@action:button", "Additional Information")); QHBoxLayout* sortingLayout = new QHBoxLayout(); sortingLayout->setMargin(0); sortingLayout->addWidget(m_sortOrder); sortingLayout->addWidget(m_sorting); sortingBox->setLayout(sortingLayout); QGridLayout* propsGridLayout = new QGridLayout(propsGrid); propsGridLayout->addWidget(viewModeLabel, 0, 0, Qt::AlignRight); propsGridLayout->addWidget(m_viewMode, 0, 1); propsGridLayout->addWidget(sortingLabel, 1, 0, Qt::AlignRight); propsGridLayout->addWidget(sortingBox, 1, 1); QVBoxLayout* propsBoxLayout = propsBox == this ? layout : new QVBoxLayout(propsBox); propsBoxLayout->addWidget(propsGrid); propsBoxLayout->addWidget(m_sortFoldersFirst); propsBoxLayout->addWidget(m_previewsShown); propsBoxLayout->addWidget(m_showInGroups); propsBoxLayout->addWidget(m_showHiddenFiles); propsBoxLayout->addWidget(m_additionalInfo); connect(m_viewMode, static_cast(&KComboBox::currentIndexChanged), this, &ViewPropertiesDialog::slotViewModeChanged); connect(m_sorting, static_cast(&KComboBox::currentIndexChanged), this, &ViewPropertiesDialog::slotSortingChanged); connect(m_sortOrder, static_cast(&KComboBox::currentIndexChanged), this, &ViewPropertiesDialog::slotSortOrderChanged); connect(m_additionalInfo, &QPushButton::clicked, this, &ViewPropertiesDialog::configureAdditionalInfo); connect(m_sortFoldersFirst, &QCheckBox::clicked, this, &ViewPropertiesDialog::slotSortFoldersFirstChanged); connect(m_previewsShown, &QCheckBox::clicked, this, &ViewPropertiesDialog::slotShowPreviewChanged); connect(m_showInGroups, &QCheckBox::clicked, this, &ViewPropertiesDialog::slotGroupedSortingChanged); connect(m_showHiddenFiles, &QCheckBox::clicked, this, &ViewPropertiesDialog::slotShowHiddenFilesChanged); // Only show the following settings if the view properties are remembered // for each directory: if (!useGlobalViewProps) { // create 'Apply View Properties To' group QGroupBox* applyBox = new QGroupBox(i18nc("@title:group", "Apply View Properties To"), this); layout->addWidget(applyBox); m_applyToCurrentFolder = new QRadioButton(i18nc("@option:radio Apply View Properties To", "Current folder"), applyBox); m_applyToCurrentFolder->setChecked(true); m_applyToSubFolders = new QRadioButton(i18nc("@option:radio Apply View Properties To", "Current folder including all sub-folders"), applyBox); m_applyToAllFolders = new QRadioButton(i18nc("@option:radio Apply View Properties To", "All folders"), applyBox); QButtonGroup* applyGroup = new QButtonGroup(this); applyGroup->addButton(m_applyToCurrentFolder); applyGroup->addButton(m_applyToSubFolders); applyGroup->addButton(m_applyToAllFolders); QVBoxLayout* applyBoxLayout = new QVBoxLayout(applyBox); applyBoxLayout->addWidget(m_applyToCurrentFolder); applyBoxLayout->addWidget(m_applyToSubFolders); applyBoxLayout->addWidget(m_applyToAllFolders); m_useAsDefault = new QCheckBox(i18nc("@option:check", "Use these view properties as default"), this); layout->addWidget(m_useAsDefault); connect(m_applyToCurrentFolder, &QRadioButton::clicked, this, &ViewPropertiesDialog::markAsDirty); connect(m_applyToSubFolders, &QRadioButton::clicked, this, &ViewPropertiesDialog::markAsDirty); connect(m_applyToAllFolders, &QRadioButton::clicked, this, &ViewPropertiesDialog::markAsDirty); connect(m_useAsDefault, &QCheckBox::clicked, this, &ViewPropertiesDialog::markAsDirty); } layout->addStretch(); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply, this); connect(buttonBox, &QDialogButtonBox::accepted, this, &ViewPropertiesDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &ViewPropertiesDialog::reject); layout->addWidget(buttonBox); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setShortcut(Qt::CTRL + Qt::Key_Return); okButton->setDefault(true); auto applyButton = buttonBox->button(QDialogButtonBox::Apply); connect(applyButton, &QPushButton::clicked, this, &ViewPropertiesDialog::slotApply); connect(this, &ViewPropertiesDialog::isDirtyChanged, applyButton, [applyButton](bool isDirty) { applyButton->setEnabled(isDirty); }); const KConfigGroup dialogConfig(KSharedConfig::openConfig(QStringLiteral("dolphinrc")), "ViewPropertiesDialog"); KWindowConfig::restoreWindowSize(windowHandle(), dialogConfig); loadSettings(); } ViewPropertiesDialog::~ViewPropertiesDialog() { m_isDirty = false; delete m_viewProps; m_viewProps = nullptr; KConfigGroup dialogConfig(KSharedConfig::openConfig(QStringLiteral("dolphinrc")), "ViewPropertiesDialog"); KWindowConfig::saveWindowSize(windowHandle(), dialogConfig); } void ViewPropertiesDialog::accept() { applyViewProperties(); QDialog::accept(); } void ViewPropertiesDialog::slotApply() { applyViewProperties(); markAsDirty(false); } void ViewPropertiesDialog::slotViewModeChanged(int index) { const QVariant itemData = m_viewMode->itemData(index); const DolphinView::Mode viewMode = static_cast(itemData.toInt()); m_viewProps->setViewMode(viewMode); markAsDirty(true); } void ViewPropertiesDialog::slotSortingChanged(int index) { const QByteArray role = m_sorting->itemData(index).toByteArray(); m_viewProps->setSortRole(role); markAsDirty(true); } void ViewPropertiesDialog::slotSortOrderChanged(int index) { const Qt::SortOrder sortOrder = (index == 0) ? Qt::AscendingOrder : Qt::DescendingOrder; m_viewProps->setSortOrder(sortOrder); markAsDirty(true); } void ViewPropertiesDialog::slotGroupedSortingChanged() { m_viewProps->setGroupedSorting(m_showInGroups->isChecked()); markAsDirty(true); } void ViewPropertiesDialog::slotSortFoldersFirstChanged() { const bool foldersFirst = m_sortFoldersFirst->isChecked(); m_viewProps->setSortFoldersFirst(foldersFirst); markAsDirty(true); } void ViewPropertiesDialog::slotShowPreviewChanged() { const bool show = m_previewsShown->isChecked(); m_viewProps->setPreviewsShown(show); markAsDirty(true); } void ViewPropertiesDialog::slotShowHiddenFilesChanged() { const bool show = m_showHiddenFiles->isChecked(); m_viewProps->setHiddenFilesShown(show); markAsDirty(true); } void ViewPropertiesDialog::markAsDirty(bool isDirty) { if (m_isDirty != isDirty) { m_isDirty = isDirty; emit isDirtyChanged(isDirty); } } void ViewPropertiesDialog::configureAdditionalInfo() { QList visibleRoles = m_viewProps->visibleRoles(); const bool useDefaultRoles = (m_viewProps->viewMode() == DolphinView::DetailsView) && visibleRoles.isEmpty(); if (useDefaultRoles) { // Using the details view without any additional information (-> additional column) // makes no sense and leads to a usability problem as no viewport area is available // anymore. Hence as fallback provide at least a size and date column. visibleRoles.clear(); visibleRoles.append("text"); visibleRoles.append("size"); visibleRoles.append("modificationtime"); m_viewProps->setVisibleRoles(visibleRoles); } QPointer dialog = new AdditionalInfoDialog(this, visibleRoles); if (dialog->exec() == QDialog::Accepted) { m_viewProps->setVisibleRoles(dialog->visibleRoles()); markAsDirty(true); } delete dialog; } void ViewPropertiesDialog::applyViewProperties() { // if nothing changed in the dialog, we have nothing to apply if (!m_isDirty) { return; } const bool applyToSubFolders = m_applyToSubFolders && m_applyToSubFolders->isChecked(); if (applyToSubFolders) { const QString text(i18nc("@info", "The view properties of all sub-folders will be changed. Do you want to continue?")); if (KMessageBox::questionYesNo(this, text) == KMessageBox::No) { return; } ViewPropsProgressInfo* info = new ViewPropsProgressInfo(m_dolphinView, m_dolphinView->url(), *m_viewProps); info->setAttribute(Qt::WA_DeleteOnClose); info->setWindowModality(Qt::NonModal); info->show(); } const bool applyToAllFolders = m_applyToAllFolders && m_applyToAllFolders->isChecked(); // If the user selected 'Apply To All Folders' the view properties implicitely // are also used as default for new folders. const bool useAsDefault = applyToAllFolders || (m_useAsDefault && m_useAsDefault->isChecked()); if (useAsDefault) { // For directories where no .directory file is available, the .directory // file stored for the global view properties is used as fallback. To update // this file we temporary turn on the global view properties mode. Q_ASSERT(!GeneralSettings::globalViewProps()); GeneralSettings::setGlobalViewProps(true); ViewProperties defaultProps(m_dolphinView->url()); defaultProps.setDirProperties(*m_viewProps); defaultProps.save(); GeneralSettings::setGlobalViewProps(false); } if (applyToAllFolders) { const QString text(i18nc("@info", "The view properties of all folders will be changed. Do you want to continue?")); if (KMessageBox::questionYesNo(this, text) == KMessageBox::No) { return; } // Updating the global view properties time stamp in the general settings makes // all existing viewproperties invalid, as they have a smaller time stamp. GeneralSettings* settings = GeneralSettings::self(); settings->setViewPropsTimestamp(QDateTime::currentDateTime()); settings->save(); } m_dolphinView->setMode(m_viewProps->viewMode()); m_dolphinView->setSortRole(m_viewProps->sortRole()); m_dolphinView->setSortOrder(m_viewProps->sortOrder()); m_dolphinView->setSortFoldersFirst(m_viewProps->sortFoldersFirst()); m_dolphinView->setGroupedSorting(m_viewProps->groupedSorting()); m_dolphinView->setVisibleRoles(m_viewProps->visibleRoles()); m_dolphinView->setPreviewsShown(m_viewProps->previewsShown()); m_dolphinView->setHiddenFilesShown(m_viewProps->hiddenFilesShown()); m_viewProps->save(); markAsDirty(false); } void ViewPropertiesDialog::loadSettings() { // Load view mode switch (m_viewProps->viewMode()) { case DolphinView::IconsView: m_viewMode->setCurrentIndex(0); break; case DolphinView::CompactView: m_viewMode->setCurrentIndex(1); break; case DolphinView::DetailsView: m_viewMode->setCurrentIndex(2); break; default: break; } // Load sort order and sorting const int sortOrderIndex = (m_viewProps->sortOrder() == Qt::AscendingOrder) ? 0 : 1; m_sortOrder->setCurrentIndex(sortOrderIndex); const QList rolesInfo = KFileItemModel::rolesInformation(); int sortRoleIndex = 0; for (int i = 0; i < rolesInfo.count(); ++i) { if (rolesInfo[i].role == m_viewProps->sortRole()) { sortRoleIndex = i; break; } } m_sorting->setCurrentIndex(sortRoleIndex); m_sortFoldersFirst->setChecked(m_viewProps->sortFoldersFirst()); // Load show preview, show in groups and show hidden files settings m_previewsShown->setChecked(m_viewProps->previewsShown()); m_showInGroups->setChecked(m_viewProps->groupedSorting()); m_showHiddenFiles->setChecked(m_viewProps->hiddenFilesShown()); markAsDirty(false); } diff --git a/src/settings/viewpropsprogressinfo.cpp b/src/settings/viewpropsprogressinfo.cpp index 1878c8137..471ac01d0 100644 --- a/src/settings/viewpropsprogressinfo.cpp +++ b/src/settings/viewpropsprogressinfo.cpp @@ -1,153 +1,152 @@ /*************************************************************************** * Copyright (C) 2006 by Peter Penz * * peter.penz@gmx.at * * * * 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 "viewpropsprogressinfo.h" #include "applyviewpropsjob.h" #include #include #include #include #include #include #include #include -#include #include ViewPropsProgressInfo::ViewPropsProgressInfo(QWidget* parent, const QUrl& dir, const ViewProperties& viewProps) : QDialog(parent), m_dir(dir), m_viewProps(nullptr), m_label(nullptr), m_progressBar(nullptr), m_dirSizeJob(nullptr), m_applyViewPropsJob(nullptr), m_timer(nullptr) { const QSize minSize = minimumSize(); setMinimumSize(QSize(320, minSize.height())); setWindowTitle(i18nc("@title:window", "Applying View Properties")); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); m_viewProps = new ViewProperties(dir); m_viewProps->setDirProperties(viewProps); // the view properties are stored by the ViewPropsApplierJob, so prevent // that the view properties are saved twice: m_viewProps->setAutoSaveEnabled(false); auto layout = new QVBoxLayout(this); setLayout(layout); m_label = new QLabel(i18nc("@info:progress", "Counting folders: %1", 0), this); layout->addWidget(m_label); m_progressBar = new QProgressBar(this); m_progressBar->setMinimum(0); m_progressBar->setMaximum(0); m_progressBar->setValue(0); layout->addWidget(m_progressBar); layout->addStretch(); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel, this); connect(buttonBox, &QDialogButtonBox::accepted, this, &ViewPropsProgressInfo::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &ViewPropsProgressInfo::reject); layout->addWidget(buttonBox); // Use the directory size job to count the number of directories first. This // allows to give a progress indication for the user when applying the view // properties later. m_dirSizeJob = KIO::directorySize(dir); connect(m_dirSizeJob, &KIO::DirectorySizeJob::result, this, &ViewPropsProgressInfo::applyViewProperties); // The directory size job cannot emit any progress signal, as it is not aware // about the total number of directories. Therefor a timer is triggered, which // periodically updates the current directory count. m_timer = new QTimer(this); connect(m_timer, &QTimer::timeout, this, &ViewPropsProgressInfo::updateProgress); m_timer->start(300); } ViewPropsProgressInfo::~ViewPropsProgressInfo() { delete m_viewProps; m_viewProps = nullptr; } void ViewPropsProgressInfo::closeEvent(QCloseEvent* event) { m_timer->stop(); m_applyViewPropsJob = nullptr; QDialog::closeEvent(event); } void ViewPropsProgressInfo::reject() { if (m_dirSizeJob) { m_dirSizeJob->kill(); m_dirSizeJob = nullptr; } if (m_applyViewPropsJob) { m_applyViewPropsJob->kill(); m_applyViewPropsJob = nullptr; } QDialog::reject(); } void ViewPropsProgressInfo::updateProgress() { if (m_dirSizeJob) { const int subdirs = m_dirSizeJob->totalSubdirs(); m_label->setText(i18nc("@info:progress", "Counting folders: %1", subdirs)); } if (m_applyViewPropsJob) { const int progress = m_applyViewPropsJob->progress(); m_progressBar->setValue(progress); } } void ViewPropsProgressInfo::applyViewProperties() { if (m_dirSizeJob->error()) { return; } const int subdirs = m_dirSizeJob->totalSubdirs(); m_label->setText(i18nc("@info:progress", "Folders: %1", subdirs)); m_progressBar->setMaximum(subdirs); m_dirSizeJob = nullptr; m_applyViewPropsJob = new ApplyViewPropsJob(m_dir, *m_viewProps); connect(m_applyViewPropsJob, &ApplyViewPropsJob::result, this, &ViewPropsProgressInfo::close); } diff --git a/src/statusbar/dolphinstatusbar.cpp b/src/statusbar/dolphinstatusbar.cpp index 16683309d..14672dff0 100644 --- a/src/statusbar/dolphinstatusbar.cpp +++ b/src/statusbar/dolphinstatusbar.cpp @@ -1,341 +1,339 @@ /*************************************************************************** * Copyright (C) 2006-2012 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 "dolphinstatusbar.h" #include "dolphin_generalsettings.h" #include #include #include #include #include "statusbarspaceinfo.h" #include #include -#include #include #include -#include #include #include #include #include #include #include namespace { const int ResetToDefaultTimeout = 1000; } DolphinStatusBar::DolphinStatusBar(QWidget* parent) : QWidget(parent), m_text(), m_defaultText(), m_label(nullptr), m_spaceInfo(nullptr), m_zoomSlider(nullptr), m_progressBar(nullptr), m_stopButton(nullptr), m_progress(100), m_showProgressBarTimer(nullptr), m_resetToDefaultTextTimer(nullptr), m_textTimestamp() { // Initialize text label m_label = new KSqueezedTextLabel(m_text, this); m_label->setWordWrap(true); m_label->setTextFormat(Qt::PlainText); // Initialize zoom widget m_zoomSlider = new QSlider(Qt::Horizontal, this); m_zoomSlider->setAccessibleName(i18n("Zoom")); m_zoomSlider->setAccessibleDescription(i18nc("Description for zoom-slider (accessibility)", "Sets the size of the file icons.")); m_zoomSlider->setPageStep(1); m_zoomSlider->setRange(ZoomLevelInfo::minimumLevel(), ZoomLevelInfo::maximumLevel()); connect(m_zoomSlider, &QSlider::valueChanged, this, &DolphinStatusBar::zoomLevelChanged); connect(m_zoomSlider, &QSlider::valueChanged, this, &DolphinStatusBar::updateZoomSliderToolTip); connect(m_zoomSlider, &QSlider::sliderMoved, this, &DolphinStatusBar::showZoomSliderToolTip); // Initialize space information m_spaceInfo = new StatusBarSpaceInfo(this); // Initialize progress information m_stopButton = new QToolButton(this); m_stopButton->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); m_stopButton->setAccessibleName(i18n("Stop")); m_stopButton->setAutoRaise(true); m_stopButton->setToolTip(i18nc("@tooltip", "Stop loading")); m_stopButton->hide(); connect(m_stopButton, &QToolButton::clicked, this, &DolphinStatusBar::stopPressed); m_progressTextLabel = new QLabel(this); m_progressTextLabel->hide(); m_progressBar = new QProgressBar(this); m_progressBar->hide(); m_showProgressBarTimer = new QTimer(this); m_showProgressBarTimer->setInterval(500); m_showProgressBarTimer->setSingleShot(true); connect(m_showProgressBarTimer, &QTimer::timeout, this, &DolphinStatusBar::updateProgressInfo); m_resetToDefaultTextTimer = new QTimer(this); m_resetToDefaultTextTimer->setInterval(ResetToDefaultTimeout); m_resetToDefaultTextTimer->setSingleShot(true); connect(m_resetToDefaultTextTimer, &QTimer::timeout, this, &DolphinStatusBar::slotResetToDefaultText); // Initialize top layout and size policies const int fontHeight = QFontMetrics(m_label->font()).height(); const int zoomSliderHeight = m_zoomSlider->minimumSizeHint().height(); const int buttonHeight = m_stopButton->height(); const int contentHeight = qMax(qMax(fontHeight, zoomSliderHeight), buttonHeight); QFontMetrics fontMetrics(m_label->font()); m_label->setFixedHeight(contentHeight); m_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_zoomSlider->setMaximumWidth(fontMetrics.averageCharWidth() * 25); m_spaceInfo->setFixedHeight(zoomSliderHeight); m_spaceInfo->setMaximumWidth(fontMetrics.averageCharWidth() * 25); m_spaceInfo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_progressBar->setFixedHeight(zoomSliderHeight); m_progressBar->setMaximumWidth(fontMetrics.averageCharWidth() * 25); QHBoxLayout* topLayout = new QHBoxLayout(this); topLayout->setContentsMargins(2, 0, 2, 0); topLayout->setSpacing(4); topLayout->addWidget(m_label, 1); topLayout->addWidget(m_zoomSlider, 1); topLayout->addWidget(m_spaceInfo, 1); topLayout->addWidget(m_stopButton); topLayout->addWidget(m_progressTextLabel); topLayout->addWidget(m_progressBar); setExtensionsVisible(true); } DolphinStatusBar::~DolphinStatusBar() { } void DolphinStatusBar::setText(const QString& text) { if (m_text == text) { return; } m_textTimestamp = QTime::currentTime(); if (text.isEmpty()) { // Assure that the previous set text won't get // cleared immediatelly. m_resetToDefaultTextTimer->start(); } else { m_text = text; if (m_resetToDefaultTextTimer->isActive()) { m_resetToDefaultTextTimer->start(); } updateLabelText(); } } QString DolphinStatusBar::text() const { return m_text; } void DolphinStatusBar::setProgressText(const QString& text) { m_progressTextLabel->setText(text); } QString DolphinStatusBar::progressText() const { return m_progressTextLabel->text(); } void DolphinStatusBar::setProgress(int percent) { // Show a busy indicator if a value < 0 is provided: m_progressBar->setMaximum((percent < 0) ? 0 : 100); percent = qBound(0, percent, 100); const bool progressRestarted = (percent < 100) && (percent < m_progress); m_progress = percent; if (progressRestarted && !m_progressBar->isVisible()) { // Show the progress bar delayed: In the case if 100 % are reached within // a short time, no progress bar will be shown at all. m_showProgressBarTimer->start(); } m_progressBar->setValue(m_progress); if (percent == 100) { // The end of the progress has been reached. Assure that the progress bar // gets hidden and the extensions widgets get visible again. m_showProgressBarTimer->stop(); updateProgressInfo(); } } int DolphinStatusBar::progress() const { return m_progress; } void DolphinStatusBar::resetToDefaultText() { QTime currentTime; if (currentTime.msecsTo(m_textTimestamp) < ResetToDefaultTimeout) { m_resetToDefaultTextTimer->start(); } else { m_resetToDefaultTextTimer->stop(); slotResetToDefaultText(); } } void DolphinStatusBar::setDefaultText(const QString& text) { m_defaultText = text; updateLabelText(); } QString DolphinStatusBar::defaultText() const { return m_defaultText; } void DolphinStatusBar::setUrl(const QUrl& url) { m_spaceInfo->setUrl(url); } QUrl DolphinStatusBar::url() const { return m_spaceInfo->url(); } void DolphinStatusBar::setZoomLevel(int zoomLevel) { if (zoomLevel != m_zoomSlider->value()) { m_zoomSlider->setValue(zoomLevel); } } int DolphinStatusBar::zoomLevel() const { return m_zoomSlider->value(); } void DolphinStatusBar::readSettings() { setExtensionsVisible(true); } void DolphinStatusBar::contextMenuEvent(QContextMenuEvent* event) { Q_UNUSED(event); QMenu menu(this); QAction* showZoomSliderAction = menu.addAction(i18nc("@action:inmenu", "Show Zoom Slider")); showZoomSliderAction->setCheckable(true); showZoomSliderAction->setChecked(GeneralSettings::showZoomSlider()); QAction* showSpaceInfoAction = menu.addAction(i18nc("@action:inmenu", "Show Space Information")); showSpaceInfoAction->setCheckable(true); showSpaceInfoAction->setChecked(GeneralSettings::showSpaceInfo()); const QAction* action = menu.exec(QCursor::pos()); if (action == showZoomSliderAction) { const bool visible = showZoomSliderAction->isChecked(); GeneralSettings::setShowZoomSlider(visible); m_zoomSlider->setVisible(visible); } else if (action == showSpaceInfoAction) { const bool visible = showSpaceInfoAction->isChecked(); GeneralSettings::setShowSpaceInfo(visible); m_spaceInfo->setVisible(visible); } } void DolphinStatusBar::showZoomSliderToolTip(int zoomLevel) { updateZoomSliderToolTip(zoomLevel); QPoint global = m_zoomSlider->rect().topLeft(); global.ry() += m_zoomSlider->height() / 2; QHelpEvent toolTipEvent(QEvent::ToolTip, QPoint(0, 0), m_zoomSlider->mapToGlobal(global)); QApplication::sendEvent(m_zoomSlider, &toolTipEvent); } void DolphinStatusBar::updateProgressInfo() { if (m_progress < 100) { // Show the progress information and hide the extensions m_stopButton->show(); m_progressTextLabel->show(); m_progressBar->show(); setExtensionsVisible(false); } else { // Hide the progress information and show the extensions m_stopButton->hide(); m_progressTextLabel->hide(); m_progressBar->hide(); setExtensionsVisible(true); } } void DolphinStatusBar::updateLabelText() { const QString text = m_text.isEmpty() ? m_defaultText : m_text; m_label->setText(text); } void DolphinStatusBar::slotResetToDefaultText() { m_text.clear(); updateLabelText(); } void DolphinStatusBar::updateZoomSliderToolTip(int zoomLevel) { const int size = ZoomLevelInfo::iconSizeForZoomLevel(zoomLevel); m_zoomSlider->setToolTip(i18ncp("@info:tooltip", "Size: 1 pixel", "Size: %1 pixels", size)); } void DolphinStatusBar::setExtensionsVisible(bool visible) { bool showSpaceInfo = visible; bool showZoomSlider = visible; if (visible) { showSpaceInfo = GeneralSettings::showSpaceInfo(); showZoomSlider = GeneralSettings::showZoomSlider(); } m_spaceInfo->setVisible(showSpaceInfo); m_zoomSlider->setVisible(showZoomSlider); } diff --git a/src/statusbar/spaceinfoobserver.cpp b/src/statusbar/spaceinfoobserver.cpp index 8f45c18f8..6cf695d55 100644 --- a/src/statusbar/spaceinfoobserver.cpp +++ b/src/statusbar/spaceinfoobserver.cpp @@ -1,85 +1,83 @@ /*************************************************************************** * Copyright (C) 2014 by Frank Reininghaus * * * * 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 "spaceinfoobserver.h" #include "mountpointobserver.h" -#include - SpaceInfoObserver::SpaceInfoObserver(const QUrl& url, QObject* parent) : QObject(parent), m_mountPointObserver(nullptr), m_dataSize(0), m_dataAvailable(0) { m_mountPointObserver = MountPointObserver::observerForUrl(url); m_mountPointObserver->ref(); connect(m_mountPointObserver, &MountPointObserver::spaceInfoChanged, this, &SpaceInfoObserver::spaceInfoChanged); m_mountPointObserver->update(); } SpaceInfoObserver::~SpaceInfoObserver() { if (m_mountPointObserver) { m_mountPointObserver->deref(); m_mountPointObserver = nullptr; } } quint64 SpaceInfoObserver::size() const { return m_dataSize; } quint64 SpaceInfoObserver::available() const { return m_dataAvailable; } void SpaceInfoObserver::setUrl(const QUrl& url) { MountPointObserver* newObserver = MountPointObserver::observerForUrl(url); if (newObserver != m_mountPointObserver) { if (m_mountPointObserver) { disconnect(m_mountPointObserver, &MountPointObserver::spaceInfoChanged, this, &SpaceInfoObserver::spaceInfoChanged); m_mountPointObserver->deref(); m_mountPointObserver = nullptr; } m_mountPointObserver = newObserver; m_mountPointObserver->ref(); connect(m_mountPointObserver, &MountPointObserver::spaceInfoChanged, this, &SpaceInfoObserver::spaceInfoChanged); // If newObserver is cached it won't call update until the next timer update, // so update the observer now. m_mountPointObserver->update(); } } void SpaceInfoObserver::spaceInfoChanged(quint64 size, quint64 available) { // Make sure that the size has actually changed if (m_dataSize != size || m_dataAvailable != available) { m_dataSize = size; m_dataAvailable = available; emit valuesChanged(); } } diff --git a/src/statusbar/statusbarspaceinfo.cpp b/src/statusbar/statusbarspaceinfo.cpp index 6b6a6d610..a8ba9db8b 100644 --- a/src/statusbar/statusbarspaceinfo.cpp +++ b/src/statusbar/statusbarspaceinfo.cpp @@ -1,111 +1,110 @@ /*************************************************************************** * Copyright (C) 2006 by Peter Penz (peter.penz@gmx.at) and * * and Patrice Tremblay * * * * 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 "statusbarspaceinfo.h" #include "spaceinfoobserver.h" #include #include #include -#include #include StatusBarSpaceInfo::StatusBarSpaceInfo(QWidget* parent) : KCapacityBar(KCapacityBar::DrawTextInline, parent), m_observer(nullptr) { setCursor(Qt::PointingHandCursor); } StatusBarSpaceInfo::~StatusBarSpaceInfo() { } void StatusBarSpaceInfo::setUrl(const QUrl& url) { if (m_url != url) { m_url = url; if (m_observer) { m_observer->setUrl(url); } } } QUrl StatusBarSpaceInfo::url() const { return m_url; } void StatusBarSpaceInfo::showEvent(QShowEvent* event) { KCapacityBar::showEvent(event); m_observer.reset(new SpaceInfoObserver(m_url, this)); slotValuesChanged(); connect(m_observer.data(), &SpaceInfoObserver::valuesChanged, this, &StatusBarSpaceInfo::slotValuesChanged); } void StatusBarSpaceInfo::hideEvent(QHideEvent* event) { m_observer.reset(); KCapacityBar::hideEvent(event); } void StatusBarSpaceInfo::mousePressEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) { // Creates a menu with tools that help to find out more about free // disk space for the given url. // Note that this object must live long enough in case the user opens // the "Configure..." dialog KMoreToolsMenuFactory menuFactory(QStringLiteral("dolphin/statusbar-diskspace-menu")); #if KNEWSTUFF_VERSION >= QT_VERSION_CHECK(5, 37, 0) menuFactory.setParentWidget(this); #endif auto menu = menuFactory.createMenuFromGroupingNames( { "disk-usage", "more:", "disk-partitions" }, m_url); menu->exec(QCursor::pos()); } } void StatusBarSpaceInfo::slotValuesChanged() { Q_ASSERT(m_observer); const quint64 size = m_observer->size(); if (size == 0) { setText(i18nc("@info:status", "Unknown size")); setValue(0); update(); } else { const quint64 available = m_observer->available(); const quint64 used = size - available; const int percentUsed = qRound(100.0 * qreal(used) / qreal(size)); setText(i18nc("@info:status Free disk space", "%1 free", KIO::convertSize(available))); setUpdatesEnabled(false); setValue(percentUsed); setUpdatesEnabled(true); update(); } } diff --git a/src/tests/kfileitemmodelbenchmark.cpp b/src/tests/kfileitemmodelbenchmark.cpp index 5e3353396..64ad116f6 100644 --- a/src/tests/kfileitemmodelbenchmark.cpp +++ b/src/tests/kfileitemmodelbenchmark.cpp @@ -1,225 +1,222 @@ /*************************************************************************** * Copyright (C) 2011 by Peter Penz * * Copyright (C) 2013 by Frank Reininghaus * * * * 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 -#include #include #include "kitemviews/kfileitemmodel.h" #include "kitemviews/private/kfileitemmodelsortalgorithm.h" -#include "testdir.h" - void myMessageOutput(QtMsgType type, const QMessageLogContext& context, const QString& msg) { Q_UNUSED(context); switch (type) { case QtDebugMsg: break; case QtWarningMsg: break; case QtCriticalMsg: fprintf(stderr, "Critical: %s\n", msg.toLocal8Bit().data()); break; case QtFatalMsg: fprintf(stderr, "Fatal: %s\n", msg.toLocal8Bit().data()); abort(); default: break; } } Q_DECLARE_METATYPE(KFileItemList) Q_DECLARE_METATYPE(KItemRangeList) class KFileItemModelBenchmark : public QObject { Q_OBJECT public: KFileItemModelBenchmark(); private slots: void insertAndRemoveManyItems_data(); void insertAndRemoveManyItems(); private: static KFileItemList createFileItemList(const QStringList& fileNames, const QString& urlPrefix = QLatin1String("file:///")); }; KFileItemModelBenchmark::KFileItemModelBenchmark() { } void KFileItemModelBenchmark::insertAndRemoveManyItems_data() { QTest::addColumn("initialItems"); QTest::addColumn("newItems"); QTest::addColumn("removedItems"); QTest::addColumn("expectedFinalItems"); QTest::addColumn("expectedItemsInserted"); QTest::addColumn("expectedItemsRemoved"); QList sizes; sizes << 100000; foreach (int n, sizes) { QStringList allStrings; for (int i = 0; i < n; ++i) { allStrings << QString::number(i); } // We want to keep the sorting overhead in the benchmark low. // Therefore, we do not use natural sorting. However, this // means that our list is currently not sorted. allStrings.sort(); KFileItemList all = createFileItemList(allStrings); KFileItemList firstHalf, secondHalf, even, odd; for (int i = 0; i < n; ++i) { if (i < n / 2) { firstHalf << all.at(i); } else { secondHalf << all.at(i); } if (i % 2 == 0) { even << all.at(i); } else { odd << all.at(i); } } KItemRangeList itemRangeListFirstHalf; itemRangeListFirstHalf << KItemRange(0, firstHalf.count()); KItemRangeList itemRangeListSecondHalf; itemRangeListSecondHalf << KItemRange(firstHalf.count(), secondHalf.count()); KItemRangeList itemRangeListOddInserted, itemRangeListOddRemoved; for (int i = 0; i < odd.count(); ++i) { // Note that the index in the KItemRange is the index of // the model *before* the items have been inserted. itemRangeListOddInserted << KItemRange(i + 1, 1); itemRangeListOddRemoved << KItemRange(2 * i + 1, 1); } const int bufferSize = 128; char buffer[bufferSize]; snprintf(buffer, bufferSize, "all--n=%i", n); QTest::newRow(buffer) << all << KFileItemList() << KFileItemList() << all << KItemRangeList() << KItemRangeList(); snprintf(buffer, bufferSize, "1st half + 2nd half--n=%i", n); QTest::newRow(buffer) << firstHalf << secondHalf << KFileItemList() << all << itemRangeListSecondHalf << KItemRangeList(); snprintf(buffer, bufferSize, "2nd half + 1st half--n=%i", n); QTest::newRow(buffer) << secondHalf << firstHalf << KFileItemList() << all << itemRangeListFirstHalf << KItemRangeList(); snprintf(buffer, bufferSize, "even + odd--n=%i", n); QTest::newRow(buffer) << even << odd << KFileItemList() << all << itemRangeListOddInserted << KItemRangeList(); snprintf(buffer, bufferSize, "all - 2nd half--n=%i", n); QTest::newRow(buffer) << all << KFileItemList() << secondHalf << firstHalf << KItemRangeList() << itemRangeListSecondHalf; snprintf(buffer, bufferSize, "all - 1st half--n=%i", n); QTest::newRow(buffer) << all << KFileItemList() << firstHalf << secondHalf << KItemRangeList() << itemRangeListFirstHalf; snprintf(buffer, bufferSize, "all - odd--n=%i", n); QTest::newRow(buffer) << all << KFileItemList() << odd << even << KItemRangeList() << itemRangeListOddRemoved; } } void KFileItemModelBenchmark::insertAndRemoveManyItems() { QFETCH(KFileItemList, initialItems); QFETCH(KFileItemList, newItems); QFETCH(KFileItemList, removedItems); QFETCH(KFileItemList, expectedFinalItems); QFETCH(KItemRangeList, expectedItemsInserted); QFETCH(KItemRangeList, expectedItemsRemoved); KFileItemModel model; // Avoid overhead caused by natural sorting // and determining the isDir/isLink roles. model.m_naturalSorting = false; model.setRoles({"text"}); QSignalSpy spyItemsInserted(&model, &KFileItemModel::itemsInserted); QSignalSpy spyItemsRemoved(&model, &KFileItemModel::itemsRemoved); QBENCHMARK { model.slotClear(); model.slotItemsAdded(model.directory(), initialItems); model.slotCompleted(); QCOMPARE(model.count(), initialItems.count()); if (!newItems.isEmpty()) { model.slotItemsAdded(model.directory(), newItems); model.slotCompleted(); } QCOMPARE(model.count(), initialItems.count() + newItems.count()); if (!removedItems.isEmpty()) { model.slotItemsDeleted(removedItems); } QCOMPARE(model.count(), initialItems.count() + newItems.count() - removedItems.count()); } QVERIFY(model.isConsistent()); for (int i = 0; i < model.count(); ++i) { QCOMPARE(model.fileItem(i), expectedFinalItems.at(i)); } if (!expectedItemsInserted.empty()) { QVERIFY(!spyItemsInserted.empty()); const KItemRangeList actualItemsInserted = spyItemsInserted.last().first().value(); QCOMPARE(actualItemsInserted, expectedItemsInserted); } if (!expectedItemsRemoved.empty()) { QVERIFY(!spyItemsRemoved.empty()); const KItemRangeList actualItemsRemoved = spyItemsRemoved.last().first().value(); QCOMPARE(actualItemsRemoved, expectedItemsRemoved); } } KFileItemList KFileItemModelBenchmark::createFileItemList(const QStringList& fileNames, const QString& prefix) { // Suppress 'file does not exist anymore' messages from KFileItemPrivate::init(). qInstallMessageHandler(myMessageOutput); KFileItemList result; foreach (const QString& name, fileNames) { const KFileItem item(QUrl::fromLocalFile(prefix + name), QString(), KFileItem::Unknown); result << item; } return result; } QTEST_MAIN(KFileItemModelBenchmark) #include "kfileitemmodelbenchmark.moc" diff --git a/src/tests/kitemrangetest.cpp b/src/tests/kitemrangetest.cpp index 5e4ea653f..ee569a4d4 100644 --- a/src/tests/kitemrangetest.cpp +++ b/src/tests/kitemrangetest.cpp @@ -1,74 +1,73 @@ /*************************************************************************** * Copyright (C) 2014 by Frank Reininghaus * * * * 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 "kitemviews/kitemrange.h" #include -#include Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(KItemRangeList) class KItemRangeTest : public QObject { Q_OBJECT private slots: void testFromSortedContainer_data(); void testFromSortedContainer(); }; void KItemRangeTest::testFromSortedContainer_data() { QTest::addColumn >("sortedNumbers"); QTest::addColumn("expected"); QTest::newRow("empty") << QVector{} << KItemRangeList(); QTest::newRow("[1]") << QVector{1} << (KItemRangeList() << KItemRange(1, 1)); QTest::newRow("[9]") << QVector{9} << (KItemRangeList() << KItemRange(9, 1)); QTest::newRow("[1-2]") << QVector{1, 2} << (KItemRangeList() << KItemRange(1, 2)); QTest::newRow("[1-3]") << QVector{1, 2, 3} << (KItemRangeList() << KItemRange(1, 3)); QTest::newRow("[1] [4]") << QVector{1, 4} << (KItemRangeList() << KItemRange(1, 1) << KItemRange(4, 1)); QTest::newRow("[1-3] [5]") << QVector{1, 2, 3, 5} << (KItemRangeList() << KItemRange(1, 3) << KItemRange(5, 1)); QTest::newRow("[1] [5-6]") << QVector{1, 5, 6} << (KItemRangeList() << KItemRange(1, 1) << KItemRange(5, 2)); QTest::newRow("duplicates: 1 1") << QVector{1, 1} << (KItemRangeList() << KItemRange(1, 1)); QTest::newRow("duplicates: 1 1 1") << QVector{1, 1, 1} << (KItemRangeList() << KItemRange(1, 1)); QTest::newRow("duplicates: 1 1 5") << QVector{1, 1, 5} << (KItemRangeList() << KItemRange(1, 1) << KItemRange(5, 1)); QTest::newRow("duplicates: 1 5 5") << QVector{1, 5, 5} << (KItemRangeList() << KItemRange(1, 1) << KItemRange(5, 1)); QTest::newRow("duplicates: 1 1 1 5") << QVector{1, 1, 1, 5} << (KItemRangeList() << KItemRange(1, 1) << KItemRange(5, 1)); QTest::newRow("duplicates: 1 5 5 5") << QVector{1, 5, 5, 5} << (KItemRangeList() << KItemRange(1, 1) << KItemRange(5, 1)); QTest::newRow("duplicates: 1 1 2") << QVector{1, 1, 2} << (KItemRangeList() << KItemRange(1, 2)); QTest::newRow("duplicates: 1 2 2") << QVector{1, 2, 2} << (KItemRangeList() << KItemRange(1, 2)); QTest::newRow("duplicates: 1 1 2 3") << QVector{1, 1, 2, 3} << (KItemRangeList() << KItemRange(1, 3)); QTest::newRow("duplicates: 1 2 2 3") << QVector{1, 2, 2, 3} << (KItemRangeList() << KItemRange(1, 3)); QTest::newRow("duplicates: 1 2 3 3") << QVector{1, 2, 3, 3} << (KItemRangeList() << KItemRange(1, 3)); } void KItemRangeTest::testFromSortedContainer() { QFETCH(QVector, sortedNumbers); QFETCH(KItemRangeList, expected); const KItemRangeList result = KItemRangeList::fromSortedContainer(sortedNumbers); QCOMPARE(expected, result); } QTEST_GUILESS_MAIN(KItemRangeTest) #include "kitemrangetest.moc" diff --git a/src/tests/kitemsettest.cpp b/src/tests/kitemsettest.cpp index d31993947..3e74184c2 100644 --- a/src/tests/kitemsettest.cpp +++ b/src/tests/kitemsettest.cpp @@ -1,611 +1,610 @@ /*************************************************************************** * Copyright (C) 2013 by Frank Reininghaus * * * * 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 "kitemviews/kitemset.h" #include -#include Q_DECLARE_METATYPE(KItemRangeList) /** * Converts a KItemRangeList to a KItemSet. */ KItemSet KItemRangeList2KItemSet(const KItemRangeList& itemRanges) { KItemSet result; foreach (const KItemRange& range, itemRanges) { for (int i = range.index; i < range.index + range.count; ++i) { result.insert(i); } } return result; } /** * Converts a KItemRangeList to a QSet. */ QSet KItemRangeList2QSet(const KItemRangeList& itemRanges) { QSet result; foreach (const KItemRange& range, itemRanges) { for (int i = range.index; i < range.index + range.count; ++i) { result.insert(i); } } return result; } /** * Converts a KItemRangeList to a QVector. */ QVector KItemRangeList2QVector(const KItemRangeList& itemRanges) { QVector result; foreach (const KItemRange& range, itemRanges) { for (int i = range.index; i < range.index + range.count; ++i) { result.append(i); } } return result; } /** * Converts a KItemSet to a QSet. */ static QSet KItemSet2QSet(const KItemSet& itemSet) { QSet result; for (int i : itemSet) { result.insert(i); } // Check that the conversion was successful. Q_ASSERT(itemSet.count() == result.count()); for (int i : itemSet) { Q_ASSERT(result.contains(i)); } foreach (int i, result) { Q_ASSERT(itemSet.contains(i)); } return result; } /** * The main test class. */ class KItemSetTest : public QObject { Q_OBJECT private slots: void initTestCase(); void testConstruction_data(); void testConstruction(); void testIterators_data(); void testIterators(); void testFind_data(); void testFind(); void testChangingOneItem_data(); void testChangingOneItem(); void testAddSets_data(); void testAddSets(); /* void testSubtractSets_data(); void testSubtractSets(); */ void testSymmetricDifference_data(); void testSymmetricDifference(); private: QHash m_testCases; }; void KItemSetTest::initTestCase() { m_testCases.insert("empty", KItemRangeList()); m_testCases.insert("[0]", KItemRangeList() << KItemRange(0, 1)); m_testCases.insert("[1]", KItemRangeList() << KItemRange(1, 1)); m_testCases.insert("[1, 2]", KItemRangeList() << KItemRange(1, 2)); m_testCases.insert("[1, 2] [5]", KItemRangeList() << KItemRange(1, 2) << KItemRange(5, 1)); m_testCases.insert("[1] [4, 5]", KItemRangeList() << KItemRange(1, 1) << KItemRange(4, 2)); m_testCases.insert("[1, 2] [4, 5]", KItemRangeList() << KItemRange(1, 2) << KItemRange(4, 2)); m_testCases.insert("[1, 5]", KItemRangeList() << KItemRange(1, 5)); m_testCases.insert("[1, 2] [4, 5] [7] [9, 10] [13] [20, 25] [30]", KItemRangeList() << KItemRange(1, 2) << KItemRange(4, 2) << KItemRange(7, 1) << KItemRange(9, 2) << KItemRange(20, 6) << KItemRange(30, 1)); m_testCases.insert("[-10, -1]", KItemRangeList() << KItemRange(-10, 10)); m_testCases.insert("[-10, 0]", KItemRangeList() << KItemRange(-10, 11)); m_testCases.insert("[-10, 1]", KItemRangeList() << KItemRange(-10, 12)); m_testCases.insert("[0, 9]", KItemRangeList() << KItemRange(0, 10)); m_testCases.insert("[0, 19]", KItemRangeList() << KItemRange(0, 10)); } void KItemSetTest::testConstruction_data() { QTest::addColumn("itemRanges"); QHash::const_iterator it = m_testCases.constBegin(); const QHash::const_iterator end = m_testCases.constEnd(); while (it != end) { QTest::newRow(it.key()) << it.value(); ++it; } } void KItemSetTest::testConstruction() { QFETCH(KItemRangeList, itemRanges); KItemSet itemSet = KItemRangeList2KItemSet(itemRanges); QSet itemsQSet = KItemRangeList2QSet(itemRanges); QVERIFY(itemSet.isValid()); QVERIFY(itemSet.count() == itemsQSet.count()); QCOMPARE(KItemSet2QSet(itemSet), itemsQSet); // Test copy constructor. KItemSet copy(itemSet); QCOMPARE(itemSet, copy); copy.clear(); QVERIFY(itemSet != copy || itemSet.isEmpty()); // Clear the set. itemSet.clear(); QVERIFY(itemSet.isEmpty()); QCOMPARE(itemSet.count(), 0); } void KItemSetTest::testIterators_data() { QTest::addColumn("itemRanges"); QHash::const_iterator it = m_testCases.constBegin(); const QHash::const_iterator end = m_testCases.constEnd(); while (it != end) { QTest::newRow(it.key()) << it.value(); ++it; } } /** * Verify that the iterators work exactly like their counterparts for the * equivalent QVector. */ void KItemSetTest::testIterators() { QFETCH(KItemRangeList, itemRanges); KItemSet itemSet = KItemRangeList2KItemSet(itemRanges); QVector itemsQVector = KItemRangeList2QVector(itemRanges); QVERIFY(itemSet.isValid()); QVERIFY(itemSet.count() == itemsQVector.count()); if (itemSet.count() == 0) { QVERIFY(itemSet.isEmpty()); QVERIFY(itemSet.begin() == itemSet.end()); QVERIFY(itemSet.constBegin() == itemSet.constEnd()); } else { QVERIFY(!itemSet.isEmpty()); QVERIFY(itemSet.begin() != itemSet.end()); QVERIFY(itemSet.constBegin() != itemSet.constEnd()); const int min = itemsQVector.first(); const int max = itemsQVector.last(); QCOMPARE(*itemSet.begin(), min); QCOMPARE(*itemSet.constBegin(), min); QCOMPARE(itemSet.first(), min); QCOMPARE(*(--itemSet.end()), max); QCOMPARE(*(--itemSet.constEnd()), max); QCOMPARE(itemSet.last(), max); } // Test iterating using the different iterators. QVector testQVector; for (KItemSet::iterator it = itemSet.begin(), end = itemSet.end(); it != end; ++it) { testQVector.append(*it); } QCOMPARE(testQVector, itemsQVector); testQVector.clear(); for (KItemSet::const_iterator it = itemSet.constBegin(), end = itemSet.constEnd(); it != end; ++it) { testQVector.append(*it); } QCOMPARE(testQVector, itemsQVector); testQVector.clear(); for (int i : itemSet) { testQVector.append(i); } QCOMPARE(testQVector, itemsQVector); // Verify that both variants of the (const)iterator's operator++ and // operator-- functions behave exactly like their QVector equivalents. KItemSet::iterator it1 = itemSet.begin(); KItemSet::iterator it2 = itemSet.begin(); KItemSet::const_iterator constIt1 = itemSet.constBegin(); KItemSet::const_iterator constIt2 = itemSet.constBegin(); QVector::iterator vectorIt1 = itemsQVector.begin(); QVector::iterator vectorIt2 = itemsQVector.begin(); QVector::const_iterator vectorConstIt1 = itemsQVector.constBegin(); QVector::const_iterator vectorConstIt2 = itemsQVector.constBegin(); while (it1 != itemSet.end()) { if (it1 != --itemSet.end()) { QCOMPARE(*(++it1), *(++vectorIt1)); QCOMPARE(*(++constIt1), *(++vectorConstIt1)); } else { QCOMPARE(++it1, itemSet.end()); QCOMPARE(++vectorIt1, itemsQVector.end()); QCOMPARE(++constIt1, itemSet.constEnd()); QCOMPARE(++vectorConstIt1, itemsQVector.constEnd()); } QCOMPARE(*(it2++), *(vectorIt2++)); QCOMPARE(*(constIt2++), *(vectorConstIt2++)); QCOMPARE(it1, it2); QCOMPARE(constIt1, constIt2); QCOMPARE(KItemSet::const_iterator(it1), constIt1); } QCOMPARE(it1, itemSet.end()); QCOMPARE(it2, itemSet.end()); QCOMPARE(constIt1, itemSet.constEnd()); QCOMPARE(constIt2, itemSet.constEnd()); QCOMPARE(vectorIt1, itemsQVector.end()); QCOMPARE(vectorIt2, itemsQVector.end()); QCOMPARE(vectorConstIt1, itemsQVector.constEnd()); QCOMPARE(vectorConstIt2, itemsQVector.constEnd()); while (it1 != itemSet.begin()) { QCOMPARE(*(--it1), *(--vectorIt1)); QCOMPARE(*(--constIt1), *(--vectorConstIt1)); if (it2 != itemSet.end()) { QCOMPARE(*(it2--), *(vectorIt2--)); QCOMPARE(*(constIt2--), *(vectorConstIt2--)); } else { QCOMPARE(it2--, itemSet.end()); QCOMPARE(vectorIt2--, itemsQVector.end()); QCOMPARE(constIt2--, itemSet.constEnd()); QCOMPARE(vectorConstIt2--, itemsQVector.constEnd()); } QCOMPARE(it1, it2); QCOMPARE(constIt1, constIt2); QCOMPARE(KItemSet::const_iterator(it1), constIt1); } QCOMPARE(it1, itemSet.begin()); QCOMPARE(it2, itemSet.begin()); QCOMPARE(constIt1, itemSet.constBegin()); QCOMPARE(constIt2, itemSet.constBegin()); QCOMPARE(vectorIt1, itemsQVector.begin()); QCOMPARE(vectorIt2, itemsQVector.begin()); QCOMPARE(vectorConstIt1, itemsQVector.constBegin()); QCOMPARE(vectorConstIt2, itemsQVector.constBegin()); } void KItemSetTest::testFind_data() { QTest::addColumn("itemRanges"); QHash::const_iterator it = m_testCases.constBegin(); const QHash::const_iterator end = m_testCases.constEnd(); while (it != end) { QTest::newRow(it.key()) << it.value(); ++it; } } /** * Test all functions that find items: * contais(int), find(int), constFind(int) */ void KItemSetTest::testFind() { QFETCH(KItemRangeList, itemRanges); KItemSet itemSet = KItemRangeList2KItemSet(itemRanges); QSet itemsQSet = KItemRangeList2QSet(itemRanges); QVERIFY(itemSet.isValid()); QVERIFY(itemSet.count() == itemsQSet.count()); // Find the minimum and maximum items. int min; int max; if (itemSet.count() == 0) { // Use some arbitrary values for the upcoming tests. min = 0; max = 5; } else { min = *itemSet.begin(); max = *(--itemSet.end()); } // Test contains(int), find(int), and constFind(int) // for items between min - 2 and max + 2. for (int i = min - 2; i <= max + 2; ++i) { const KItemSet::iterator it = itemSet.find(i); const KItemSet::const_iterator constIt = itemSet.constFind(i); QCOMPARE(KItemSet::const_iterator(it), constIt); if (itemsQSet.contains(i)) { QVERIFY(itemSet.contains(i)); QCOMPARE(*it, i); QCOMPARE(*constIt, i); } else { QVERIFY(!itemSet.contains(i)); QCOMPARE(it, itemSet.end()); QCOMPARE(constIt, itemSet.constEnd()); } } } void KItemSetTest::testChangingOneItem_data() { QTest::addColumn("itemRanges"); QHash::const_iterator it = m_testCases.constBegin(); const QHash::const_iterator end = m_testCases.constEnd(); while (it != end) { QTest::newRow(it.key()) << it.value(); ++it; } } /** * Test all functions that change a single item: * insert(int), remove(int), erase(KItemSet::iterator) */ void KItemSetTest::testChangingOneItem() { QFETCH(KItemRangeList, itemRanges); KItemSet itemSet = KItemRangeList2KItemSet(itemRanges); QSet itemsQSet = KItemRangeList2QSet(itemRanges); QVERIFY(itemSet.isValid()); QVERIFY(itemSet.count() == itemsQSet.count()); // Find the minimum and maximum items. int min; int max; if (itemSet.count() == 0) { // Use some arbitrary values for the upcoming tests. min = 0; max = 5; } else { min = *itemSet.begin(); max = *(--itemSet.end()); } // Test insert(int), remove(int), and erase(KItemSet::iterator) // for items between min - 2 and max + 2. for (int i = min - 2; i <= max + 2; ++i) { // Test insert(int). { KItemSet tmp(itemSet); const KItemSet::iterator insertedIt = tmp.insert(i); QCOMPARE(*insertedIt, i); QVERIFY(tmp.isValid()); QVERIFY(tmp.contains(i)); QSet expectedQSet = itemsQSet; expectedQSet.insert(i); QCOMPARE(KItemSet2QSet(tmp), expectedQSet); if (!itemSet.contains(i)) { QVERIFY(itemSet != tmp); QCOMPARE(tmp.count(), itemSet.count() + 1); } else { QCOMPARE(itemSet, tmp); } QCOMPARE(i, *tmp.find(i)); QCOMPARE(i, *tmp.constFind(i)); // Erase the new item and check that we get the old KItemSet back. tmp.erase(tmp.find(i)); QVERIFY(tmp.isValid()); QVERIFY(!tmp.contains(i)); if (!itemSet.contains(i)) { QCOMPARE(itemSet, tmp); } expectedQSet.remove(i); QCOMPARE(KItemSet2QSet(tmp), expectedQSet); } // Test remove(int). { KItemSet tmp(itemSet); const bool removed = tmp.remove(i); QCOMPARE(removed, itemSet.contains(i)); QVERIFY(tmp.isValid()); QVERIFY(!tmp.contains(i)); QSet expectedQSet = itemsQSet; expectedQSet.remove(i); QCOMPARE(KItemSet2QSet(tmp), expectedQSet); if (itemSet.contains(i)) { QVERIFY(itemSet != tmp); QCOMPARE(tmp.count(), itemSet.count() - 1); } else { QCOMPARE(itemSet, tmp); } QCOMPARE(tmp.end(), tmp.find(i)); QCOMPARE(tmp.constEnd(), tmp.constFind(i)); } // Test erase(KItemSet::iterator). if (itemSet.contains(i)) { KItemSet tmp(itemSet); KItemSet::iterator it = tmp.find(i); it = tmp.erase(it); QVERIFY(tmp.isValid()); QVERIFY(!tmp.contains(i)); QSet expectedQSet = itemsQSet; expectedQSet.remove(i); QCOMPARE(KItemSet2QSet(tmp), expectedQSet); if (itemSet.contains(i)) { QVERIFY(itemSet != tmp); QCOMPARE(tmp.count(), itemSet.count() - 1); } else { QCOMPARE(itemSet, tmp); } QCOMPARE(tmp.end(), tmp.find(i)); QCOMPARE(tmp.constEnd(), tmp.constFind(i)); // Check the returen value, now contained in 'it'. if (i == max) { QCOMPARE(it, tmp.end()); } else { // it now points to the next item. QVERIFY(tmp.contains(*it)); for (int j = i; j < *it; ++j) { QVERIFY(!tmp.contains(j)); } } } } // Clear the set. itemSet.clear(); QVERIFY(itemSet.isEmpty()); QCOMPARE(itemSet.count(), 0); } void KItemSetTest::testAddSets_data() { QTest::addColumn("itemRanges1"); QTest::addColumn("itemRanges2"); QHash::const_iterator it1 = m_testCases.constBegin(); const QHash::const_iterator end = m_testCases.constEnd(); while (it1 != end) { QHash::const_iterator it2 = m_testCases.constBegin(); while (it2 != end) { QByteArray name = it1.key() + QByteArray(" + ") + it2.key(); QTest::newRow(name) << it1.value() << it2.value(); ++it2; } ++it1; } } void KItemSetTest::testAddSets() { QFETCH(KItemRangeList, itemRanges1); QFETCH(KItemRangeList, itemRanges2); KItemSet itemSet1 = KItemRangeList2KItemSet(itemRanges1); QSet itemsQSet1 = KItemRangeList2QSet(itemRanges1); KItemSet itemSet2 = KItemRangeList2KItemSet(itemRanges2); QSet itemsQSet2 = KItemRangeList2QSet(itemRanges2); KItemSet sum = itemSet1 + itemSet2; QSet sumQSet = itemsQSet1 + itemsQSet2; QCOMPARE(sum.count(), sumQSet.count()); QCOMPARE(KItemSet2QSet(sum), sumQSet); } void KItemSetTest::testSymmetricDifference_data() { QTest::addColumn("itemRanges1"); QTest::addColumn("itemRanges2"); QHash::const_iterator it1 = m_testCases.constBegin(); const QHash::const_iterator end = m_testCases.constEnd(); while (it1 != end) { QHash::const_iterator it2 = m_testCases.constBegin(); while (it2 != end) { QByteArray name = it1.key() + QByteArray(" ^ ") + it2.key(); QTest::newRow(name) << it1.value() << it2.value(); ++it2; } ++it1; } } void KItemSetTest::testSymmetricDifference() { QFETCH(KItemRangeList, itemRanges1); QFETCH(KItemRangeList, itemRanges2); KItemSet itemSet1 = KItemRangeList2KItemSet(itemRanges1); QSet itemsQSet1 = KItemRangeList2QSet(itemRanges1); KItemSet itemSet2 = KItemRangeList2KItemSet(itemRanges2); QSet itemsQSet2 = KItemRangeList2QSet(itemRanges2); KItemSet symmetricDifference = itemSet1 ^ itemSet2; QSet symmetricDifferenceQSet = (itemsQSet1 - itemsQSet2) + (itemsQSet2 - itemsQSet1); QCOMPARE(symmetricDifference.count(), symmetricDifferenceQSet.count()); QCOMPARE(KItemSet2QSet(symmetricDifference), symmetricDifferenceQSet); // Check commutativity. QCOMPARE(itemSet2 ^ itemSet1, symmetricDifference); // Some more checks: // itemSet1 ^ symmetricDifference == itemSet2, // itemSet2 ^ symmetricDifference == itemSet1. QCOMPARE(itemSet1 ^ symmetricDifference, itemSet2); QCOMPARE(itemSet2 ^ symmetricDifference, itemSet1); } QTEST_GUILESS_MAIN(KItemSetTest) #include "kitemsettest.moc" diff --git a/src/tests/placesitemmodeltest.cpp b/src/tests/placesitemmodeltest.cpp index a74b3ea6c..ec44c8f31 100644 --- a/src/tests/placesitemmodeltest.cpp +++ b/src/tests/placesitemmodeltest.cpp @@ -1,850 +1,845 @@ /*************************************************************************** * Copyright (C) 2017 by Renato Araujo Oliveira * * * * 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 -#include -#include -#include #include #include #include -#include #include #include #include #include #include #include "panels/places/placesitemmodel.h" #include "panels/places/placesitem.h" #include "views/viewproperties.h" -#include "kitemviews/kitemrange.h" Q_DECLARE_METATYPE(KItemRangeList) Q_DECLARE_METATYPE(KItemRange) #ifdef Q_OS_WIN //c:\ as root for windows #define KDE_ROOT_PATH "C:\\" #else #define KDE_ROOT_PATH "/" #endif static QString bookmarksFile() { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/user-places.xbel"; } class PlacesItemModelTest : public QObject { Q_OBJECT private slots: void init(); void cleanup(); void initTestCase(); void cleanupTestCase(); void testModelSort(); void testGroups(); void testDeletePlace(); void testPlaceItem_data(); void testPlaceItem(); void testTearDownDevice(); void testDefaultViewProperties_data(); void testDefaultViewProperties(); void testClear(); void testHideItem(); void testSystemItems(); void testEditBookmark(); void testEditAfterCreation(); void testEditMetadata(); void testRefresh(); void testIcons_data(); void testIcons(); void testDragAndDrop(); void testHideDevices(); void testDuplicatedEntries(); void renameAfterCreation(); private: PlacesItemModel* m_model; QSet m_tobeRemoved; QMap m_interfacesMap; void setBalooEnabled(bool enabled); int indexOf(const QUrl &url); QDBusInterface *fakeManager(); QDBusInterface *fakeDevice(const QString &udi); QStringList placesUrls(PlacesItemModel *model = nullptr) const; QStringList initialUrls() const; void createPlaceItem(const QString &text, const QUrl &url, const QString &icon); void removePlaceAfter(int index); void cancelPlaceRemoval(int index); void removeTestUserData(); QMimeData *createMimeData(const QList &indexes) const; }; #define CHECK_PLACES_URLS(urls) \ { \ QStringList places = placesUrls(); \ if (places != urls) { \ qWarning() << "Expected:" << urls; \ qWarning() << "Got:" << places; \ QCOMPARE(places, urls); \ } \ } void PlacesItemModelTest::setBalooEnabled(bool enabled) { KConfig config(QStringLiteral("baloofilerc")); KConfigGroup basicSettings = config.group("Basic Settings"); basicSettings.writeEntry("Indexing-Enabled", enabled); config.sync(); } int PlacesItemModelTest::indexOf(const QUrl &url) { for (int r = 0; r < m_model->count(); r++) { if (m_model->placesItem(r)->url() == url) { return r; } } return -1; } QDBusInterface *PlacesItemModelTest::fakeManager() { return fakeDevice(QStringLiteral("/org/kde/solid/fakehw")); } QDBusInterface *PlacesItemModelTest::fakeDevice(const QString &udi) { if (m_interfacesMap.contains(udi)) { return m_interfacesMap[udi]; } QDBusInterface *iface = new QDBusInterface(QDBusConnection::sessionBus().baseService(), udi); m_interfacesMap[udi] = iface; return iface; } QStringList PlacesItemModelTest::placesUrls(PlacesItemModel *model) const { QStringList urls; if (!model) { model = m_model; } for (int row = 0; row < model->count(); ++row) { urls << model->placesItem(row)->url().toDisplayString(QUrl::PreferLocalFile); } return urls; } QStringList PlacesItemModelTest::initialUrls() const { static QStringList urls; if (urls.isEmpty()) { urls << QDir::homePath() << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") << QStringLiteral("remote:/") << QStringLiteral("timeline:/today") << QStringLiteral("timeline:/yesterday") << QStringLiteral("timeline:/thismonth") << QStringLiteral("timeline:/lastmonth") << QStringLiteral("search:/documents") << QStringLiteral("search:/images") << QStringLiteral("search:/audio") << QStringLiteral("search:/videos") << QStringLiteral("/media/nfs") << QStringLiteral("/foreign") << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom"); } return urls; } void PlacesItemModelTest::createPlaceItem(const QString &text, const QUrl &url, const QString &icon) { m_model->createPlacesItem(text, url, icon); } void PlacesItemModelTest::removePlaceAfter(int index) { m_tobeRemoved.insert(index); } void PlacesItemModelTest::cancelPlaceRemoval(int index) { m_tobeRemoved.remove(index); } void PlacesItemModelTest::removeTestUserData() { // user hardcoded path to avoid removal of any user personal data QDir dir(QStringLiteral("/home/renato/.qttest/share/placesitemmodeltest")); if (dir.exists()) { QVERIFY(dir.removeRecursively()); } } QMimeData *PlacesItemModelTest::createMimeData(const QList &indexes) const { QByteArray itemData; QDataStream stream(&itemData, QIODevice::WriteOnly); QList urls; for (int index : indexes) { const QUrl itemUrl = m_model->placesItem(index)->url(); if (itemUrl.isValid()) { urls << itemUrl; } stream << index; } QMimeData* mimeData = new QMimeData(); mimeData->setUrls(urls); // copied from PlacesItemModel::internalMimeType() const QString internalMimeType = "application/x-dolphinplacesmodel-" + QString::number((qptrdiff)m_model); mimeData->setData(internalMimeType, itemData); return mimeData; } void PlacesItemModelTest::init() { m_model = new PlacesItemModel(); // WORKAROUND: need to wait for bookmark to load, check: PlacesItemModel::updateBookmarks QTest::qWait(300); QCOMPARE(m_model->count(), 17); } void PlacesItemModelTest::cleanup() { for (int i : m_tobeRemoved) { int before = m_model->count(); m_model->deleteItem(i); QTRY_COMPARE(m_model->count(), before - 1); } m_tobeRemoved.clear(); delete m_model; m_model = nullptr; removeTestUserData(); } void PlacesItemModelTest::initTestCase() { QStandardPaths::setTestModeEnabled(true); // remove test user data removeTestUserData(); const QString fakeHw = QFINDTESTDATA("data/fakecomputer.xml"); QVERIFY(!fakeHw.isEmpty()); qputenv("SOLID_FAKEHW", QFile::encodeName(fakeHw)); setBalooEnabled(true); const QString bookmarsFileName = bookmarksFile(); if (QFileInfo::exists(bookmarsFileName)) { // Ensure we'll have a clean bookmark file to start QVERIFY(QFile::remove(bookmarsFileName)); } qRegisterMetaType(); qRegisterMetaType(); } void PlacesItemModelTest::cleanupTestCase() { qDeleteAll(m_interfacesMap); QFile::remove(bookmarksFile()); // Remove any previous properties file removeTestUserData(); } void PlacesItemModelTest::testModelSort() { CHECK_PLACES_URLS(initialUrls()); } void PlacesItemModelTest::testGroups() { const auto groups = m_model->groups(); QCOMPARE(groups.size(), 6); QCOMPARE(groups.at(0).first, 0); QCOMPARE(groups.at(0).second.toString(), QStringLiteral("Places")); QCOMPARE(groups.at(1).first, 3); QCOMPARE(groups.at(1).second.toString(), QStringLiteral("Remote")); QCOMPARE(groups.at(2).first, 4); QCOMPARE(groups.at(2).second.toString(), QStringLiteral("Recently Saved")); QCOMPARE(groups.at(3).first, 8); QCOMPARE(groups.at(3).second.toString(), QStringLiteral("Search For")); QCOMPARE(groups.at(4).first, 12); QCOMPARE(groups.at(4).second.toString(), QStringLiteral("Devices")); QCOMPARE(groups.at(5).first, 14); QCOMPARE(groups.at(5).second.toString(), QStringLiteral("Removable Devices")); } void PlacesItemModelTest::testPlaceItem_data() { QTest::addColumn("url"); QTest::addColumn("expectedIsHidden"); QTest::addColumn("expectedIsSystemItem"); QTest::addColumn("expectedGroup"); QTest::addColumn("expectedStorageSetupNeeded"); // places QTest::newRow("Places - Home") << QUrl::fromLocalFile(QDir::homePath()) << false << true << QStringLiteral("Places") << false; // baloo -search QTest::newRow("Baloo - Documents") << QUrl("search:/documents") << false << true << QStringLiteral("Search For") << false; // baloo - timeline QTest::newRow("Baloo - Last Month") << QUrl("timeline:/lastmonth") << false << true << QStringLiteral("Recently Saved") << false; // devices QTest::newRow("Devices - Floppy") << QUrl("file:///media/floppy0") << false << false << QStringLiteral("Removable Devices") << false; } void PlacesItemModelTest::testPlaceItem() { QFETCH(QUrl, url); QFETCH(bool, expectedIsHidden); QFETCH(bool, expectedIsSystemItem); QFETCH(QString, expectedGroup); QFETCH(bool, expectedStorageSetupNeeded); const int index = indexOf(url); PlacesItem *item = m_model->placesItem(index); QCOMPARE(item->url(), url); QCOMPARE(item->isHidden(), expectedIsHidden); QCOMPARE(item->isSystemItem(), expectedIsSystemItem); QCOMPARE(item->group(), expectedGroup); QCOMPARE(item->storageSetupNeeded(), expectedStorageSetupNeeded); } void PlacesItemModelTest::testDeletePlace() { const QUrl tempUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)); QStringList urls = initialUrls(); QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted); QSignalSpy itemsRemovedSpy(m_model, &PlacesItemModel::itemsRemoved); PlacesItemModel *model = new PlacesItemModel(); // create a new place createPlaceItem(QStringLiteral("Temporary Dir"), tempUrl, QString()); urls.insert(3, tempUrl.toLocalFile()); // check if the new entry was created QTRY_COMPARE(itemsInsertedSpy.count(), 1); CHECK_PLACES_URLS(urls); QTRY_COMPARE(model->count(), m_model->count()); // delete item m_model->deleteItem(3); // make sure that the new item is removed QTRY_COMPARE(itemsRemovedSpy.count(), 1); QTRY_COMPARE(m_model->count(), 17); CHECK_PLACES_URLS(initialUrls()); QTRY_COMPARE(model->count(), m_model->count()); } void PlacesItemModelTest::testTearDownDevice() { const QUrl mediaUrl = QUrl::fromLocalFile(QStringLiteral("/media/XO-Y4")); int index = indexOf(mediaUrl); QVERIFY(index != -1); auto ejectAction = m_model->ejectAction(index); QVERIFY(!ejectAction); auto teardownAction = m_model->teardownAction(index); QVERIFY(teardownAction); QCOMPARE(m_model->count(), 17); QSignalSpy spyItemsRemoved(m_model, &PlacesItemModel::itemsRemoved); fakeManager()->call(QStringLiteral("unplug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); QTRY_COMPARE(m_model->count(), 16); QCOMPARE(spyItemsRemoved.count(), 1); const QList spyItemsRemovedArgs = spyItemsRemoved.takeFirst(); const KItemRangeList removedRange = spyItemsRemovedArgs.at(0).value(); QCOMPARE(removedRange.size(), 1); QCOMPARE(removedRange.first().index, index); QCOMPARE(removedRange.first().count, 1); QCOMPARE(indexOf(mediaUrl), -1); QSignalSpy spyItemsInserted(m_model, &PlacesItemModel::itemsInserted); fakeManager()->call(QStringLiteral("plug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); QTRY_COMPARE(m_model->count(), 17); QCOMPARE(spyItemsInserted.count(), 1); index = indexOf(mediaUrl); const QList args = spyItemsInserted.takeFirst(); const KItemRangeList insertedRange = args.at(0).value(); QCOMPARE(insertedRange.size(), 1); QCOMPARE(insertedRange.first().index, index); QCOMPARE(insertedRange.first().count, 1); } void PlacesItemModelTest::testDefaultViewProperties_data() { QTest::addColumn("url"); QTest::addColumn("expectedViewMode"); QTest::addColumn("expectedPreviewShow"); QTest::addColumn >("expectedVisibleRole"); // places QTest::newRow("Places - Home") << QUrl::fromLocalFile(QDir::homePath()) << DolphinView::IconsView << true << QList({"text"}); // baloo -search QTest::newRow("Baloo - Documents") << QUrl("search:/documents") << DolphinView::DetailsView << false << QList({"text", "path"}); // audio files QTest::newRow("Places - Audio") << QUrl("search:/audio") << DolphinView::DetailsView << false << QList({"text", "artist", "album"}); // baloo - timeline QTest::newRow("Baloo - Last Month") << QUrl("timeline:/lastmonth") << DolphinView::DetailsView << true << QList({"text", "modificationtime"}); // devices QTest::newRow("Devices - Floppy") << QUrl("file:///media/floppy0") << DolphinView::IconsView << true << QList({"text"}); } void PlacesItemModelTest::testDefaultViewProperties() { QFETCH(QUrl, url); QFETCH(DolphinView::Mode, expectedViewMode); QFETCH(bool, expectedPreviewShow); QFETCH(QList, expectedVisibleRole); ViewProperties properties(KFilePlacesModel::convertedUrl(url)); QCOMPARE(properties.viewMode(), expectedViewMode); QCOMPARE(properties.previewsShown(), expectedPreviewShow); QCOMPARE(properties.visibleRoles(), expectedVisibleRole); } void PlacesItemModelTest::testClear() { QCOMPARE(m_model->count(), 17); m_model->clear(); QCOMPARE(m_model->count(), 0); QCOMPARE(m_model->hiddenCount(), 0); m_model->refresh(); QTRY_COMPARE(m_model->count(), 17); } void PlacesItemModelTest::testHideItem() { const QUrl mediaUrl = QUrl::fromLocalFile(QStringLiteral("/media/XO-Y4")); const int index = indexOf(mediaUrl); PlacesItem *item = m_model->placesItem(index); QSignalSpy spyItemsRemoved(m_model, &PlacesItemModel::itemsRemoved); QList spyItemsRemovedArgs; KItemRangeList removedRange; QSignalSpy spyItemsInserted(m_model, &PlacesItemModel::itemsInserted); QList spyItemsInsertedArgs; KItemRangeList insertedRange; QVERIFY(item); // hide an item item->setHidden(true); // check if items removed was fired QTRY_COMPARE(m_model->count(), 16); QCOMPARE(spyItemsRemoved.count(), 1); spyItemsRemovedArgs = spyItemsRemoved.takeFirst(); removedRange = spyItemsRemovedArgs.at(0).value(); QCOMPARE(removedRange.size(), 1); QCOMPARE(removedRange.first().index, index); QCOMPARE(removedRange.first().count, 1); // allow model to show hidden items m_model->setHiddenItemsShown(true); // check if the items inserted was fired spyItemsInsertedArgs = spyItemsInserted.takeFirst(); insertedRange = spyItemsInsertedArgs.at(0).value(); QCOMPARE(insertedRange.size(), 1); QCOMPARE(insertedRange.first().index, index); QCOMPARE(insertedRange.first().count, 1); // mark item as visible item = m_model->placesItem(index); item->setHidden(false); // mark model to hide invisible items m_model->setHiddenItemsShown(true); QTRY_COMPARE(m_model->count(), 17); } void PlacesItemModelTest::testSystemItems() { QCOMPARE(m_model->count(), 17); for (int r = 0; r < m_model->count(); r++) { QCOMPARE(m_model->placesItem(r)->isSystemItem(), !m_model->placesItem(r)->device().isValid()); } QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted); // create a new entry (non system item) createPlaceItem(QStringLiteral("Temporary Dir"), QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)), QString()); // check if the new entry was created QTRY_COMPARE(itemsInsertedSpy.count(), 1); // make sure the new place get removed removePlaceAfter(3); QList args = itemsInsertedSpy.takeFirst(); KItemRangeList range = args.at(0).value(); QCOMPARE(range.first().index, 3); QCOMPARE(range.first().count, 1); QVERIFY(!m_model->placesItem(3)->isSystemItem()); QCOMPARE(m_model->count(), 18); QTest::qWait(300); // check if the removal signal is correct QSignalSpy itemsRemovedSpy(m_model, &PlacesItemModel::itemsRemoved); m_model->deleteItem(3); QTRY_COMPARE(itemsRemovedSpy.count(), 1); args = itemsRemovedSpy.takeFirst(); range = args.at(0).value(); QCOMPARE(range.first().index, 3); QCOMPARE(range.first().count, 1); QTRY_COMPARE(m_model->count(), 17); //cancel removal (it was removed above) cancelPlaceRemoval(3); } void PlacesItemModelTest::testEditBookmark() { const QUrl tempUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)); QScopedPointer other(new PlacesItemModel()); createPlaceItem(QStringLiteral("Temporary Dir"), QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)), QString()); // make sure that the new item will be removed later removePlaceAfter(3); QSignalSpy itemsChangedSply(m_model, &PlacesItemModel::itemsChanged); // modify place text m_model->item(3)->setText(QStringLiteral("Renamed place")); m_model->refresh(); // check if the correct signal was fired QTRY_COMPARE(itemsChangedSply.count(), 1); QList args = itemsChangedSply.takeFirst(); KItemRangeList range = args.at(0).value(); QCOMPARE(range.first().index, 3); QCOMPARE(range.first().count, 1); QSet roles = args.at(1).value >(); QCOMPARE(roles.size(), 1); QCOMPARE(*roles.begin(), QByteArrayLiteral("text")); QCOMPARE(m_model->item(3)->text(), QStringLiteral("Renamed place")); // check if the item was updated in the other model QTRY_COMPARE(other->item(3)->text(), QStringLiteral("Renamed place")); } void PlacesItemModelTest::testEditAfterCreation() { const QUrl tempUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)); QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted); // create a new place createPlaceItem(QStringLiteral("Temporary Dir"), tempUrl, QString()); QTRY_COMPARE(itemsInsertedSpy.count(), 1); PlacesItemModel *model = new PlacesItemModel(); QTRY_COMPARE(model->count(), m_model->count()); // make sure that the new item will be removed later removePlaceAfter(3); // modify place text PlacesItem *item = m_model->placesItem(3); item->setText(QStringLiteral("Renamed place")); m_model->refresh(); // check if the second model got the changes QTRY_COMPARE(model->count(), m_model->count()); QTRY_COMPARE(model->placesItem(3)->text(), m_model->placesItem(3)->text()); QTRY_COMPARE(model->placesItem(3)->bookmark().metaDataItem(QStringLiteral("OnlyInApp")), m_model->placesItem(3)->bookmark().metaDataItem(QStringLiteral("OnlyInApp"))); QTRY_COMPARE(model->placesItem(3)->icon(), m_model->placesItem(3)->icon()); QTRY_COMPARE(model->placesItem(3)->url(), m_model->placesItem(3)->url()); } void PlacesItemModelTest::testEditMetadata() { const QUrl tempUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)); QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted); // create a new place createPlaceItem(QStringLiteral("Temporary Dir"), tempUrl, QString()); QTRY_COMPARE(itemsInsertedSpy.count(), 1); // check if the new entry was created PlacesItemModel *model = new PlacesItemModel(); QTRY_COMPARE(model->count(), m_model->count()); // make sure that the new item will be removed later removePlaceAfter(3); // modify place metadata PlacesItem *item = m_model->placesItem(3); item->bookmark().setMetaDataItem(QStringLiteral("OnlyInApp"), KAboutData::applicationData().componentName()); m_model->refresh(); // check if the place was modified in both models QTRY_COMPARE(model->placesItem(3)->bookmark().metaDataItem(QStringLiteral("OnlyInApp")), KAboutData::applicationData().componentName()); QTRY_COMPARE(model->placesItem(3)->text(), m_model->placesItem(3)->text()); QTRY_COMPARE(model->placesItem(3)->bookmark().metaDataItem(QStringLiteral("OnlyInApp")), m_model->placesItem(3)->bookmark().metaDataItem(QStringLiteral("OnlyInApp"))); QTRY_COMPARE(model->placesItem(3)->icon(), m_model->placesItem(3)->icon()); QTRY_COMPARE(model->placesItem(3)->url(), m_model->placesItem(3)->url()); } void PlacesItemModelTest::testRefresh() { const QUrl tempUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)); QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted); // create a new place createPlaceItem(QStringLiteral("Temporary Dir"), tempUrl, QString()); QTRY_COMPARE(itemsInsertedSpy.count(), 1); PlacesItemModel *model = new PlacesItemModel(); QTRY_COMPARE(model->count(), m_model->count()); // make sure that the new item will be removed later removePlaceAfter(3); PlacesItem *item = m_model->placesItem(3); PlacesItem *sameItem = model->placesItem(3); QCOMPARE(item->text(), sameItem->text()); // modify place text item->setText(QStringLiteral("Renamed place")); // item from another model is not affected at the moment QVERIFY(item->text() != sameItem->text()); // propagate change m_model->refresh(); // item must be equal QTRY_COMPARE(item->text(), sameItem->text()); } void PlacesItemModelTest::testIcons_data() { QTest::addColumn("url"); QTest::addColumn("expectedIconName"); // places QTest::newRow("Places - Home") << QUrl::fromLocalFile(QDir::homePath()) << QStringLiteral("user-home"); // baloo -search QTest::newRow("Baloo - Documents") << QUrl("search:/documents") << QStringLiteral("folder-text"); // baloo - timeline QTest::newRow("Baloo - Last Month") << QUrl("timeline:/lastmonth") << QStringLiteral("view-calendar-month"); // devices QTest::newRow("Devices - Floppy") << QUrl("file:///media/floppy0") << QStringLiteral("blockdevice"); } void PlacesItemModelTest::testIcons() { QFETCH(QUrl, url); QFETCH(QString, expectedIconName); PlacesItem *item = m_model->placesItem(indexOf(url)); QCOMPARE(item->icon(), expectedIconName); for (int r = 0; r < m_model->count(); r++) { QVERIFY(!m_model->placesItem(r)->icon().isEmpty()); } } void PlacesItemModelTest::testDragAndDrop() { QList args; KItemRangeList range; QStringList urls = initialUrls(); QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted); QSignalSpy itemsRemovedSpy(m_model, &PlacesItemModel::itemsRemoved); CHECK_PLACES_URLS(initialUrls()); // Move the KDE_ROOT_PATH at the end of the places list will case it to be moved to the end of the places group QMimeData *dropData = createMimeData(QList() << 1); m_model->dropMimeDataBefore(m_model->count() - 1, dropData); urls.move(1, 2); delete dropData; QTRY_COMPARE(itemsInsertedSpy.count(), 1); QTRY_COMPARE(itemsRemovedSpy.count(), 1); // remove item from actual position args = itemsRemovedSpy.takeFirst(); range = args.at(0).value(); QCOMPARE(range.size(), 1); QCOMPARE(range.at(0).count, 1); QCOMPARE(range.at(0).index, 1); // insert intem in his group args = itemsInsertedSpy.takeFirst(); range = args.at(0).value(); QCOMPARE(range.size(), 1); QCOMPARE(range.at(0).count, 1); QCOMPARE(range.at(0).index, 2); CHECK_PLACES_URLS(urls); itemsInsertedSpy.clear(); itemsRemovedSpy.clear(); // Move the KDE_ROOT_PATH to his original position dropData = createMimeData(QList() << 2); m_model->dropMimeDataBefore(1, dropData); urls.move(2, 1); delete dropData; QTRY_COMPARE(itemsInsertedSpy.count(), 1); QTRY_COMPARE(itemsRemovedSpy.count(), 1); // remove item from actual position args = itemsRemovedSpy.takeFirst(); range = args.at(0).value(); QCOMPARE(range.size(), 1); QCOMPARE(range.at(0).count, 1); QCOMPARE(range.at(0).index, 2); // insert intem in the requested position args = itemsInsertedSpy.takeFirst(); range = args.at(0).value(); QCOMPARE(range.size(), 1); QCOMPARE(range.at(0).count, 1); QCOMPARE(range.at(0).index, 1); CHECK_PLACES_URLS(urls); } void PlacesItemModelTest::testHideDevices() { QSignalSpy itemsRemoved(m_model, &PlacesItemModel::itemsRemoved); QStringList urls = initialUrls(); m_model->setGroupHidden(KFilePlacesModel::RemovableDevicesType, true); QTRY_VERIFY(m_model->isGroupHidden(KFilePlacesModel::RemovableDevicesType)); QTRY_COMPARE(itemsRemoved.count(), 3); // remove removable-devices urls.removeOne(QStringLiteral("/media/floppy0")); urls.removeOne(QStringLiteral("/media/XO-Y4")); urls.removeOne(QStringLiteral("/media/cdrom")); // check if the correct urls was removed CHECK_PLACES_URLS(urls); delete m_model; m_model = new PlacesItemModel(); QTRY_COMPARE(m_model->count(), urls.count()); CHECK_PLACES_URLS(urls); // revert changes m_model->setGroupHidden(KFilePlacesModel::RemovableDevicesType, false); urls = initialUrls(); QTRY_COMPARE(m_model->count(), urls.count()); CHECK_PLACES_URLS(urls); } void PlacesItemModelTest::testDuplicatedEntries() { QStringList urls = initialUrls(); // create a duplicated entry on bookmark KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); KBookmarkGroup root = bookmarkManager->root(); KBookmark bookmark = root.addBookmark(QStringLiteral("Duplicated Search Videos"), QUrl("search:/videos"), {}); const QString id = QUuid::createUuid().toString(); bookmark.setMetaDataItem(QStringLiteral("ID"), id); bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), KAboutData::applicationData().componentName()); bookmarkManager->emitChanged(bookmarkManager->root()); PlacesItemModel *newModel = new PlacesItemModel(); QTRY_COMPARE(placesUrls(newModel).count(QStringLiteral("search:/videos")), 1); QTRY_COMPARE(urls, placesUrls(newModel)); delete newModel; } void PlacesItemModelTest::renameAfterCreation() { const QUrl tempUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)); QStringList urls = initialUrls(); PlacesItemModel *model = new PlacesItemModel(); CHECK_PLACES_URLS(urls); QTRY_COMPARE(model->count(), m_model->count()); // create a new place createPlaceItem(QStringLiteral("Temporary Dir"), tempUrl, QString()); urls.insert(3, tempUrl.toLocalFile()); // make sure that the new item will be removed later removePlaceAfter(3); CHECK_PLACES_URLS(urls); QCOMPARE(model->count(), m_model->count()); // modify place text QSignalSpy changedSpy(m_model, &PlacesItemModel::itemsChanged); PlacesItem *item = m_model->placesItem(3); item->setText(QStringLiteral("New Temporary Dir")); item->setUrl(item->url()); item->setIcon(item->icon()); m_model->refresh(); QTRY_COMPARE(changedSpy.count(), 1); // check if the place was modified in both models QTRY_COMPARE(m_model->placesItem(3)->text(), QStringLiteral("New Temporary Dir")); QTRY_COMPARE(model->placesItem(3)->text(), QStringLiteral("New Temporary Dir")); } QTEST_MAIN(PlacesItemModelTest) #include "placesitemmodeltest.moc" diff --git a/src/tests/testdir.cpp b/src/tests/testdir.cpp index b84b344b7..de81378d8 100644 --- a/src/tests/testdir.cpp +++ b/src/tests/testdir.cpp @@ -1,128 +1,126 @@ /***************************************************************************** * Copyright (C) 2010-2011 by Frank Reininghaus (frank78ac@googlemail.com) * * * * 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 "testdir.h" -#include - #ifdef Q_OS_UNIX #include #else #include #endif TestDir::TestDir(const QString& directoryPrefix) : QTemporaryDir(directoryPrefix) { } TestDir::~TestDir() { } QUrl TestDir::url() const { return QUrl::fromLocalFile(path()); } /** The following function is taken from kdelibs/kio/tests/kiotesthelper.h, copyright (C) 2006 by David Faure */ static void setTimeStamp(const QString& path, const QDateTime& mtime) { #ifdef Q_OS_UNIX struct utimbuf utbuf; utbuf.actime = mtime.toTime_t(); utbuf.modtime = utbuf.actime; utime(QFile::encodeName(path), &utbuf); #elif defined(Q_OS_WIN) struct _utimbuf utbuf; utbuf.actime = mtime.toTime_t(); utbuf.modtime = utbuf.actime; _wutime(reinterpret_cast(path.utf16()), &utbuf); #endif } void TestDir::createFile(const QString& path, const QByteArray& data, const QDateTime& time) { QString absolutePath = path; makePathAbsoluteAndCreateParents(absolutePath); QFile f(absolutePath); f.open(QIODevice::WriteOnly); f.write(data); f.close(); if (time.isValid()) { setTimeStamp(absolutePath, time); } Q_ASSERT(QFile::exists(absolutePath)); } void TestDir::createFiles(const QStringList& files) { foreach (const QString& path, files) { createFile(path); } } void TestDir::createDir(const QString& path, const QDateTime& time) { QString absolutePath = path; makePathAbsoluteAndCreateParents(absolutePath); QDir(TestDir::path()).mkdir(absolutePath); if (time.isValid()) { setTimeStamp(absolutePath, time); } Q_ASSERT(QFile::exists(absolutePath)); } void TestDir::removeFiles(const QStringList& files) { foreach (const QString& path, files) { removeFile(path); } } void TestDir::removeFile(const QString& path) { QString absolutePath = path; QFileInfo fileInfo(absolutePath); if (!fileInfo.isAbsolute()) { absolutePath = TestDir::path() + QLatin1Char('/') + path; } QFile::remove(absolutePath); } void TestDir::makePathAbsoluteAndCreateParents(QString& path) { QFileInfo fileInfo(path); if (!fileInfo.isAbsolute()) { path = TestDir::path() + QLatin1Char('/') + path; fileInfo.setFile(path); } const QDir dir = fileInfo.dir(); if (!dir.exists()) { createDir(dir.absolutePath()); } Q_ASSERT(dir.exists()); } diff --git a/src/tests/viewpropertiestest.cpp b/src/tests/viewpropertiestest.cpp index 5193bb35a..0cd55662d 100644 --- a/src/tests/viewpropertiestest.cpp +++ b/src/tests/viewpropertiestest.cpp @@ -1,99 +1,98 @@ /*************************************************************************** * Copyright (C) 2012 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 "dolphin_generalsettings.h" #include "views/viewproperties.h" #include "testdir.h" #include -#include class ViewPropertiesTest : public QObject { Q_OBJECT private slots: void init(); void cleanup(); void testReadOnlyBehavior(); void testAutoSave(); private: bool m_globalViewProps; TestDir* m_testDir; }; void ViewPropertiesTest::init() { m_globalViewProps = GeneralSettings::self()->globalViewProps(); GeneralSettings::self()->setGlobalViewProps(false); GeneralSettings::self()->save(); // It is mandatory to create the test-directory inside the home-directory // of the user: ViewProperties does not write inside directories // outside the home-directory to prevent overwriting other user-settings // in case if write-permissions are given. m_testDir = new TestDir(QDir::homePath() + "/.viewPropertiesTest-"); } void ViewPropertiesTest::cleanup() { delete m_testDir; m_testDir = nullptr; GeneralSettings::self()->setGlobalViewProps(m_globalViewProps); GeneralSettings::self()->save(); } /** * Test whether only reading properties won't result in creating * a .directory file when destructing the ViewProperties instance * and autosaving is enabled. */ void ViewPropertiesTest::testReadOnlyBehavior() { QString dotDirectoryFile = m_testDir->url().toLocalFile() + "/.directory"; QVERIFY(!QFile::exists(dotDirectoryFile)); QScopedPointer props(new ViewProperties(m_testDir->url())); QVERIFY(props->isAutoSaveEnabled()); const QByteArray sortRole = props->sortRole(); Q_UNUSED(sortRole); props.reset(); QVERIFY(!QFile::exists(dotDirectoryFile)); } void ViewPropertiesTest::testAutoSave() { QString dotDirectoryFile = m_testDir->url().toLocalFile() + "/.directory"; QVERIFY(!QFile::exists(dotDirectoryFile)); QScopedPointer props(new ViewProperties(m_testDir->url())); QVERIFY(props->isAutoSaveEnabled()); props->setSortRole("someNewSortRole"); props.reset(); QVERIFY(QFile::exists(dotDirectoryFile)); } QTEST_GUILESS_MAIN(ViewPropertiesTest) #include "viewpropertiestest.moc" diff --git a/src/views/dolphinfileitemlistwidget.cpp b/src/views/dolphinfileitemlistwidget.cpp index e7398af2d..197fe5591 100644 --- a/src/views/dolphinfileitemlistwidget.cpp +++ b/src/views/dolphinfileitemlistwidget.cpp @@ -1,127 +1,125 @@ /*************************************************************************** * 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 "dolphinfileitemlistwidget.h" -#include #include -#include #include "dolphindebug.h" DolphinFileItemListWidget::DolphinFileItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent) : KFileItemListWidget(informant, parent) { } DolphinFileItemListWidget::~DolphinFileItemListWidget() { } void DolphinFileItemListWidget::refreshCache() { QColor color; const QHash values = data(); if (values.contains("version")) { // The item is under version control. Apply the text color corresponding // to its version state. const KVersionControlPlugin::ItemVersion version = static_cast(values.value("version").toInt()); const QColor textColor = styleOption().palette.text().color(); QColor tintColor = textColor; // Using hardcoded colors is generally a bad idea. In this case the colors just act // as tint colors and are mixed with the current set text color. The tint colors // have been optimized for the base colors of the corresponding Oxygen emblems. switch (version) { case KVersionControlPlugin::UpdateRequiredVersion: tintColor = Qt::yellow; break; case KVersionControlPlugin::LocallyModifiedUnstagedVersion: tintColor = Qt::green; break; case KVersionControlPlugin::LocallyModifiedVersion: tintColor = Qt::green; break; case KVersionControlPlugin::AddedVersion: tintColor = Qt::green; break; case KVersionControlPlugin::RemovedVersion: tintColor = Qt::darkRed; break; case KVersionControlPlugin::ConflictingVersion: tintColor = Qt::red; break; case KVersionControlPlugin::IgnoredVersion: tintColor = Qt::white; break; case KVersionControlPlugin::MissingVersion: tintColor = Qt::red; break; case KVersionControlPlugin::NormalVersion: case KVersionControlPlugin::UnversionedVersion: default: break; } color = QColor((tintColor.red() + textColor.red()) / 2, (tintColor.green() + textColor.green()) / 2, (tintColor.blue() + textColor.blue()) / 2, (tintColor.alpha() + textColor.alpha()) / 2); setOverlay(overlayForState(version, styleOption().iconSize)); } else if (!overlay().isNull()) { setOverlay(QPixmap()); } setTextColor(color); } QPixmap DolphinFileItemListWidget::overlayForState(KVersionControlPlugin::ItemVersion version, int size) { int overlayHeight = KIconLoader::SizeSmall; if (size >= KIconLoader::SizeEnormous) { overlayHeight = KIconLoader::SizeMedium; } else if (size >= KIconLoader::SizeLarge) { overlayHeight = KIconLoader::SizeSmallMedium; } else if (size >= KIconLoader::SizeMedium) { overlayHeight = KIconLoader::SizeSmall; } else { overlayHeight = KIconLoader::SizeSmall / 2; } QString iconName; switch (version) { case KVersionControlPlugin::NormalVersion: iconName = QStringLiteral("vcs-normal"); break; case KVersionControlPlugin::UpdateRequiredVersion: iconName = QStringLiteral("vcs-update-required"); break; case KVersionControlPlugin::LocallyModifiedVersion: iconName = QStringLiteral("vcs-locally-modified"); break; case KVersionControlPlugin::LocallyModifiedUnstagedVersion: iconName = QStringLiteral("vcs-locally-modified-unstaged"); break; case KVersionControlPlugin::AddedVersion: iconName = QStringLiteral("vcs-added"); break; case KVersionControlPlugin::RemovedVersion: iconName = QStringLiteral("vcs-removed"); break; case KVersionControlPlugin::ConflictingVersion: iconName = QStringLiteral("vcs-conflicting"); break; case KVersionControlPlugin::UnversionedVersion: case KVersionControlPlugin::IgnoredVersion: case KVersionControlPlugin::MissingVersion: break; default: Q_ASSERT(false); break; } return QIcon::fromTheme(iconName).pixmap(QSize(overlayHeight, overlayHeight)); } diff --git a/src/views/dolphinitemlistview.cpp b/src/views/dolphinitemlistview.cpp index a1472c661..be142205d 100644 --- a/src/views/dolphinitemlistview.cpp +++ b/src/views/dolphinitemlistview.cpp @@ -1,260 +1,258 @@ /*************************************************************************** * 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 "dolphinitemlistview.h" #include "dolphin_generalsettings.h" #include "dolphin_iconsmodesettings.h" #include "dolphin_detailsmodesettings.h" #include "dolphin_compactmodesettings.h" #include "dolphinfileitemlistwidget.h" -#include #include #include -#include #include #include #include "zoomlevelinfo.h" DolphinItemListView::DolphinItemListView(QGraphicsWidget* parent) : KFileItemListView(parent), m_zoomLevel(0) { updateFont(); updateGridSize(); } DolphinItemListView::~DolphinItemListView() { writeSettings(); } void DolphinItemListView::setZoomLevel(int level) { if (level < ZoomLevelInfo::minimumLevel()) { level = ZoomLevelInfo::minimumLevel(); } else if (level > ZoomLevelInfo::maximumLevel()) { level = ZoomLevelInfo::maximumLevel(); } if (level == m_zoomLevel) { return; } m_zoomLevel = level; ViewModeSettings settings(viewMode()); if (previewsShown()) { const int previewSize = ZoomLevelInfo::iconSizeForZoomLevel(level); settings.setPreviewSize(previewSize); } else { const int iconSize = ZoomLevelInfo::iconSizeForZoomLevel(level); settings.setIconSize(iconSize); } updateGridSize(); } int DolphinItemListView::zoomLevel() const { return m_zoomLevel; } void DolphinItemListView::readSettings() { ViewModeSettings settings(viewMode()); settings.readConfig(); beginTransaction(); setEnabledSelectionToggles(GeneralSettings::showSelectionToggle()); setSupportsItemExpanding(itemLayoutSupportsItemExpanding(itemLayout())); updateFont(); updateGridSize(); const KConfigGroup globalConfig(KSharedConfig::openConfig(), "PreviewSettings"); setEnabledPlugins(globalConfig.readEntry("Plugins", KIO::PreviewJob::defaultPlugins())); endTransaction(); } void DolphinItemListView::writeSettings() { IconsModeSettings::self()->save(); CompactModeSettings::self()->save(); DetailsModeSettings::self()->save(); } KItemListWidgetCreatorBase* DolphinItemListView::defaultWidgetCreator() const { return new KItemListWidgetCreator(); } bool DolphinItemListView::itemLayoutSupportsItemExpanding(ItemLayout layout) const { return layout == DetailsLayout && DetailsModeSettings::expandableFolders(); } void DolphinItemListView::onItemLayoutChanged(ItemLayout current, ItemLayout previous) { setHeaderVisible(current == DetailsLayout); updateFont(); updateGridSize(); KFileItemListView::onItemLayoutChanged(current, previous); } void DolphinItemListView::onPreviewsShownChanged(bool shown) { Q_UNUSED(shown); updateGridSize(); } void DolphinItemListView::onVisibleRolesChanged(const QList& current, const QList& previous) { KFileItemListView::onVisibleRolesChanged(current, previous); updateGridSize(); } void DolphinItemListView::updateFont() { const ViewModeSettings settings(viewMode()); if (settings.useSystemFont()) { KItemListView::updateFont(); } else { QFont font(settings.fontFamily(), qRound(settings.fontSize())); font.setItalic(settings.italicFont()); font.setWeight(settings.fontWeight()); font.setPointSizeF(settings.fontSize()); KItemListStyleOption option = styleOption(); option.font = font; option.fontMetrics = QFontMetrics(font); setStyleOption(option); } } void DolphinItemListView::updateGridSize() { const ViewModeSettings settings(viewMode()); // Calculate the size of the icon const int iconSize = previewsShown() ? settings.previewSize() : settings.iconSize(); m_zoomLevel = ZoomLevelInfo::zoomLevelForIconSize(QSize(iconSize, iconSize)); KItemListStyleOption option = styleOption(); const int padding = 2; int horizontalMargin = 0; int verticalMargin = 0; // Calculate the item-width and item-height int itemWidth; int itemHeight; int maxTextLines = 0; int maxTextWidth = 0; switch (itemLayout()) { case KFileItemListView::IconsLayout: { const int minItemWidth = 48; itemWidth = minItemWidth + IconsModeSettings::textWidthIndex() * 64; if (previewsShown()) { // Optimize the width for previews with a 3:2 aspect ratio instead // of a 1:1 ratio to avoid wasting too much vertical space when // showing photos. const int minWidth = iconSize * 3 / 2; itemWidth = qMax(itemWidth, minWidth); } if (itemWidth < iconSize + padding * 2) { itemWidth = iconSize + padding * 2; } itemHeight = padding * 3 + iconSize + option.fontMetrics.lineSpacing(); horizontalMargin = 4; verticalMargin = 8; maxTextLines = IconsModeSettings::maximumTextLines(); break; } case KFileItemListView::CompactLayout: { itemWidth = padding * 4 + iconSize + option.fontMetrics.height() * 5; const int textLinesCount = visibleRoles().count(); itemHeight = padding * 2 + qMax(iconSize, textLinesCount * option.fontMetrics.lineSpacing()); if (CompactModeSettings::maximumTextWidthIndex() > 0) { // A restriction is given for the maximum width of the text (0 means // having no restriction) maxTextWidth = option.fontMetrics.height() * 10 * CompactModeSettings::maximumTextWidthIndex(); } horizontalMargin = 8; break; } case KFileItemListView::DetailsLayout: { itemWidth = -1; itemHeight = padding * 2 + qMax(iconSize, option.fontMetrics.lineSpacing()); break; } default: itemWidth = -1; itemHeight = -1; Q_ASSERT(false); break; } // Apply the calculated values option.padding = padding; option.horizontalMargin = horizontalMargin; option.verticalMargin = verticalMargin; option.iconSize = iconSize; option.maxTextLines = maxTextLines; option.maxTextWidth = maxTextWidth; beginTransaction(); setStyleOption(option); setItemSize(QSizeF(itemWidth, itemHeight)); endTransaction(); } ViewModeSettings::ViewMode DolphinItemListView::viewMode() const { ViewModeSettings::ViewMode mode; switch (itemLayout()) { case KFileItemListView::IconsLayout: mode = ViewModeSettings::IconsMode; break; case KFileItemListView::CompactLayout: mode = ViewModeSettings::CompactMode; break; case KFileItemListView::DetailsLayout: mode = ViewModeSettings::DetailsMode; break; default: mode = ViewModeSettings::IconsMode; Q_ASSERT(false); break; } return mode; } diff --git a/src/views/dolphinremoteencoding.cpp b/src/views/dolphinremoteencoding.cpp index 116168a4b..bdf747f08 100644 --- a/src/views/dolphinremoteencoding.cpp +++ b/src/views/dolphinremoteencoding.cpp @@ -1,235 +1,234 @@ /*************************************************************************** * Copyright (C) 2009 by Rahman Duran * * * * 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 * ***************************************************************************/ /* * This code is largely based on the kremoteencodingplugin * Copyright (c) 2003 Thiago Macieira * Distributed under the same terms. */ #include "dolphinremoteencoding.h" #include "dolphinviewactionhandler.h" #include "dolphindebug.h" #include #include -#include -#include #include #include #include #include #include #include #include +#include #define DATA_KEY QStringLiteral("Charset") DolphinRemoteEncoding::DolphinRemoteEncoding(QObject* parent, DolphinViewActionHandler* actionHandler) :QObject(parent), m_actionHandler(actionHandler), m_loaded(false), m_idDefault(0) { m_menu = new KActionMenu(QIcon::fromTheme(QStringLiteral("character-set")), i18n("Select Remote Charset"), this); m_actionHandler->actionCollection()->addAction(QStringLiteral("change_remote_encoding"), m_menu); connect(m_menu->menu(), &QMenu::aboutToShow, this, &DolphinRemoteEncoding::slotAboutToShow); m_menu->setEnabled(false); m_menu->setDelayed(false); } DolphinRemoteEncoding::~DolphinRemoteEncoding() { } void DolphinRemoteEncoding::slotReload() { loadSettings(); } void DolphinRemoteEncoding::loadSettings() { m_loaded = true; m_encodingDescriptions = KCharsets::charsets()->descriptiveEncodingNames(); fillMenu(); } void DolphinRemoteEncoding::slotAboutToOpenUrl() { QUrl oldURL = m_currentURL; m_currentURL = m_actionHandler->currentView()->url(); if (m_currentURL.scheme() != oldURL.scheme()) { // This plugin works on ftp, fish, etc. // everything whose type is T_FILESYSTEM except for local files if (!m_currentURL.isLocalFile() && KProtocolManager::outputType(m_currentURL) == KProtocolInfo::T_FILESYSTEM) { m_menu->setEnabled(true); loadSettings(); } else { m_menu->setEnabled(false); } return; } if (m_currentURL.host() != oldURL.host()) { updateMenu(); } } void DolphinRemoteEncoding::fillMenu() { QMenu* menu = m_menu->menu(); menu->clear(); for (int i = 0; i < m_encodingDescriptions.size();i++) { QAction* action = new QAction(m_encodingDescriptions.at(i), this); action->setCheckable(true); action->setData(i); menu->addAction(action); } menu->addSeparator(); menu->addAction(i18n("Reload"), this, SLOT(slotReload()), 0); menu->addAction(i18n("Default"), this, SLOT(slotDefault()), 0)->setCheckable(true); m_idDefault = m_encodingDescriptions.size() + 2; connect(menu, &QMenu::triggered, this, &DolphinRemoteEncoding::slotItemSelected); } void DolphinRemoteEncoding::updateMenu() { if (!m_loaded) { loadSettings(); } // uncheck everything for (int i = 0; i < m_menu->menu()->actions().count(); i++) { m_menu->menu()->actions().at(i)->setChecked(false); } const QString charset = KCharsets::charsets()->descriptionForEncoding(KProtocolManager::charsetFor(m_currentURL)); if (!charset.isEmpty()) { int id = 0; bool isFound = false; for (int i = 0; i < m_encodingDescriptions.size(); i++) { if (m_encodingDescriptions.at(i) == charset) { isFound = true; id = i; break; } } qCDebug(DolphinDebug) << "URL=" << m_currentURL << " charset=" << charset; if (!isFound) { qCWarning(DolphinDebug) << "could not find entry for charset=" << charset ; } else { m_menu->menu()->actions().at(id)->setChecked(true); } } else { m_menu->menu()->actions().at(m_idDefault)->setChecked(true); } } void DolphinRemoteEncoding::slotAboutToShow() { if (!m_loaded) { loadSettings(); } updateMenu(); } void DolphinRemoteEncoding::slotItemSelected(QAction* action) { if (action) { int id = action->data().toInt(); KConfig config(("kio_" + m_currentURL.scheme() + "rc").toLatin1()); QString host = m_currentURL.host(); if (m_menu->menu()->actions().at(id)->isChecked()) { QString charset = KCharsets::charsets()->encodingForName(m_encodingDescriptions.at(id)); KConfigGroup cg(&config, host); cg.writeEntry(DATA_KEY, charset); config.sync(); // Update the io-slaves... updateView(); } } } void DolphinRemoteEncoding::slotDefault() { // We have no choice but delete all higher domain level // settings here since it affects what will be matched. KConfig config(("kio_" + m_currentURL.scheme() + "rc").toLatin1()); QStringList partList = m_currentURL.host().split('.', QString::SkipEmptyParts); if (!partList.isEmpty()) { partList.erase(partList.begin()); QStringList domains; // Remove the exact name match... domains << m_currentURL.host(); while (!partList.isEmpty()) { if (partList.count() == 2) { if (partList[0].length() <= 2 && partList[1].length() == 2) { break; } } if (partList.count() == 1) { break; } domains << partList.join(QLatin1Char('.')); partList.erase(partList.begin()); } for (QStringList::const_iterator it = domains.constBegin(); it != domains.constEnd();++it) { qCDebug(DolphinDebug) << "Domain to remove: " << *it; if (config.hasGroup(*it)) { config.deleteGroup(*it); } else if (config.group("").hasKey(*it)) { config.group("").deleteEntry(*it); //don't know what group name is supposed to be XXX } } } config.sync(); // Update the io-slaves. updateView(); } void DolphinRemoteEncoding::updateView() { KIO::Scheduler::emitReparseSlaveConfiguration(); // Reload the page with the new charset m_actionHandler->currentView()->setUrl(m_currentURL); m_actionHandler->currentView()->reload(); } diff --git a/src/views/dolphinview.cpp b/src/views/dolphinview.cpp index e40e49d6e..463ab51ba 100644 --- a/src/views/dolphinview.cpp +++ b/src/views/dolphinview.cpp @@ -1,1817 +1,1810 @@ /*************************************************************************** * Copyright (C) 2006-2009 by Peter Penz * * Copyright (C) 2006 by Gregor Kališnik * * * * 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 "dolphinview.h" -#include - #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include -#include #include #include -#include #include "dolphinnewfilemenuobserver.h" #include "dolphin_detailsmodesettings.h" #include "dolphin_generalsettings.h" #include "dolphinitemlistview.h" #include "draganddrophelper.h" #include "renamedialog.h" #include "versioncontrol/versioncontrolobserver.h" -#include "viewmodecontroller.h" #include "viewproperties.h" #include "views/tooltips/tooltipmanager.h" #include "zoomlevelinfo.h" #ifdef HAVE_BALOO #include #endif #include DolphinView::DolphinView(const QUrl& url, QWidget* parent) : QWidget(parent), m_active(true), m_tabsForFiles(false), m_assureVisibleCurrentIndex(false), m_isFolderWritable(true), m_dragging(false), m_url(url), m_viewPropertiesContext(), m_mode(DolphinView::IconsView), m_visibleRoles(), m_topLayout(nullptr), m_model(nullptr), m_view(nullptr), m_container(nullptr), m_toolTipManager(nullptr), m_selectionChangedTimer(nullptr), m_currentItemUrl(), m_scrollToCurrentItem(false), m_restoredContentsPosition(), m_selectedUrls(), m_clearSelectionBeforeSelectingNewItems(false), m_markFirstNewlySelectedItemAsCurrent(false), m_versionControlObserver(nullptr), m_twoClicksRenamingTimer(nullptr) { m_topLayout = new QVBoxLayout(this); m_topLayout->setSpacing(0); m_topLayout->setMargin(0); // When a new item has been created by the "Create New..." menu, the item should // get selected and it must be assured that the item will get visible. As the // creation is done asynchronously, several signals must be checked: connect(&DolphinNewFileMenuObserver::instance(), &DolphinNewFileMenuObserver::itemCreated, this, &DolphinView::observeCreatedItem); m_selectionChangedTimer = new QTimer(this); m_selectionChangedTimer->setSingleShot(true); m_selectionChangedTimer->setInterval(300); connect(m_selectionChangedTimer, &QTimer::timeout, this, &DolphinView::emitSelectionChangedSignal); m_model = new KFileItemModel(this); m_view = new DolphinItemListView(); m_view->setEnabledSelectionToggles(GeneralSettings::showSelectionToggle()); m_view->setVisibleRoles({"text"}); applyModeToView(); KItemListController* controller = new KItemListController(m_model, m_view, this); const int delay = GeneralSettings::autoExpandFolders() ? 750 : -1; controller->setAutoActivationDelay(delay); // The EnlargeSmallPreviews setting can only be changed after the model // has been set in the view by KItemListController. m_view->setEnlargeSmallPreviews(GeneralSettings::enlargeSmallPreviews()); m_container = new KItemListContainer(controller, this); m_container->installEventFilter(this); setFocusProxy(m_container); connect(m_container->horizontalScrollBar(), &QScrollBar::valueChanged, this, &DolphinView::hideToolTip); connect(m_container->verticalScrollBar(), &QScrollBar::valueChanged, this, &DolphinView::hideToolTip); controller->setSelectionBehavior(KItemListController::MultiSelection); connect(controller, &KItemListController::itemActivated, this, &DolphinView::slotItemActivated); connect(controller, &KItemListController::itemsActivated, this, &DolphinView::slotItemsActivated); connect(controller, &KItemListController::itemMiddleClicked, this, &DolphinView::slotItemMiddleClicked); connect(controller, &KItemListController::itemContextMenuRequested, this, &DolphinView::slotItemContextMenuRequested); connect(controller, &KItemListController::viewContextMenuRequested, this, &DolphinView::slotViewContextMenuRequested); connect(controller, &KItemListController::headerContextMenuRequested, this, &DolphinView::slotHeaderContextMenuRequested); connect(controller, &KItemListController::mouseButtonPressed, this, &DolphinView::slotMouseButtonPressed); connect(controller, &KItemListController::itemHovered, this, &DolphinView::slotItemHovered); connect(controller, &KItemListController::itemUnhovered, this, &DolphinView::slotItemUnhovered); connect(controller, &KItemListController::itemDropEvent, this, &DolphinView::slotItemDropEvent); connect(controller, &KItemListController::escapePressed, this, &DolphinView::stopLoading); connect(controller, &KItemListController::modelChanged, this, &DolphinView::slotModelChanged); connect(controller, &KItemListController::selectedItemTextPressed, this, &DolphinView::slotSelectedItemTextPressed); connect(m_model, &KFileItemModel::directoryLoadingStarted, this, &DolphinView::slotDirectoryLoadingStarted); connect(m_model, &KFileItemModel::directoryLoadingCompleted, this, &DolphinView::slotDirectoryLoadingCompleted); connect(m_model, &KFileItemModel::directoryLoadingCanceled, this, &DolphinView::directoryLoadingCanceled); connect(m_model, &KFileItemModel::directoryLoadingProgress, this, &DolphinView::directoryLoadingProgress); connect(m_model, &KFileItemModel::directorySortingProgress, this, &DolphinView::directorySortingProgress); connect(m_model, &KFileItemModel::itemsChanged, this, &DolphinView::slotItemsChanged); connect(m_model, &KFileItemModel::itemsRemoved, this, &DolphinView::itemCountChanged); connect(m_model, &KFileItemModel::itemsInserted, this, &DolphinView::itemCountChanged); connect(m_model, &KFileItemModel::infoMessage, this, &DolphinView::infoMessage); connect(m_model, &KFileItemModel::errorMessage, this, &DolphinView::errorMessage); connect(m_model, &KFileItemModel::directoryRedirection, this, &DolphinView::slotDirectoryRedirection); connect(m_model, &KFileItemModel::urlIsFileError, this, &DolphinView::urlIsFileError); m_view->installEventFilter(this); connect(m_view, &DolphinItemListView::sortOrderChanged, this, &DolphinView::slotSortOrderChangedByHeader); connect(m_view, &DolphinItemListView::sortRoleChanged, this, &DolphinView::slotSortRoleChangedByHeader); connect(m_view, &DolphinItemListView::visibleRolesChanged, this, &DolphinView::slotVisibleRolesChangedByHeader); connect(m_view, &DolphinItemListView::roleEditingCanceled, this, &DolphinView::slotRoleEditingCanceled); connect(m_view->header(), &KItemListHeader::columnWidthChangeFinished, this, &DolphinView::slotHeaderColumnWidthChangeFinished); KItemListSelectionManager* selectionManager = controller->selectionManager(); connect(selectionManager, &KItemListSelectionManager::selectionChanged, this, &DolphinView::slotSelectionChanged); m_toolTipManager = new ToolTipManager(this); connect(m_toolTipManager, &ToolTipManager::urlActivated, this, &DolphinView::urlActivated); m_versionControlObserver = new VersionControlObserver(this); m_versionControlObserver->setModel(m_model); connect(m_versionControlObserver, &VersionControlObserver::infoMessage, this, &DolphinView::infoMessage); connect(m_versionControlObserver, &VersionControlObserver::errorMessage, this, &DolphinView::errorMessage); connect(m_versionControlObserver, &VersionControlObserver::operationCompletedMessage, this, &DolphinView::operationCompletedMessage); m_twoClicksRenamingTimer = new QTimer(this); m_twoClicksRenamingTimer->setSingleShot(true); connect(m_twoClicksRenamingTimer, &QTimer::timeout, this, &DolphinView::slotTwoClicksRenamingTimerTimeout); applyViewProperties(); m_topLayout->addWidget(m_container); loadDirectory(url); } DolphinView::~DolphinView() { } QUrl DolphinView::url() const { return m_url; } void DolphinView::setActive(bool active) { if (active == m_active) { return; } m_active = active; updatePalette(); if (active) { m_container->setFocus(); emit activated(); emit writeStateChanged(m_isFolderWritable); } } bool DolphinView::isActive() const { return m_active; } void DolphinView::setMode(Mode mode) { if (mode != m_mode) { ViewProperties props(viewPropertiesUrl()); props.setViewMode(mode); // We pass the new ViewProperties to applyViewProperties, rather than // storing them on disk and letting applyViewProperties() read them // from there, to prevent that changing the view mode fails if the // .directory file is not writable (see bug 318534). applyViewProperties(props); } } DolphinView::Mode DolphinView::mode() const { return m_mode; } void DolphinView::setPreviewsShown(bool show) { if (previewsShown() == show) { return; } ViewProperties props(viewPropertiesUrl()); props.setPreviewsShown(show); const int oldZoomLevel = m_view->zoomLevel(); m_view->setPreviewsShown(show); emit previewsShownChanged(show); const int newZoomLevel = m_view->zoomLevel(); if (newZoomLevel != oldZoomLevel) { emit zoomLevelChanged(newZoomLevel, oldZoomLevel); } } bool DolphinView::previewsShown() const { return m_view->previewsShown(); } void DolphinView::setHiddenFilesShown(bool show) { if (m_model->showHiddenFiles() == show) { return; } const KFileItemList itemList = selectedItems(); m_selectedUrls.clear(); m_selectedUrls = itemList.urlList(); ViewProperties props(viewPropertiesUrl()); props.setHiddenFilesShown(show); m_model->setShowHiddenFiles(show); emit hiddenFilesShownChanged(show); } bool DolphinView::hiddenFilesShown() const { return m_model->showHiddenFiles(); } void DolphinView::setGroupedSorting(bool grouped) { if (grouped == groupedSorting()) { return; } ViewProperties props(viewPropertiesUrl()); props.setGroupedSorting(grouped); props.save(); m_container->controller()->model()->setGroupedSorting(grouped); emit groupedSortingChanged(grouped); } bool DolphinView::groupedSorting() const { return m_model->groupedSorting(); } KFileItemList DolphinView::items() const { KFileItemList list; const int itemCount = m_model->count(); list.reserve(itemCount); for (int i = 0; i < itemCount; ++i) { list.append(m_model->fileItem(i)); } return list; } int DolphinView::itemsCount() const { return m_model->count(); } KFileItemList DolphinView::selectedItems() const { const KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); KFileItemList selectedItems; const auto items = selectionManager->selectedItems(); selectedItems.reserve(items.count()); for (int index : items) { selectedItems.append(m_model->fileItem(index)); } return selectedItems; } int DolphinView::selectedItemsCount() const { const KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); return selectionManager->selectedItems().count(); } void DolphinView::markUrlsAsSelected(const QList& urls) { m_selectedUrls = urls; } void DolphinView::markUrlAsCurrent(const QUrl &url) { m_currentItemUrl = url; m_scrollToCurrentItem = true; } void DolphinView::selectItems(const QRegExp& pattern, bool enabled) { const KItemListSelectionManager::SelectionMode mode = enabled ? KItemListSelectionManager::Select : KItemListSelectionManager::Deselect; KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); for (int index = 0; index < m_model->count(); index++) { const KFileItem item = m_model->fileItem(index); if (pattern.exactMatch(item.text())) { // An alternative approach would be to store the matching items in a KItemSet and // select them in one go after the loop, but we'd need a new function // KItemListSelectionManager::setSelected(KItemSet, SelectionMode mode) // for that. selectionManager->setSelected(index, 1, mode); } } } void DolphinView::setZoomLevel(int level) { const int oldZoomLevel = zoomLevel(); m_view->setZoomLevel(level); if (zoomLevel() != oldZoomLevel) { hideToolTip(); emit zoomLevelChanged(zoomLevel(), oldZoomLevel); } } int DolphinView::zoomLevel() const { return m_view->zoomLevel(); } void DolphinView::setSortRole(const QByteArray& role) { if (role != sortRole()) { updateSortRole(role); } } QByteArray DolphinView::sortRole() const { const KItemModelBase* model = m_container->controller()->model(); return model->sortRole(); } void DolphinView::setSortOrder(Qt::SortOrder order) { if (sortOrder() != order) { updateSortOrder(order); } } Qt::SortOrder DolphinView::sortOrder() const { return m_model->sortOrder(); } void DolphinView::setSortFoldersFirst(bool foldersFirst) { if (sortFoldersFirst() != foldersFirst) { updateSortFoldersFirst(foldersFirst); } } bool DolphinView::sortFoldersFirst() const { return m_model->sortDirectoriesFirst(); } void DolphinView::setVisibleRoles(const QList& roles) { const QList previousRoles = roles; ViewProperties props(viewPropertiesUrl()); props.setVisibleRoles(roles); m_visibleRoles = roles; m_view->setVisibleRoles(roles); emit visibleRolesChanged(m_visibleRoles, previousRoles); } QList DolphinView::visibleRoles() const { return m_visibleRoles; } void DolphinView::reload() { QByteArray viewState; QDataStream saveStream(&viewState, QIODevice::WriteOnly); saveState(saveStream); setUrl(url()); loadDirectory(url(), true); QDataStream restoreStream(viewState); restoreState(restoreStream); } void DolphinView::readSettings() { const int oldZoomLevel = m_view->zoomLevel(); GeneralSettings::self()->load(); m_view->readSettings(); applyViewProperties(); const int delay = GeneralSettings::autoExpandFolders() ? 750 : -1; m_container->controller()->setAutoActivationDelay(delay); const int newZoomLevel = m_view->zoomLevel(); if (newZoomLevel != oldZoomLevel) { emit zoomLevelChanged(newZoomLevel, oldZoomLevel); } } void DolphinView::writeSettings() { GeneralSettings::self()->save(); m_view->writeSettings(); } void DolphinView::setNameFilter(const QString& nameFilter) { m_model->setNameFilter(nameFilter); } QString DolphinView::nameFilter() const { return m_model->nameFilter(); } void DolphinView::setMimeTypeFilters(const QStringList& filters) { return m_model->setMimeTypeFilters(filters); } QStringList DolphinView::mimeTypeFilters() const { return m_model->mimeTypeFilters(); } QString DolphinView::statusBarText() const { QString summary; QString foldersText; QString filesText; int folderCount = 0; int fileCount = 0; KIO::filesize_t totalFileSize = 0; if (m_container->controller()->selectionManager()->hasSelection()) { // Give a summary of the status of the selected files const KFileItemList list = selectedItems(); foreach (const KFileItem& item, list) { if (item.isDir()) { ++folderCount; } else { ++fileCount; totalFileSize += item.size(); } } if (folderCount + fileCount == 1) { // If only one item is selected, show info about it return list.first().getStatusBarInfo(); } else { // At least 2 items are selected foldersText = i18ncp("@info:status", "1 Folder selected", "%1 Folders selected", folderCount); filesText = i18ncp("@info:status", "1 File selected", "%1 Files selected", fileCount); } } else { calculateItemCount(fileCount, folderCount, totalFileSize); foldersText = i18ncp("@info:status", "1 Folder", "%1 Folders", folderCount); filesText = i18ncp("@info:status", "1 File", "%1 Files", fileCount); } if (fileCount > 0 && folderCount > 0) { summary = i18nc("@info:status folders, files (size)", "%1, %2 (%3)", foldersText, filesText, KFormat().formatByteSize(totalFileSize)); } else if (fileCount > 0) { summary = i18nc("@info:status files (size)", "%1 (%2)", filesText, KFormat().formatByteSize(totalFileSize)); } else if (folderCount > 0) { summary = foldersText; } else { summary = i18nc("@info:status", "0 Folders, 0 Files"); } return summary; } QList DolphinView::versionControlActions(const KFileItemList& items) const { QList actions; if (items.isEmpty()) { const KFileItem item = m_model->rootItem(); if (!item.isNull()) { actions = m_versionControlObserver->actions(KFileItemList() << item); } } else { actions = m_versionControlObserver->actions(items); } return actions; } void DolphinView::setUrl(const QUrl& url) { if (url == m_url) { return; } clearSelection(); m_url = url; hideToolTip(); disconnect(m_view, &DolphinItemListView::roleEditingFinished, this, &DolphinView::slotRoleEditingFinished); // 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 // anyhow afterwards by loadDirectory(). m_model->clear(); applyViewProperties(); loadDirectory(url); emit urlChanged(url); } void DolphinView::selectAll() { KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); selectionManager->setSelected(0, m_model->count()); } void DolphinView::invertSelection() { KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); selectionManager->setSelected(0, m_model->count(), KItemListSelectionManager::Toggle); } void DolphinView::clearSelection() { m_selectedUrls.clear(); m_container->controller()->selectionManager()->clearSelection(); } void DolphinView::renameSelectedItems() { const KFileItemList items = selectedItems(); if (items.isEmpty()) { return; } if (items.count() == 1 && GeneralSettings::renameInline()) { const int index = m_model->index(items.first()); m_view->editRole(index, "text"); hideToolTip(); connect(m_view, &DolphinItemListView::roleEditingFinished, this, &DolphinView::slotRoleEditingFinished); } else { RenameDialog* dialog = new RenameDialog(this, items); connect(dialog, &RenameDialog::renamingFinished, this, &DolphinView::slotRenameDialogRenamingFinished); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); dialog->raise(); dialog->activateWindow(); } // Assure that the current index remains visible when KFileItemModel // will notify the view about changed items (which might result in // a changed sorting). m_assureVisibleCurrentIndex = true; } void DolphinView::trashSelectedItems() { const QList list = simplifiedSelectedUrls(); KIO::JobUiDelegate uiDelegate; uiDelegate.setWindow(window()); if (uiDelegate.askDeleteConfirmation(list, KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation)) { KIO::Job* job = KIO::trash(list); KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Trash, list, QUrl(QStringLiteral("trash:/")), job); KJobWidgets::setWindow(job, this); connect(job, &KIO::Job::result, this, &DolphinView::slotTrashFileFinished); } } void DolphinView::deleteSelectedItems() { const QList list = simplifiedSelectedUrls(); KIO::JobUiDelegate uiDelegate; uiDelegate.setWindow(window()); if (uiDelegate.askDeleteConfirmation(list, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::DefaultConfirmation)) { KIO::Job* job = KIO::del(list); KJobWidgets::setWindow(job, this); connect(job, &KIO::Job::result, this, &DolphinView::slotDeleteFileFinished); } } void DolphinView::cutSelectedItems() { QMimeData* mimeData = selectionMimeData(); KIO::setClipboardDataCut(mimeData, true); QApplication::clipboard()->setMimeData(mimeData); } void DolphinView::copySelectedItems() { QMimeData* mimeData = selectionMimeData(); QApplication::clipboard()->setMimeData(mimeData); } void DolphinView::paste() { pasteToUrl(url()); } void DolphinView::pasteIntoFolder() { const KFileItemList items = selectedItems(); if ((items.count() == 1) && items.first().isDir()) { pasteToUrl(items.first().url()); } } void DolphinView::stopLoading() { m_model->cancelDirectoryLoading(); } void DolphinView::updatePalette() { QColor color = KColorScheme(QPalette::Active, KColorScheme::View).background().color(); if (!m_active) { color.setAlpha(150); } QWidget* viewport = m_container->viewport(); if (viewport) { QPalette palette; palette.setColor(viewport->backgroundRole(), color); viewport->setPalette(palette); } update(); } void DolphinView::abortTwoClicksRenaming() { m_twoClicksRenamingItemUrl.clear(); m_twoClicksRenamingTimer->stop(); } bool DolphinView::eventFilter(QObject* watched, QEvent* event) { switch (event->type()) { case QEvent::PaletteChange: updatePalette(); QPixmapCache::clear(); break; case QEvent::KeyPress: if (GeneralSettings::useTabForSwitchingSplitView()) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Tab && keyEvent->modifiers() == Qt::NoModifier) { emit toggleActiveViewRequested(); return true; } } break; case QEvent::FocusIn: if (watched == m_container) { setActive(true); } break; case QEvent::GraphicsSceneDragEnter: if (watched == m_view) { m_dragging = true; } break; case QEvent::GraphicsSceneDragLeave: if (watched == m_view) { m_dragging = false; } break; case QEvent::GraphicsSceneDrop: if (watched == m_view) { m_dragging = false; } default: break; } return QWidget::eventFilter(watched, event); } void DolphinView::wheelEvent(QWheelEvent* event) { if (event->modifiers().testFlag(Qt::ControlModifier)) { const int numDegrees = event->delta() / 8; const int numSteps = numDegrees / 15; setZoomLevel(zoomLevel() + numSteps); event->accept(); } else { event->ignore(); } } void DolphinView::hideEvent(QHideEvent* event) { hideToolTip(); QWidget::hideEvent(event); } bool DolphinView::event(QEvent* event) { if (event->type() == QEvent::WindowDeactivate) { /* See Bug 297355 * Dolphin leaves file preview tooltips open even when is not visible. * * Hide tool-tip when Dolphin loses focus. */ hideToolTip(); abortTwoClicksRenaming(); } return QWidget::event(event); } void DolphinView::activate() { setActive(true); } void DolphinView::slotItemActivated(int index) { abortTwoClicksRenaming(); const KFileItem item = m_model->fileItem(index); if (!item.isNull()) { emit itemActivated(item); } } void DolphinView::slotItemsActivated(const KItemSet& indexes) { Q_ASSERT(indexes.count() >= 2); abortTwoClicksRenaming(); if (indexes.count() > 5) { QString question = i18np("Are you sure you want to open 1 item?", "Are you sure you want to open %1 items?", indexes.count()); const int answer = KMessageBox::warningYesNo(this, question); if (answer != KMessageBox::Yes) { return; } } KFileItemList items; items.reserve(indexes.count()); for (int index : indexes) { KFileItem item = m_model->fileItem(index); const QUrl& url = openItemAsFolderUrl(item); if (!url.isEmpty()) { // Open folders in new tabs emit tabRequested(url); } else { items.append(item); } } if (items.count() == 1) { emit itemActivated(items.first()); } else if (items.count() > 1) { emit itemsActivated(items); } } void DolphinView::slotItemMiddleClicked(int index) { const KFileItem& item = m_model->fileItem(index); const QUrl& url = openItemAsFolderUrl(item); if (!url.isEmpty()) { emit tabRequested(url); } else if (isTabsForFilesEnabled()) { emit tabRequested(item.url()); } } void DolphinView::slotItemContextMenuRequested(int index, const QPointF& pos) { // Force emit of a selection changed signal before we request the // context menu, to update the edit-actions first. (See Bug 294013) if (m_selectionChangedTimer->isActive()) { emitSelectionChangedSignal(); } const KFileItem item = m_model->fileItem(index); emit requestContextMenu(pos.toPoint(), item, url(), QList()); } void DolphinView::slotViewContextMenuRequested(const QPointF& pos) { emit requestContextMenu(pos.toPoint(), KFileItem(), url(), QList()); } void DolphinView::slotHeaderContextMenuRequested(const QPointF& pos) { ViewProperties props(viewPropertiesUrl()); QPointer menu = new QMenu(QApplication::activeWindow()); KItemListView* view = m_container->controller()->view(); const QSet visibleRolesSet = view->visibleRoles().toSet(); bool indexingEnabled = false; #ifdef HAVE_BALOO Baloo::IndexerConfig config; indexingEnabled = config.fileIndexingEnabled(); #endif QString groupName; QMenu* groupMenu = nullptr; // Add all roles to the menu that can be shown or hidden by the user const QList rolesInfo = KFileItemModel::rolesInformation(); foreach (const KFileItemModel::RoleInfo& info, rolesInfo) { if (info.role == "text") { // It should not be possible to hide the "text" role continue; } const QString text = m_model->roleDescription(info.role); QAction* action = nullptr; if (info.group.isEmpty()) { action = menu->addAction(text); } else { if (!groupMenu || info.group != groupName) { groupName = info.group; groupMenu = menu->addMenu(groupName); } action = groupMenu->addAction(text); } action->setCheckable(true); action->setChecked(visibleRolesSet.contains(info.role)); action->setData(info.role); const bool enable = (!info.requiresBaloo && !info.requiresIndexer) || (info.requiresBaloo) || (info.requiresIndexer && indexingEnabled); action->setEnabled(enable); } menu->addSeparator(); QActionGroup* widthsGroup = new QActionGroup(menu); const bool autoColumnWidths = props.headerColumnWidths().isEmpty(); QAction* autoAdjustWidthsAction = menu->addAction(i18nc("@action:inmenu", "Automatic Column Widths")); autoAdjustWidthsAction->setCheckable(true); autoAdjustWidthsAction->setChecked(autoColumnWidths); autoAdjustWidthsAction->setActionGroup(widthsGroup); QAction* customWidthsAction = menu->addAction(i18nc("@action:inmenu", "Custom Column Widths")); customWidthsAction->setCheckable(true); customWidthsAction->setChecked(!autoColumnWidths); customWidthsAction->setActionGroup(widthsGroup); QAction* action = menu->exec(pos.toPoint()); if (menu && action) { KItemListHeader* header = view->header(); if (action == autoAdjustWidthsAction) { // Clear the column-widths from the viewproperties and turn on // the automatic resizing of the columns props.setHeaderColumnWidths(QList()); header->setAutomaticColumnResizing(true); } else if (action == customWidthsAction) { // Apply the current column-widths as custom column-widths and turn // off the automatic resizing of the columns QList columnWidths; columnWidths.reserve(view->visibleRoles().count()); foreach (const QByteArray& role, view->visibleRoles()) { columnWidths.append(header->columnWidth(role)); } props.setHeaderColumnWidths(columnWidths); header->setAutomaticColumnResizing(false); } else { // Show or hide the selected role const QByteArray selectedRole = action->data().toByteArray(); QList visibleRoles = view->visibleRoles(); if (action->isChecked()) { visibleRoles.append(selectedRole); } else { visibleRoles.removeOne(selectedRole); } view->setVisibleRoles(visibleRoles); props.setVisibleRoles(visibleRoles); QList columnWidths; if (!header->automaticColumnResizing()) { columnWidths.reserve(view->visibleRoles().count()); foreach (const QByteArray& role, view->visibleRoles()) { columnWidths.append(header->columnWidth(role)); } } props.setHeaderColumnWidths(columnWidths); } } delete menu; } void DolphinView::slotHeaderColumnWidthChangeFinished(const QByteArray& role, qreal current) { const QList visibleRoles = m_view->visibleRoles(); ViewProperties props(viewPropertiesUrl()); QList columnWidths = props.headerColumnWidths(); if (columnWidths.count() != visibleRoles.count()) { columnWidths.clear(); columnWidths.reserve(visibleRoles.count()); const KItemListHeader* header = m_view->header(); foreach (const QByteArray& role, visibleRoles) { const int width = header->columnWidth(role); columnWidths.append(width); } } const int roleIndex = visibleRoles.indexOf(role); Q_ASSERT(roleIndex >= 0 && roleIndex < columnWidths.count()); columnWidths[roleIndex] = current; props.setHeaderColumnWidths(columnWidths); } void DolphinView::slotItemHovered(int index) { const KFileItem item = m_model->fileItem(index); if (GeneralSettings::showToolTips() && !m_dragging) { QRectF itemRect = m_container->controller()->view()->itemContextRect(index); const QPoint pos = m_container->mapToGlobal(itemRect.topLeft().toPoint()); itemRect.moveTo(pos); m_toolTipManager->showToolTip(item, itemRect, nativeParentWidget()->windowHandle()); } emit requestItemInfo(item); } void DolphinView::slotItemUnhovered(int index) { Q_UNUSED(index); hideToolTip(); emit requestItemInfo(KFileItem()); } void DolphinView::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event) { QUrl destUrl; KFileItem destItem = m_model->fileItem(index); if (destItem.isNull() || (!destItem.isDir() && !destItem.isDesktopFile())) { // Use the URL of the view as drop target if the item is no directory // or desktop-file destItem = m_model->rootItem(); destUrl = url(); } else { // The item represents a directory or desktop-file destUrl = destItem.mostLocalUrl(); } QDropEvent dropEvent(event->pos().toPoint(), event->possibleActions(), event->mimeData(), event->buttons(), event->modifiers()); dropUrls(destUrl, &dropEvent, this); setActive(true); } void DolphinView::dropUrls(const QUrl &destUrl, QDropEvent *dropEvent, QWidget *dropWidget) { KIO::DropJob* job = DragAndDropHelper::dropUrls(destUrl, dropEvent, dropWidget); if (job) { connect(job, &KIO::DropJob::result, this, &DolphinView::slotPasteJobResult); if (destUrl == url()) { // Mark the dropped urls as selected. m_clearSelectionBeforeSelectingNewItems = true; m_markFirstNewlySelectedItemAsCurrent = true; connect(job, &KIO::DropJob::itemCreated, this, &DolphinView::slotItemCreated); } } } void DolphinView::slotModelChanged(KItemModelBase* current, KItemModelBase* previous) { if (previous != nullptr) { Q_ASSERT(qobject_cast(previous)); KFileItemModel* fileItemModel = static_cast(previous); disconnect(fileItemModel, &KFileItemModel::directoryLoadingCompleted, this, &DolphinView::slotDirectoryLoadingCompleted); m_versionControlObserver->setModel(nullptr); } if (current) { Q_ASSERT(qobject_cast(current)); KFileItemModel* fileItemModel = static_cast(current); connect(fileItemModel, &KFileItemModel::directoryLoadingCompleted, this, &DolphinView::slotDirectoryLoadingCompleted); m_versionControlObserver->setModel(fileItemModel); } } void DolphinView::slotMouseButtonPressed(int itemIndex, Qt::MouseButtons buttons) { Q_UNUSED(itemIndex); hideToolTip(); if (buttons & Qt::BackButton) { emit goBackRequested(); } else if (buttons & Qt::ForwardButton) { emit goForwardRequested(); } } void DolphinView::slotSelectedItemTextPressed(int index) { if (GeneralSettings::renameInline()) { const KFileItem item = m_model->fileItem(index); const KFileItemListProperties capabilities(KFileItemList() << item); if (capabilities.supportsMoving()) { m_twoClicksRenamingItemUrl = item.url(); m_twoClicksRenamingTimer->start(QApplication::doubleClickInterval()); } } } void DolphinView::slotItemCreated(const QUrl& url) { if (m_markFirstNewlySelectedItemAsCurrent) { markUrlAsCurrent(url); m_markFirstNewlySelectedItemAsCurrent = false; } m_selectedUrls << url; } void DolphinView::slotPasteJobResult(KJob *job) { if (job->error()) { emit errorMessage(job->errorString()); } if (!m_selectedUrls.isEmpty()) { m_selectedUrls << KDirModel::simplifiedUrlList(m_selectedUrls); } } void DolphinView::slotSelectionChanged(const KItemSet& current, const KItemSet& 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 // be emitted asynchronously as fast as possible to update the edit-actions. m_selectionChangedTimer->setInterval(selectionStateChanged ? 0 : 300); m_selectionChangedTimer->start(); } void DolphinView::emitSelectionChangedSignal() { m_selectionChangedTimer->stop(); emit selectionChanged(selectedItems()); } void DolphinView::updateSortRole(const QByteArray& role) { ViewProperties props(viewPropertiesUrl()); props.setSortRole(role); KItemModelBase* model = m_container->controller()->model(); model->setSortRole(role); emit sortRoleChanged(role); } void DolphinView::updateSortOrder(Qt::SortOrder order) { ViewProperties props(viewPropertiesUrl()); props.setSortOrder(order); m_model->setSortOrder(order); emit sortOrderChanged(order); } void DolphinView::updateSortFoldersFirst(bool foldersFirst) { ViewProperties props(viewPropertiesUrl()); props.setSortFoldersFirst(foldersFirst); m_model->setSortDirectoriesFirst(foldersFirst); emit sortFoldersFirstChanged(foldersFirst); } QPair DolphinView::pasteInfo() const { const QMimeData *mimeData = QApplication::clipboard()->mimeData(); QPair info; info.second = KIO::pasteActionText(mimeData, &info.first, rootItem()); return info; } void DolphinView::setTabsForFilesEnabled(bool tabsForFiles) { m_tabsForFiles = tabsForFiles; } bool DolphinView::isTabsForFilesEnabled() const { return m_tabsForFiles; } bool DolphinView::itemsExpandable() const { return m_mode == DetailsView; } void DolphinView::restoreState(QDataStream& stream) { // Read the version number of the view state and check if the version is supported. quint32 version = 0; stream >> version; if (version != 1) { // The version of the view state isn't supported, we can't restore it. return; } // Restore the current item that had the keyboard focus stream >> m_currentItemUrl; // Restore the previously selected items stream >> m_selectedUrls; // Restore the view position stream >> m_restoredContentsPosition; // Restore expanded folders (only relevant for the details view - will be ignored by the view in other view modes) QSet urls; stream >> urls; m_model->restoreExpandedDirectories(urls); } void DolphinView::saveState(QDataStream& stream) { stream << quint32(1); // View state version // Save the current item that has the keyboard focus const int currentIndex = m_container->controller()->selectionManager()->currentItem(); if (currentIndex != -1) { KFileItem item = m_model->fileItem(currentIndex); Q_ASSERT(!item.isNull()); // If the current index is valid a item must exist QUrl currentItemUrl = item.url(); stream << currentItemUrl; } else { stream << QUrl(); } // Save the selected urls stream << selectedItems().urlList(); // Save view position const qreal x = m_container->horizontalScrollBar()->value(); const qreal y = m_container->verticalScrollBar()->value(); stream << QPoint(x, y); // Save expanded folders (only relevant for the details view - the set will be empty in other view modes) stream << m_model->expandedDirectories(); } KFileItem DolphinView::rootItem() const { return m_model->rootItem(); } void DolphinView::setViewPropertiesContext(const QString& context) { m_viewPropertiesContext = context; } QString DolphinView::viewPropertiesContext() const { return m_viewPropertiesContext; } QUrl DolphinView::openItemAsFolderUrl(const KFileItem& item, const bool browseThroughArchives) { if (item.isNull()) { return QUrl(); } QUrl url = item.targetUrl(); if (item.isDir()) { return url; } if (item.isMimeTypeKnown()) { const QString& mimetype = item.mimetype(); if (browseThroughArchives && item.isFile() && url.isLocalFile()) { // Generic mechanism for redirecting to tar:// when clicking on a tar file, // zip:// when clicking on a zip file, etc. // The .protocol file specifies the mimetype that the kioslave handles. // Note that we don't use mimetype inheritance since we don't want to // open OpenDocument files as zip folders... const QString& protocol = KProtocolManager::protocolForArchiveMimetype(mimetype); if (!protocol.isEmpty()) { url.setScheme(protocol); return url; } } if (mimetype == QLatin1String("application/x-desktop")) { // Redirect to the URL in Type=Link desktop files, unless it is a http(s) URL. KDesktopFile desktopFile(url.toLocalFile()); if (desktopFile.hasLinkType()) { const QString linkUrl = desktopFile.readUrl(); if (!linkUrl.startsWith(QLatin1String("http"))) { return QUrl::fromUserInput(linkUrl); } } } } return QUrl(); } void DolphinView::observeCreatedItem(const QUrl& url) { if (m_active) { forceUrlsSelection(url, {url}); } } void DolphinView::slotDirectoryRedirection(const QUrl& oldUrl, const QUrl& newUrl) { if (oldUrl.matches(url(), QUrl::StripTrailingSlash)) { emit redirection(oldUrl, newUrl); m_url = newUrl; // #186947 } } void DolphinView::updateViewState() { if (m_currentItemUrl != QUrl()) { KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); // if there is a selection already, leave it that way if (!selectionManager->hasSelection()) { const int currentIndex = m_model->index(m_currentItemUrl); if (currentIndex != -1) { selectionManager->setCurrentItem(currentIndex); // scroll to current item and reset the state if (m_scrollToCurrentItem) { m_view->scrollToItem(currentIndex); m_scrollToCurrentItem = false; } } else { selectionManager->setCurrentItem(0); } } m_currentItemUrl = QUrl(); } if (!m_restoredContentsPosition.isNull()) { const int x = m_restoredContentsPosition.x(); const int y = m_restoredContentsPosition.y(); m_restoredContentsPosition = QPoint(); m_container->horizontalScrollBar()->setValue(x); m_container->verticalScrollBar()->setValue(y); } if (!m_selectedUrls.isEmpty()) { KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); // if there is a selection already, leave it that way if (!selectionManager->hasSelection()) { if (m_clearSelectionBeforeSelectingNewItems) { selectionManager->clearSelection(); m_clearSelectionBeforeSelectingNewItems = false; } KItemSet selectedItems = selectionManager->selectedItems(); QList::iterator it = m_selectedUrls.begin(); while (it != m_selectedUrls.end()) { const int index = m_model->index(*it); if (index >= 0) { selectedItems.insert(index); it = m_selectedUrls.erase(it); } else { ++it; } } selectionManager->beginAnchoredSelection(selectionManager->currentItem()); selectionManager->setSelectedItems(selectedItems); } } } void DolphinView::hideToolTip() { if (GeneralSettings::showToolTips()) { m_toolTipManager->hideToolTip(); } } void DolphinView::calculateItemCount(int& fileCount, int& folderCount, KIO::filesize_t& totalFileSize) const { const int itemCount = m_model->count(); for (int i = 0; i < itemCount; ++i) { const KFileItem item = m_model->fileItem(i); if (item.isDir()) { ++folderCount; } else { ++fileCount; totalFileSize += item.size(); } } } void DolphinView::slotTwoClicksRenamingTimerTimeout() { const KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); // verify that only one item is selected and that no item is dragged if (selectionManager->selectedItems().count() == 1 && !m_dragging) { const int index = selectionManager->currentItem(); const QUrl fileItemUrl = m_model->fileItem(index).url(); // check if the selected item was the same item that started the twoClicksRenaming if (fileItemUrl.isValid() && m_twoClicksRenamingItemUrl == fileItemUrl) { renameSelectedItems(); } } } void DolphinView::slotTrashFileFinished(KJob* job) { if (job->error() == 0) { emit operationCompletedMessage(i18nc("@info:status", "Trash operation completed.")); } else if (job->error() != KIO::ERR_USER_CANCELED) { emit errorMessage(job->errorString()); } } void DolphinView::slotDeleteFileFinished(KJob* job) { if (job->error() == 0) { emit operationCompletedMessage(i18nc("@info:status", "Delete operation completed.")); } else if (job->error() != KIO::ERR_USER_CANCELED) { emit errorMessage(job->errorString()); } } void DolphinView::slotRenamingResult(KJob* job) { if (job->error()) { KIO::CopyJob *copyJob = qobject_cast(job); Q_ASSERT(copyJob); const QUrl newUrl = copyJob->destUrl(); const int index = m_model->index(newUrl); if (index >= 0) { QHash data; const QUrl oldUrl = copyJob->srcUrls().at(0); data.insert("text", oldUrl.fileName()); m_model->setData(index, data); } } } void DolphinView::slotDirectoryLoadingStarted() { // Disable the writestate temporary until it can be determined in a fast way // in DolphinView::slotLoadingCompleted() if (m_isFolderWritable) { m_isFolderWritable = false; emit writeStateChanged(m_isFolderWritable); } emit directoryLoadingStarted(); } void DolphinView::slotDirectoryLoadingCompleted() { // Update the view-state. This has to be done asynchronously // because the view might not be in its final state yet. QTimer::singleShot(0, this, &DolphinView::updateViewState); emit directoryLoadingCompleted(); updateWritableState(); } void DolphinView::slotItemsChanged() { m_assureVisibleCurrentIndex = false; } void DolphinView::slotSortOrderChangedByHeader(Qt::SortOrder current, Qt::SortOrder previous) { Q_UNUSED(previous); Q_ASSERT(m_model->sortOrder() == current); ViewProperties props(viewPropertiesUrl()); props.setSortOrder(current); emit sortOrderChanged(current); } void DolphinView::slotSortRoleChangedByHeader(const QByteArray& current, const QByteArray& previous) { Q_UNUSED(previous); Q_ASSERT(m_model->sortRole() == current); ViewProperties props(viewPropertiesUrl()); props.setSortRole(current); emit sortRoleChanged(current); } void DolphinView::slotVisibleRolesChangedByHeader(const QList& current, const QList& previous) { Q_UNUSED(previous); Q_ASSERT(m_container->controller()->view()->visibleRoles() == current); const QList previousVisibleRoles = m_visibleRoles; m_visibleRoles = current; ViewProperties props(viewPropertiesUrl()); props.setVisibleRoles(m_visibleRoles); emit visibleRolesChanged(m_visibleRoles, previousVisibleRoles); } void DolphinView::slotRoleEditingCanceled() { disconnect(m_view, &DolphinItemListView::roleEditingFinished, this, &DolphinView::slotRoleEditingFinished); } void DolphinView::slotRoleEditingFinished(int index, const QByteArray& role, const QVariant& value) { disconnect(m_view, &DolphinItemListView::roleEditingFinished, this, &DolphinView::slotRoleEditingFinished); if (index < 0 || index >= m_model->count()) { return; } if (role == "text") { const KFileItem oldItem = m_model->fileItem(index); const QString newName = value.toString(); if (!newName.isEmpty() && newName != oldItem.text() && newName != QLatin1String(".") && newName != QLatin1String("..")) { const QUrl oldUrl = oldItem.url(); QUrl newUrl = oldUrl.adjusted(QUrl::RemoveFilename); newUrl.setPath(newUrl.path() + KIO::encodeFileName(newName)); const bool newNameExistsAlready = (m_model->index(newUrl) >= 0); if (!newNameExistsAlready) { // Only change the data in the model if no item with the new name // is in the model yet. If there is an item with the new name // already, calling KIO::CopyJob will open a dialog // asking for a new name, and KFileItemModel will update the // data when the dir lister signals that the file name has changed. QHash data; data.insert(role, value); m_model->setData(index, data); } KIO::Job * job = KIO::moveAs(oldUrl, newUrl); KJobWidgets::setWindow(job, this); KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, {oldUrl}, newUrl, job); job->uiDelegate()->setAutoErrorHandlingEnabled(true); forceUrlsSelection(newUrl, {newUrl}); if (!newNameExistsAlready) { // Only connect the result signal if there is no item with the new name // in the model yet, see bug 328262. connect(job, &KJob::result, this, &DolphinView::slotRenamingResult); } } } } void DolphinView::loadDirectory(const QUrl& url, bool reload) { if (!url.isValid()) { const QString location(url.toDisplayString(QUrl::PreferLocalFile)); if (location.isEmpty()) { emit errorMessage(i18nc("@info:status", "The location is empty.")); } else { emit errorMessage(i18nc("@info:status", "The location '%1' is invalid.", location)); } return; } if (reload) { m_model->refreshDirectory(url); } else { m_model->loadDirectory(url); } } void DolphinView::applyViewProperties() { const ViewProperties props(viewPropertiesUrl()); applyViewProperties(props); } void DolphinView::applyViewProperties(const ViewProperties& props) { m_view->beginTransaction(); const Mode mode = props.viewMode(); if (m_mode != mode) { const Mode previousMode = m_mode; m_mode = mode; // Changing the mode might result in changing // the zoom level. Remember the old zoom level so // that zoomLevelChanged() can get emitted. const int oldZoomLevel = m_view->zoomLevel(); applyModeToView(); emit modeChanged(m_mode, previousMode); if (m_view->zoomLevel() != oldZoomLevel) { emit zoomLevelChanged(m_view->zoomLevel(), oldZoomLevel); } } const bool hiddenFilesShown = props.hiddenFilesShown(); if (hiddenFilesShown != m_model->showHiddenFiles()) { m_model->setShowHiddenFiles(hiddenFilesShown); emit hiddenFilesShownChanged(hiddenFilesShown); } const bool groupedSorting = props.groupedSorting(); if (groupedSorting != m_model->groupedSorting()) { m_model->setGroupedSorting(groupedSorting); emit groupedSortingChanged(groupedSorting); } const QByteArray sortRole = props.sortRole(); if (sortRole != m_model->sortRole()) { m_model->setSortRole(sortRole); emit sortRoleChanged(sortRole); } const Qt::SortOrder sortOrder = props.sortOrder(); if (sortOrder != m_model->sortOrder()) { m_model->setSortOrder(sortOrder); emit sortOrderChanged(sortOrder); } const bool sortFoldersFirst = props.sortFoldersFirst(); if (sortFoldersFirst != m_model->sortDirectoriesFirst()) { m_model->setSortDirectoriesFirst(sortFoldersFirst); emit sortFoldersFirstChanged(sortFoldersFirst); } const QList visibleRoles = props.visibleRoles(); if (visibleRoles != m_visibleRoles) { const QList previousVisibleRoles = m_visibleRoles; m_visibleRoles = visibleRoles; m_view->setVisibleRoles(visibleRoles); emit visibleRolesChanged(m_visibleRoles, previousVisibleRoles); } const bool previewsShown = props.previewsShown(); if (previewsShown != m_view->previewsShown()) { const int oldZoomLevel = zoomLevel(); m_view->setPreviewsShown(previewsShown); emit previewsShownChanged(previewsShown); // Changing the preview-state might result in a changed zoom-level if (oldZoomLevel != zoomLevel()) { emit zoomLevelChanged(zoomLevel(), oldZoomLevel); } } KItemListView* itemListView = m_container->controller()->view(); if (itemListView->isHeaderVisible()) { KItemListHeader* header = itemListView->header(); const QList headerColumnWidths = props.headerColumnWidths(); const int rolesCount = m_visibleRoles.count(); if (headerColumnWidths.count() == rolesCount) { header->setAutomaticColumnResizing(false); QHash columnWidths; for (int i = 0; i < rolesCount; ++i) { columnWidths.insert(m_visibleRoles[i], headerColumnWidths[i]); } header->setColumnWidths(columnWidths); } else { header->setAutomaticColumnResizing(true); } } m_view->endTransaction(); } void DolphinView::applyModeToView() { switch (m_mode) { case IconsView: m_view->setItemLayout(KFileItemListView::IconsLayout); break; case CompactView: m_view->setItemLayout(KFileItemListView::CompactLayout); break; case DetailsView: m_view->setItemLayout(KFileItemListView::DetailsLayout); break; default: Q_ASSERT(false); break; } } void DolphinView::pasteToUrl(const QUrl& url) { KIO::PasteJob *job = KIO::paste(QApplication::clipboard()->mimeData(), url); KJobWidgets::setWindow(job, this); m_clearSelectionBeforeSelectingNewItems = true; m_markFirstNewlySelectedItemAsCurrent = true; connect(job, &KIO::PasteJob::itemCreated, this, &DolphinView::slotItemCreated); connect(job, &KIO::PasteJob::result, this, &DolphinView::slotPasteJobResult); } QList DolphinView::simplifiedSelectedUrls() const { QList urls; const KFileItemList items = selectedItems(); urls.reserve(items.count()); foreach (const KFileItem& item, items) { urls.append(item.url()); } if (itemsExpandable()) { // TODO: Check if we still need KDirModel for this in KDE 5.0 urls = KDirModel::simplifiedUrlList(urls); } return urls; } QMimeData* DolphinView::selectionMimeData() const { const KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); const KItemSet selectedIndexes = selectionManager->selectedItems(); return m_model->createMimeData(selectedIndexes); } void DolphinView::updateWritableState() { const bool wasFolderWritable = m_isFolderWritable; m_isFolderWritable = false; KFileItem item = m_model->rootItem(); if (item.isNull()) { // Try to find out if the URL is writable even if the "root item" is // null, see https://bugs.kde.org/show_bug.cgi?id=330001 item = KFileItem(url()); item.setDelayedMimeTypes(true); } KFileItemListProperties capabilities(KFileItemList() << item); m_isFolderWritable = capabilities.supportsWriting(); if (m_isFolderWritable != wasFolderWritable) { emit writeStateChanged(m_isFolderWritable); } } QUrl DolphinView::viewPropertiesUrl() const { if (m_viewPropertiesContext.isEmpty()) { return m_url; } QUrl url; url.setScheme(m_url.scheme()); url.setPath(m_viewPropertiesContext); return url; } void DolphinView::slotRenameDialogRenamingFinished(const QList& urls) { forceUrlsSelection(urls.first(), urls); } void DolphinView::forceUrlsSelection(const QUrl& current, const QList& selected) { clearSelection(); m_clearSelectionBeforeSelectingNewItems = true; markUrlAsCurrent(current); markUrlsAsSelected(selected); } diff --git a/src/views/dolphinviewactionhandler.cpp b/src/views/dolphinviewactionhandler.cpp index 2684816ef..9fff160b2 100644 --- a/src/views/dolphinviewactionhandler.cpp +++ b/src/views/dolphinviewactionhandler.cpp @@ -1,606 +1,600 @@ /*************************************************************************** * Copyright (C) 2008 by David Faure * * Copyright (C) 2012 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 "dolphinviewactionhandler.h" -#include - #include "settings/viewpropertiesdialog.h" -#include "views/dolphinview.h" #include "views/zoomlevelinfo.h" #include #include #include #include #include #include #include -#include -#include #include #include -#include #include "dolphindebug.h" #ifdef HAVE_BALOO #include #endif DolphinViewActionHandler::DolphinViewActionHandler(KActionCollection* collection, QObject* parent) : QObject(parent), m_actionCollection(collection), m_currentView(nullptr), m_sortByActions(), m_visibleRoles() { Q_ASSERT(m_actionCollection); createActions(); } void DolphinViewActionHandler::setCurrentView(DolphinView* view) { Q_ASSERT(view); if (m_currentView) { disconnect(m_currentView, nullptr, this, nullptr); } m_currentView = view; connect(view, &DolphinView::modeChanged, this, &DolphinViewActionHandler::updateViewActions); connect(view, &DolphinView::previewsShownChanged, this, &DolphinViewActionHandler::slotPreviewsShownChanged); connect(view, &DolphinView::sortOrderChanged, this, &DolphinViewActionHandler::slotSortOrderChanged); connect(view, &DolphinView::sortFoldersFirstChanged, this, &DolphinViewActionHandler::slotSortFoldersFirstChanged); connect(view, &DolphinView::visibleRolesChanged, this, &DolphinViewActionHandler::slotVisibleRolesChanged); connect(view, &DolphinView::groupedSortingChanged, this, &DolphinViewActionHandler::slotGroupedSortingChanged); connect(view, &DolphinView::hiddenFilesShownChanged, this, &DolphinViewActionHandler::slotHiddenFilesShownChanged); connect(view, &DolphinView::sortRoleChanged, this, &DolphinViewActionHandler::slotSortRoleChanged); connect(view, &DolphinView::zoomLevelChanged, this, &DolphinViewActionHandler::slotZoomLevelChanged); connect(view, &DolphinView::writeStateChanged, this, &DolphinViewActionHandler::slotWriteStateChanged); } DolphinView* DolphinViewActionHandler::currentView() { return m_currentView; } void DolphinViewActionHandler::createActions() { // This action doesn't appear in the GUI, it's for the shortcut only. // KNewFileMenu takes care of the GUI stuff. QAction* newDirAction = m_actionCollection->addAction(QStringLiteral("create_dir")); newDirAction->setText(i18nc("@action", "Create Folder...")); m_actionCollection->setDefaultShortcut(newDirAction, Qt::Key_F10); newDirAction->setIcon(QIcon::fromTheme(QStringLiteral("folder-new"))); newDirAction->setEnabled(false); // Will be enabled in slotWriteStateChanged(bool) if the current URL is writable connect(newDirAction, &QAction::triggered, this, &DolphinViewActionHandler::createDirectory); // File menu KStandardAction::renameFile(this, &DolphinViewActionHandler::slotRename, m_actionCollection); auto trashAction = KStandardAction::moveToTrash(this, &DolphinViewActionHandler::slotTrashActivated, m_actionCollection); auto trashShortcuts = trashAction->shortcuts(); if (!trashShortcuts.contains(QKeySequence::Delete)) { trashShortcuts.append(QKeySequence::Delete); m_actionCollection->setDefaultShortcuts(trashAction, trashShortcuts); } auto deleteAction = KStandardAction::deleteFile(this, &DolphinViewActionHandler::slotDeleteItems, m_actionCollection); auto deleteShortcuts = deleteAction->shortcuts(); if (!deleteShortcuts.contains(Qt::SHIFT | Qt::Key_Delete)) { deleteShortcuts.append(Qt::SHIFT | Qt::Key_Delete); m_actionCollection->setDefaultShortcuts(deleteAction, deleteShortcuts); } // This action is useful for being enabled when KStandardAction::MoveToTrash should be // disabled and KStandardAction::DeleteFile is enabled (e.g. non-local files), so that Key_Del // can be used for deleting the file (#76016). It needs to be a separate action // so that the Edit menu isn't affected. QAction* deleteWithTrashShortcut = m_actionCollection->addAction(QStringLiteral("delete_shortcut")); // The descriptive text is just for the shortcuts editor. deleteWithTrashShortcut->setText(i18nc("@action \"Move to Trash\" for non-local files, etc.", "Delete (using shortcut for Trash)")); m_actionCollection->setDefaultShortcut(deleteWithTrashShortcut, QKeySequence::Delete); deleteWithTrashShortcut->setEnabled(false); connect(deleteWithTrashShortcut, &QAction::triggered, this, &DolphinViewActionHandler::slotDeleteItems); QAction *propertiesAction = m_actionCollection->addAction( QStringLiteral("properties") ); // Well, it's the File menu in dolphinmainwindow and the Edit menu in dolphinpart... :) propertiesAction->setText( i18nc("@action:inmenu File", "Properties") ); propertiesAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); m_actionCollection->setDefaultShortcuts(propertiesAction, {Qt::ALT + Qt::Key_Return, Qt::ALT + Qt::Key_Enter}); connect(propertiesAction, &QAction::triggered, this, &DolphinViewActionHandler::slotProperties); // View menu KToggleAction* iconsAction = iconsModeAction(); KToggleAction* compactAction = compactModeAction(); KToggleAction* detailsAction = detailsModeAction(); KSelectAction* viewModeActions = m_actionCollection->add(QStringLiteral("view_mode")); viewModeActions->setText(i18nc("@action:intoolbar", "View Mode")); viewModeActions->addAction(iconsAction); viewModeActions->addAction(compactAction); viewModeActions->addAction(detailsAction); viewModeActions->setToolBarMode(KSelectAction::MenuMode); connect(viewModeActions, static_cast(&KSelectAction::triggered), this, &DolphinViewActionHandler::slotViewModeActionTriggered); KStandardAction::zoomIn(this, &DolphinViewActionHandler::zoomIn, m_actionCollection); KStandardAction::zoomOut(this, &DolphinViewActionHandler::zoomOut, m_actionCollection); KToggleAction* showPreview = m_actionCollection->add(QStringLiteral("show_preview")); showPreview->setText(i18nc("@action:intoolbar", "Preview")); showPreview->setToolTip(i18nc("@info", "Show preview of files and folders")); showPreview->setIcon(QIcon::fromTheme(QStringLiteral("view-preview"))); connect(showPreview, &KToggleAction::triggered, this, &DolphinViewActionHandler::togglePreview); KToggleAction* sortDescending = m_actionCollection->add(QStringLiteral("descending")); sortDescending->setText(i18nc("@action:inmenu Sort", "Descending")); connect(sortDescending, &KToggleAction::triggered, this, &DolphinViewActionHandler::toggleSortOrder); KToggleAction* sortFoldersFirst = m_actionCollection->add(QStringLiteral("folders_first")); sortFoldersFirst->setText(i18nc("@action:inmenu Sort", "Folders First")); connect(sortFoldersFirst, &KToggleAction::triggered, this, &DolphinViewActionHandler::toggleSortFoldersFirst); // View -> Sort By QActionGroup* sortByActionGroup = createFileItemRolesActionGroup(QStringLiteral("sort_by_")); KActionMenu* sortByActionMenu = m_actionCollection->add(QStringLiteral("sort")); sortByActionMenu->setText(i18nc("@action:inmenu View", "Sort By")); sortByActionMenu->setDelayed(false); foreach (QAction* action, sortByActionGroup->actions()) { sortByActionMenu->addAction(action); } sortByActionMenu->addSeparator(); sortByActionMenu->addAction(sortDescending); sortByActionMenu->addAction(sortFoldersFirst); // View -> Additional Information QActionGroup* visibleRolesGroup = createFileItemRolesActionGroup(QStringLiteral("show_")); KActionMenu* visibleRolesMenu = m_actionCollection->add(QStringLiteral("additional_info")); visibleRolesMenu->setText(i18nc("@action:inmenu View", "Additional Information")); visibleRolesMenu->setDelayed(false); foreach (QAction* action, visibleRolesGroup->actions()) { visibleRolesMenu->addAction(action); } KToggleAction* showInGroups = m_actionCollection->add(QStringLiteral("show_in_groups")); showInGroups->setIcon(QIcon::fromTheme(QStringLiteral("view-group"))); showInGroups->setText(i18nc("@action:inmenu View", "Show in Groups")); connect(showInGroups, &KToggleAction::triggered, this, &DolphinViewActionHandler::toggleGroupedSorting); KToggleAction* showHiddenFiles = m_actionCollection->add(QStringLiteral("show_hidden_files")); showHiddenFiles->setText(i18nc("@action:inmenu View", "Hidden Files")); showHiddenFiles->setToolTip(i18nc("@info", "Visibility of hidden files and folders")); m_actionCollection->setDefaultShortcuts(showHiddenFiles, {Qt::ALT + Qt::Key_Period, Qt::CTRL + Qt::Key_H, Qt::Key_F8}); connect(showHiddenFiles, &KToggleAction::triggered, this, &DolphinViewActionHandler::toggleShowHiddenFiles); QAction* adjustViewProps = m_actionCollection->addAction(QStringLiteral("view_properties")); adjustViewProps->setText(i18nc("@action:inmenu View", "Adjust View Properties...")); connect(adjustViewProps, &QAction::triggered, this, &DolphinViewActionHandler::slotAdjustViewProperties); } QActionGroup* DolphinViewActionHandler::createFileItemRolesActionGroup(const QString& groupPrefix) { const bool isSortGroup = (groupPrefix == QLatin1String("sort_by_")); Q_ASSERT(isSortGroup || (!isSortGroup && groupPrefix == QLatin1String("show_"))); QActionGroup* rolesActionGroup = new QActionGroup(m_actionCollection); rolesActionGroup->setExclusive(isSortGroup); if (isSortGroup) { connect(rolesActionGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::slotSortTriggered); } else { connect(rolesActionGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::toggleVisibleRole); } QString groupName; KActionMenu* groupMenu = nullptr; QActionGroup* groupMenuGroup = nullptr; bool indexingEnabled = false; #ifdef HAVE_BALOO Baloo::IndexerConfig config; indexingEnabled = config.fileIndexingEnabled(); #endif const QList rolesInfo = KFileItemModel::rolesInformation(); foreach (const KFileItemModel::RoleInfo& info, rolesInfo) { if (!isSortGroup && info.role == "text") { // It should not be possible to hide the "text" role continue; } KToggleAction* action = nullptr; const QString name = groupPrefix + info.role; if (info.group.isEmpty()) { action = m_actionCollection->add(name); action->setActionGroup(rolesActionGroup); } else { if (!groupMenu || info.group != groupName) { groupName = info.group; groupMenu = m_actionCollection->add(groupName); groupMenu->setText(groupName); groupMenu->setActionGroup(rolesActionGroup); groupMenuGroup = new QActionGroup(groupMenu); groupMenuGroup->setExclusive(isSortGroup); if (isSortGroup) { connect(groupMenuGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::slotSortTriggered); } else { connect(groupMenuGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::toggleVisibleRole); } } action = new KToggleAction(groupMenu); action->setActionGroup(groupMenuGroup); groupMenu->addAction(action); } action->setText(info.translation); action->setData(info.role); const bool enable = (!info.requiresBaloo && !info.requiresIndexer) || (info.requiresBaloo) || (info.requiresIndexer && indexingEnabled); action->setEnabled(enable); if (isSortGroup) { m_sortByActions.insert(info.role, action); } else { m_visibleRoles.insert(info.role, action); } } return rolesActionGroup; } void DolphinViewActionHandler::slotViewModeActionTriggered(QAction* action) { const DolphinView::Mode mode = action->data().value(); m_currentView->setMode(mode); QAction* viewModeMenu = m_actionCollection->action(QStringLiteral("view_mode")); viewModeMenu->setIcon(action->icon()); } void DolphinViewActionHandler::slotRename() { emit actionBeingHandled(); m_currentView->renameSelectedItems(); } void DolphinViewActionHandler::slotTrashActivated() { emit actionBeingHandled(); m_currentView->trashSelectedItems(); } void DolphinViewActionHandler::slotDeleteItems() { emit actionBeingHandled(); m_currentView->deleteSelectedItems(); } void DolphinViewActionHandler::togglePreview(bool show) { emit actionBeingHandled(); m_currentView->setPreviewsShown(show); } void DolphinViewActionHandler::slotPreviewsShownChanged(bool shown) { Q_UNUSED(shown); // It is not enough to update the 'Show Preview' action, also // the 'Zoom In' and 'Zoom Out' actions must be adapted. updateViewActions(); } QString DolphinViewActionHandler::currentViewModeActionName() const { switch (m_currentView->mode()) { case DolphinView::IconsView: return QStringLiteral("icons"); case DolphinView::DetailsView: return QStringLiteral("details"); case DolphinView::CompactView: return QStringLiteral("compact"); default: Q_ASSERT(false); break; } return QString(); // can't happen } KActionCollection* DolphinViewActionHandler::actionCollection() { return m_actionCollection; } void DolphinViewActionHandler::updateViewActions() { QAction* viewModeAction = m_actionCollection->action(currentViewModeActionName()); if (viewModeAction) { viewModeAction->setChecked(true); QAction* viewModeMenu = m_actionCollection->action(QStringLiteral("view_mode")); viewModeMenu->setIcon(viewModeAction->icon()); } QAction* showPreviewAction = m_actionCollection->action(QStringLiteral("show_preview")); showPreviewAction->setChecked(m_currentView->previewsShown()); slotSortOrderChanged(m_currentView->sortOrder()); slotSortFoldersFirstChanged(m_currentView->sortFoldersFirst()); slotVisibleRolesChanged(m_currentView->visibleRoles(), QList()); slotGroupedSortingChanged(m_currentView->groupedSorting()); slotSortRoleChanged(m_currentView->sortRole()); slotZoomLevelChanged(m_currentView->zoomLevel(), -1); // Updates the "show_hidden_files" action state and icon slotHiddenFilesShownChanged(m_currentView->hiddenFilesShown()); } void DolphinViewActionHandler::zoomIn() { const int level = m_currentView->zoomLevel(); m_currentView->setZoomLevel(level + 1); updateViewActions(); } void DolphinViewActionHandler::zoomOut() { const int level = m_currentView->zoomLevel(); m_currentView->setZoomLevel(level - 1); updateViewActions(); } void DolphinViewActionHandler::toggleSortOrder() { const Qt::SortOrder order = (m_currentView->sortOrder() == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder; m_currentView->setSortOrder(order); } void DolphinViewActionHandler::toggleSortFoldersFirst() { const bool sortFirst = m_currentView->sortFoldersFirst(); m_currentView->setSortFoldersFirst(!sortFirst); } void DolphinViewActionHandler::slotSortOrderChanged(Qt::SortOrder order) { QAction* descending = m_actionCollection->action(QStringLiteral("descending")); const bool sortDescending = (order == Qt::DescendingOrder); descending->setChecked(sortDescending); } void DolphinViewActionHandler::slotSortFoldersFirstChanged(bool foldersFirst) { m_actionCollection->action(QStringLiteral("folders_first"))->setChecked(foldersFirst); } void DolphinViewActionHandler::toggleVisibleRole(QAction* action) { emit actionBeingHandled(); const QByteArray toggledRole = action->data().toByteArray(); QList roles = m_currentView->visibleRoles(); const bool show = action->isChecked(); const int index = roles.indexOf(toggledRole); const bool containsInfo = (index >= 0); if (show && !containsInfo) { roles.append(toggledRole); m_currentView->setVisibleRoles(roles); } else if (!show && containsInfo) { roles.removeAt(index); m_currentView->setVisibleRoles(roles); Q_ASSERT(roles.indexOf(toggledRole) < 0); } } void DolphinViewActionHandler::slotVisibleRolesChanged(const QList& current, const QList& previous) { Q_UNUSED(previous); const QSet checkedRoles = current.toSet(); QHashIterator it(m_visibleRoles); while (it.hasNext()) { it.next(); const QByteArray& role = it.key(); KToggleAction* action = it.value(); action->setChecked(checkedRoles.contains(role)); } } void DolphinViewActionHandler::toggleGroupedSorting(bool grouped) { m_currentView->setGroupedSorting(grouped); } void DolphinViewActionHandler::slotGroupedSortingChanged(bool groupedSorting) { QAction* showInGroupsAction = m_actionCollection->action(QStringLiteral("show_in_groups")); showInGroupsAction->setChecked(groupedSorting); } void DolphinViewActionHandler::toggleShowHiddenFiles(bool show) { emit actionBeingHandled(); m_currentView->setHiddenFilesShown(show); } void DolphinViewActionHandler::slotHiddenFilesShownChanged(bool shown) { QAction* showHiddenFilesAction = m_actionCollection->action(QStringLiteral("show_hidden_files")); showHiddenFilesAction->setChecked(shown); // #374508: don't overwrite custom icons. const QString iconName = showHiddenFilesAction->icon().name(); if (!iconName.isEmpty() && iconName != QLatin1String("visibility") && iconName != QLatin1String("hint")) { return; } showHiddenFilesAction->setIcon(QIcon::fromTheme(shown ? QStringLiteral("visibility") : QStringLiteral("hint"))); } void DolphinViewActionHandler::slotWriteStateChanged(bool isFolderWritable) { m_actionCollection->action(QStringLiteral("create_dir"))->setEnabled(isFolderWritable && KProtocolManager::supportsMakeDir(currentView()->url())); } KToggleAction* DolphinViewActionHandler::iconsModeAction() { KToggleAction* iconsView = m_actionCollection->add(QStringLiteral("icons")); iconsView->setText(i18nc("@action:inmenu View Mode", "Icons")); iconsView->setToolTip(i18nc("@info", "Icons view mode")); m_actionCollection->setDefaultShortcut(iconsView, Qt::CTRL + Qt::Key_1); iconsView->setIcon(QIcon::fromTheme(QStringLiteral("view-list-icons"))); iconsView->setData(QVariant::fromValue(DolphinView::IconsView)); return iconsView; } KToggleAction* DolphinViewActionHandler::compactModeAction() { KToggleAction* iconsView = m_actionCollection->add(QStringLiteral("compact")); iconsView->setText(i18nc("@action:inmenu View Mode", "Compact")); iconsView->setToolTip(i18nc("@info", "Compact view mode")); m_actionCollection->setDefaultShortcut(iconsView, Qt::CTRL + Qt::Key_2); iconsView->setIcon(QIcon::fromTheme(QStringLiteral("view-list-details"))); // TODO: discuss with Oxygen-team the wrong (?) name iconsView->setData(QVariant::fromValue(DolphinView::CompactView)); return iconsView; } KToggleAction* DolphinViewActionHandler::detailsModeAction() { KToggleAction* detailsView = m_actionCollection->add(QStringLiteral("details")); detailsView->setText(i18nc("@action:inmenu View Mode", "Details")); detailsView->setToolTip(i18nc("@info", "Details view mode")); m_actionCollection->setDefaultShortcut(detailsView, Qt::CTRL + Qt::Key_3); detailsView->setIcon(QIcon::fromTheme(QStringLiteral("view-list-tree"))); detailsView->setData(QVariant::fromValue(DolphinView::DetailsView)); return detailsView; } void DolphinViewActionHandler::slotSortRoleChanged(const QByteArray& role) { KToggleAction* action = m_sortByActions.value(role); if (action) { action->setChecked(true); if (!action->icon().isNull()) { QAction* sortByMenu = m_actionCollection->action(QStringLiteral("sort")); sortByMenu->setIcon(action->icon()); } } } void DolphinViewActionHandler::slotZoomLevelChanged(int current, int previous) { Q_UNUSED(previous); QAction* zoomInAction = m_actionCollection->action(KStandardAction::name(KStandardAction::ZoomIn)); if (zoomInAction) { zoomInAction->setEnabled(current < ZoomLevelInfo::maximumLevel()); } QAction* zoomOutAction = m_actionCollection->action(KStandardAction::name(KStandardAction::ZoomOut)); if (zoomOutAction) { zoomOutAction->setEnabled(current > ZoomLevelInfo::minimumLevel()); } } void DolphinViewActionHandler::slotSortTriggered(QAction* action) { // The radiobuttons of the "Sort By"-menu are split between the main-menu // and several sub-menus. Because of this they don't have a common // action-group that assures an exclusive toggle-state between the main-menu // actions and the sub-menu-actions. If an action gets checked, it must // be assured that all other actions get unchecked. QAction* sortByMenu = m_actionCollection->action(QStringLiteral("sort")); foreach (QAction* groupAction, sortByMenu->menu()->actions()) { KActionMenu* actionMenu = qobject_cast(groupAction); if (actionMenu) { foreach (QAction* subAction, actionMenu->menu()->actions()) { subAction->setChecked(false); } } else if (groupAction->actionGroup()) { groupAction->setChecked(false); } } action->setChecked(true); // Apply the activated sort-role to the view const QByteArray role = action->data().toByteArray(); m_currentView->setSortRole(role); } void DolphinViewActionHandler::slotAdjustViewProperties() { emit actionBeingHandled(); QPointer dialog = new ViewPropertiesDialog(m_currentView); dialog->exec(); delete dialog; } void DolphinViewActionHandler::slotProperties() { KPropertiesDialog* dialog = nullptr; const KFileItemList list = m_currentView->selectedItems(); if (list.isEmpty()) { const QUrl url = m_currentView->url(); dialog = new KPropertiesDialog(url, m_currentView); } else { dialog = new KPropertiesDialog(list, m_currentView); } dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); dialog->raise(); dialog->activateWindow(); } diff --git a/src/views/draganddrophelper.cpp b/src/views/draganddrophelper.cpp index 4d76992ca..bd4374ec3 100644 --- a/src/views/draganddrophelper.cpp +++ b/src/views/draganddrophelper.cpp @@ -1,79 +1,77 @@ /*************************************************************************** * Copyright (C) 2007-2011 by Peter Penz * * Copyright (C) 2007 by David Faure * * * * 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 "draganddrophelper.h" -#include #include #include -#include #include #include #include QHash DragAndDropHelper::m_urlListMatchesUrlCache; bool DragAndDropHelper::urlListMatchesUrl(const QList& urls, const QUrl& destUrl) { auto iteratorResult = m_urlListMatchesUrlCache.constFind(destUrl); if (iteratorResult != m_urlListMatchesUrlCache.constEnd()) { return *iteratorResult; } const bool destUrlMatches = std::find_if(urls.constBegin(), urls.constEnd(), [destUrl](const QUrl& url) { return url.matches(destUrl, QUrl::StripTrailingSlash); }) != urls.constEnd(); return *m_urlListMatchesUrlCache.insert(destUrl, destUrlMatches); } KIO::DropJob* DragAndDropHelper::dropUrls(const QUrl& destUrl, QDropEvent* event, QWidget* window) { const QMimeData* mimeData = event->mimeData(); if (mimeData->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-service")) && mimeData->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-path"))) { const QString remoteDBusClient = mimeData->data(QStringLiteral("application/x-kde-ark-dndextract-service")); const QString remoteDBusPath = mimeData->data(QStringLiteral("application/x-kde-ark-dndextract-path")); QDBusMessage message = QDBusMessage::createMethodCall(remoteDBusClient, remoteDBusPath, QStringLiteral("org.kde.ark.DndExtract"), QStringLiteral("extractSelectedFilesTo")); message.setArguments({destUrl.toDisplayString(QUrl::PreferLocalFile)}); QDBusConnection::sessionBus().call(message); } else { if (urlListMatchesUrl(event->mimeData()->urls(), destUrl)) { return nullptr; } // Drop into a directory or a desktop-file KIO::DropJob *job = KIO::drop(event, destUrl); KJobWidgets::setWindow(job, window); return job; } return nullptr; } void DragAndDropHelper::clearUrlListMatchesUrlCache() { DragAndDropHelper::m_urlListMatchesUrlCache.clear(); } diff --git a/src/views/renamedialog.cpp b/src/views/renamedialog.cpp index 95a12474b..24b7986be 100644 --- a/src/views/renamedialog.cpp +++ b/src/views/renamedialog.cpp @@ -1,217 +1,216 @@ /*************************************************************************** * Copyright (C) 2006-2010 by Peter Penz (peter.penz@gmx.at) * * * * 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 "renamedialog.h" #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include RenameDialog::RenameDialog(QWidget *parent, const KFileItemList& items) : QDialog(parent), m_renameOneItem(false), m_newName(), m_lineEdit(nullptr), m_items(items), m_allExtensionsDifferent(true), m_spinBox(nullptr) { const QSize minSize = minimumSize(); setMinimumSize(QSize(320, minSize.height())); const int itemCount = items.count(); Q_ASSERT(itemCount >= 1); m_renameOneItem = (itemCount == 1); setWindowTitle(m_renameOneItem ? i18nc("@title:window", "Rename Item") : i18nc("@title:window", "Rename Items")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); m_okButton = buttonBox->button(QDialogButtonBox::Ok); m_okButton->setDefault(true); m_okButton->setShortcut(Qt::CTRL + Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &RenameDialog::slotAccepted); connect(buttonBox, &QDialogButtonBox::rejected, this, &RenameDialog::reject); m_okButton->setDefault(true); KGuiItem::assign(m_okButton, KGuiItem(i18nc("@action:button", "&Rename"), QStringLiteral("dialog-ok-apply"))); QWidget* page = new QWidget(this); mainLayout->addWidget(page); mainLayout->addWidget(buttonBox); QVBoxLayout* topLayout = new QVBoxLayout(page); QLabel* editLabel = nullptr; if (m_renameOneItem) { m_newName = items.first().name(); editLabel = new QLabel(xi18nc("@label:textbox", "Rename the item %1 to:", m_newName), page); editLabel->setTextFormat(Qt::PlainText); } else { m_newName = i18nc("@info:status", "New name #"); editLabel = new QLabel(i18ncp("@label:textbox", "Rename the %1 selected item to:", "Rename the %1 selected items to:", itemCount), page); } m_lineEdit = new QLineEdit(page); mainLayout->addWidget(m_lineEdit); connect(m_lineEdit, &QLineEdit::textChanged, this, &RenameDialog::slotTextChanged); int selectionLength = m_newName.length(); if (m_renameOneItem) { const QString fileName = items.first().url().toDisplayString(); QMimeDatabase db; const QString extension = db.suffixForFileName(fileName.toLower()); // If the current item is a directory, select the whole file name. if ((extension.length() > 0) && !items.first().isDir()) { // Don't select the extension selectionLength -= extension.length() + 1; } } else { // Don't select the # character --selectionLength; } m_lineEdit->setText(m_newName); m_lineEdit->setSelection(0, selectionLength); topLayout->addWidget(editLabel); topLayout->addWidget(m_lineEdit); if (!m_renameOneItem) { QSet extensions; foreach (const KFileItem& item, m_items) { QMimeDatabase db; const QString extension = db.suffixForFileName(item.url().toDisplayString().toLower()); if (extensions.contains(extension)) { m_allExtensionsDifferent = false; break; } extensions.insert(extension); } QLabel* infoLabel = new QLabel(i18nc("@info", "# will be replaced by ascending numbers starting with:"), page); mainLayout->addWidget(infoLabel); m_spinBox = new QSpinBox(page); m_spinBox->setMaximum(10000); m_spinBox->setMinimum(0); m_spinBox->setSingleStep(1); m_spinBox->setValue(1); m_spinBox->setDisplayIntegerBase(10); QHBoxLayout* horizontalLayout = new QHBoxLayout(page); horizontalLayout->setMargin(0); horizontalLayout->addWidget(infoLabel); horizontalLayout->addWidget(m_spinBox); topLayout->addLayout(horizontalLayout); } } RenameDialog::~RenameDialog() { } void RenameDialog::slotAccepted() { QWidget* widget = parentWidget(); if (!widget) { widget = this; } KIO::FileUndoManager::CommandType cmdType; if (m_renameOneItem) { Q_ASSERT(m_items.count() == 1); cmdType = KIO::FileUndoManager::Rename; } else { cmdType = KIO::FileUndoManager::BatchRename; } const QList srcList = m_items.urlList(); KIO::BatchRenameJob* job = KIO::batchRename(srcList, m_lineEdit->text(), m_spinBox->value(), QLatin1Char('#')); KJobWidgets::setWindow(job, widget); const QUrl parentUrl = srcList.first().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); KIO::FileUndoManager::self()->recordJob(cmdType, srcList, parentUrl, job); connect(job, &KIO::BatchRenameJob::fileRenamed, this, &RenameDialog::slotFileRenamed); connect(job, &KIO::BatchRenameJob::result, this, &RenameDialog::slotResult); job->uiDelegate()->setAutoErrorHandlingEnabled(true); accept(); } void RenameDialog::slotTextChanged(const QString& newName) { bool enable = !newName.isEmpty() && (newName != QLatin1String("..")) && (newName != QLatin1String(".")); if (enable && !m_renameOneItem) { const int count = newName.count(QLatin1Char('#')); if (count == 0) { // Renaming multiple files without '#' will only work if all extensions are different. enable = m_allExtensionsDifferent; } else { // Assure that the new name contains exactly one # (or a connected sequence of #'s) const int first = newName.indexOf(QLatin1Char('#')); const int last = newName.lastIndexOf(QLatin1Char('#')); enable = (last - first + 1 == count); } } m_okButton->setEnabled(enable); } void RenameDialog::slotFileRenamed(const QUrl &oldUrl, const QUrl &newUrl) { Q_UNUSED(oldUrl) m_renamedItems << newUrl; } void RenameDialog::slotResult(KJob *job) { if (!job->error()) { emit renamingFinished(m_renamedItems); } } void RenameDialog::showEvent(QShowEvent* event) { m_lineEdit->setFocus(); QDialog::showEvent(event); } diff --git a/src/views/tooltips/dolphinfilemetadatawidget.cpp b/src/views/tooltips/dolphinfilemetadatawidget.cpp index cabfc0617..fcf51a240 100644 --- a/src/views/tooltips/dolphinfilemetadatawidget.cpp +++ b/src/views/tooltips/dolphinfilemetadatawidget.cpp @@ -1,162 +1,161 @@ /*************************************************************************** * Copyright (C) 2010 by Peter Penz * * Copyright (C) 2008 by Fredrik Höglund * * Copyright (C) 2012 by Mark Gaiser * * * * 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 "dolphinfilemetadatawidget.h" #include #include #include #include #include #include #include #include #include -#include #ifndef HAVE_BALOO #include #else #include #endif DolphinFileMetaDataWidget::DolphinFileMetaDataWidget(QWidget* parent) : QWidget(parent), m_preview(nullptr), m_name(nullptr), m_fileMetaDataWidget(nullptr) { // Create widget for file preview m_preview = new QLabel(this); m_preview->setAlignment(Qt::AlignTop); // Create widget for file name m_name = new QLabel(this); m_name->setForegroundRole(QPalette::ToolTipText); m_name->setTextFormat(Qt::PlainText); m_name->setAlignment(Qt::AlignHCenter); QFont font = m_name->font(); font.setBold(true); m_name->setFont(font); QFontMetrics fontMetrics(font); m_name->setMaximumWidth(fontMetrics.averageCharWidth() * 40); // Create widget for the meta data #ifndef HAVE_BALOO m_fileMetaDataWidget = new KFileMetaDataWidget(this); connect(m_fileMetaDataWidget, &KFileMetaDataWidget::metaDataRequestFinished, this, &DolphinFileMetaDataWidget::metaDataRequestFinished); connect(m_fileMetaDataWidget, &KFileMetaDataWidget::urlActivated, this, &DolphinFileMetaDataWidget::urlActivated); #else m_fileMetaDataWidget = new Baloo::FileMetaDataWidget(this); connect(m_fileMetaDataWidget, &Baloo::FileMetaDataWidget::metaDataRequestFinished, this, &DolphinFileMetaDataWidget::metaDataRequestFinished); connect(m_fileMetaDataWidget, &Baloo::FileMetaDataWidget::urlActivated, this, &DolphinFileMetaDataWidget::urlActivated); #endif m_fileMetaDataWidget->setForegroundRole(QPalette::ToolTipText); m_fileMetaDataWidget->setReadOnly(true); QVBoxLayout* textLayout = new QVBoxLayout(); textLayout->addWidget(m_name); textLayout->addWidget(new KSeparator()); textLayout->addWidget(m_fileMetaDataWidget); textLayout->setAlignment(m_name, Qt::AlignCenter); textLayout->setAlignment(m_fileMetaDataWidget, Qt::AlignLeft); // Assure that the text-layout gets top-aligned by adding a stretch. // Don't use textLayout->setAlignment(Qt::AlignTop) instead, as this does // not work with the heightForWidth()-size-hint of m_fileMetaDataWidget // (see bug #241608) textLayout->addStretch(); QHBoxLayout* layout = new QHBoxLayout(this); layout->addWidget(m_preview); layout->addSpacing(layout->margin()); layout->addLayout(textLayout); } DolphinFileMetaDataWidget::~DolphinFileMetaDataWidget() { } void DolphinFileMetaDataWidget::setPreview(const QPixmap& pixmap) { m_preview->setPixmap(pixmap); } QPixmap DolphinFileMetaDataWidget::preview() const { if (m_preview->pixmap()) { return *m_preview->pixmap(); } return QPixmap(); } void DolphinFileMetaDataWidget::setName(const QString& name) { QTextOption textOption; textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); const QString processedName = Qt::mightBeRichText(name) ? name : KStringHandler::preProcessWrap(name); QTextLayout textLayout(processedName); textLayout.setFont(m_name->font()); textLayout.setTextOption(textOption); QString wrappedText; wrappedText.reserve(processedName.length()); // wrap the text to fit into the maximum width of m_name textLayout.beginLayout(); QTextLine line = textLayout.createLine(); while (line.isValid()) { line.setLineWidth(m_name->maximumWidth()); wrappedText += processedName.midRef(line.textStart(), line.textLength()); line = textLayout.createLine(); if (line.isValid()) { wrappedText += QChar::LineSeparator; } } textLayout.endLayout(); m_name->setText(wrappedText); } QString DolphinFileMetaDataWidget::name() const { return m_name->text(); } void DolphinFileMetaDataWidget::setItems(const KFileItemList& items) { m_fileMetaDataWidget->setItems(items); } KFileItemList DolphinFileMetaDataWidget::items() const { return m_fileMetaDataWidget->items(); } diff --git a/src/views/versioncontrol/updateitemstatesthread.cpp b/src/views/versioncontrol/updateitemstatesthread.cpp index 574402dcd..0a183b4a5 100644 --- a/src/views/versioncontrol/updateitemstatesthread.cpp +++ b/src/views/versioncontrol/updateitemstatesthread.cpp @@ -1,69 +1,67 @@ /*************************************************************************** * Copyright (C) 2009 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 "updateitemstatesthread.h" -#include -#include UpdateItemStatesThread::UpdateItemStatesThread(KVersionControlPlugin* plugin, const QMap >& itemStates) : QThread(), m_globalPluginMutex(nullptr), m_plugin(plugin), m_itemStates(itemStates) { // Several threads may share one instance of a plugin. A global // mutex is required to serialize the retrieval of version control // states inside run(). static QMutex globalMutex; m_globalPluginMutex = &globalMutex; } UpdateItemStatesThread::~UpdateItemStatesThread() { } void UpdateItemStatesThread::run() { Q_ASSERT(!m_itemStates.isEmpty()); Q_ASSERT(m_plugin); QMutexLocker pluginLocker(m_globalPluginMutex); QMap >::iterator it = m_itemStates.begin(); for (; it != m_itemStates.end(); ++it) { if (m_plugin->beginRetrieval(it.key())) { QVector& items = it.value(); const int count = items.count(); for (int i = 0; i < count; ++i) { const KFileItem& item = items.at(i).first; const KVersionControlPlugin::ItemVersion version = m_plugin->itemVersion(item); items[i].second = version; } } m_plugin->endRetrieval(); } } QMap > UpdateItemStatesThread::itemStates() const { return m_itemStates; } diff --git a/src/views/versioncontrol/versioncontrolobserver.cpp b/src/views/versioncontrol/versioncontrolobserver.cpp index 44dffac45..459bef0e9 100644 --- a/src/views/versioncontrol/versioncontrolobserver.cpp +++ b/src/views/versioncontrol/versioncontrolobserver.cpp @@ -1,340 +1,339 @@ /*************************************************************************** * Copyright (C) 2009 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 "versioncontrolobserver.h" #include "dolphin_versioncontrolsettings.h" #include #include #include "dolphindebug.h" #include #include #include "updateitemstatesthread.h" -#include #include VersionControlObserver::VersionControlObserver(QObject* parent) : QObject(parent), m_pendingItemStatesUpdate(false), m_versionedDirectory(false), m_silentUpdate(false), m_model(nullptr), m_dirVerificationTimer(nullptr), m_plugin(nullptr), m_updateItemStatesThread(nullptr) { // The verification timer specifies the timeout until the shown directory // is checked whether it is versioned. Per default it is assumed that users // don't iterate through versioned directories and a high timeout is used // The timeout will be decreased as soon as a versioned directory has been // found (see verifyDirectory()). m_dirVerificationTimer = new QTimer(this); m_dirVerificationTimer->setSingleShot(true); m_dirVerificationTimer->setInterval(500); connect(m_dirVerificationTimer, &QTimer::timeout, this, &VersionControlObserver::verifyDirectory); } VersionControlObserver::~VersionControlObserver() { if (m_plugin) { m_plugin->disconnect(this); m_plugin = nullptr; } } void VersionControlObserver::setModel(KFileItemModel* model) { if (m_model) { disconnect(m_model, &KFileItemModel::itemsInserted, this, &VersionControlObserver::delayedDirectoryVerification); disconnect(m_model, &KFileItemModel::itemsChanged, this, &VersionControlObserver::delayedDirectoryVerification); } m_model = model; if (model) { connect(m_model, &KFileItemModel::itemsInserted, this, &VersionControlObserver::delayedDirectoryVerification); connect(m_model, &KFileItemModel::itemsChanged, this, &VersionControlObserver::delayedDirectoryVerification); } } KFileItemModel* VersionControlObserver::model() const { return m_model; } QList VersionControlObserver::actions(const KFileItemList& items) const { bool hasNullItems = false; foreach (const KFileItem& item, items) { if (item.isNull()) { qCWarning(DolphinDebug) << "Requesting version-control-actions for empty items"; hasNullItems = true; break; } } if (!m_model || hasNullItems || !isVersioned()) { return {}; } return m_plugin->actions(items); } void VersionControlObserver::delayedDirectoryVerification() { m_silentUpdate = false; m_dirVerificationTimer->start(); } void VersionControlObserver::silentDirectoryVerification() { m_silentUpdate = true; m_dirVerificationTimer->start(); } void VersionControlObserver::verifyDirectory() { if (!m_model) { return; } const KFileItem rootItem = m_model->rootItem(); if (rootItem.isNull() || !rootItem.url().isLocalFile()) { return; } if (m_plugin) { m_plugin->disconnect(this); } m_plugin = searchPlugin(rootItem.url()); if (m_plugin) { connect(m_plugin, &KVersionControlPlugin::itemVersionsChanged, this, &VersionControlObserver::silentDirectoryVerification); connect(m_plugin, &KVersionControlPlugin::infoMessage, this, &VersionControlObserver::infoMessage); connect(m_plugin, &KVersionControlPlugin::errorMessage, this, &VersionControlObserver::errorMessage); connect(m_plugin, &KVersionControlPlugin::operationCompletedMessage, this, &VersionControlObserver::operationCompletedMessage); if (!m_versionedDirectory) { m_versionedDirectory = true; // The directory is versioned. Assume that the user will further browse through // versioned directories and decrease the verification timer. m_dirVerificationTimer->setInterval(100); } updateItemStates(); } else if (m_versionedDirectory) { m_versionedDirectory = false; // The directory is not versioned. Reset the verification timer to a higher // value, so that browsing through non-versioned directories is not slown down // by an immediate verification. m_dirVerificationTimer->setInterval(500); } } void VersionControlObserver::slotThreadFinished() { UpdateItemStatesThread* thread = m_updateItemStatesThread; m_updateItemStatesThread = nullptr; // The thread deletes itself automatically (see updateItemStates()) if (!m_plugin || !thread) { return; } const QMap >& itemStates = thread->itemStates(); QMap >::const_iterator it = itemStates.constBegin(); for (; it != itemStates.constEnd(); ++it) { const QVector& items = it.value(); foreach (const ItemState& item, items) { const KFileItem& fileItem = item.first; const KVersionControlPlugin::ItemVersion version = item.second; QHash values; values.insert("version", QVariant(version)); m_model->setData(m_model->index(fileItem), values); } } if (!m_silentUpdate) { // Using an empty message results in clearing the previously shown information message and showing // the default status bar information. This is useful as the user already gets feedback that the // operation has been completed because of the icon emblems. emit operationCompletedMessage(QString()); } if (m_pendingItemStatesUpdate) { m_pendingItemStatesUpdate = false; updateItemStates(); } } void VersionControlObserver::updateItemStates() { Q_ASSERT(m_plugin); if (m_updateItemStatesThread) { // An update is currently ongoing. Wait until the thread has finished // the update (see slotThreadFinished()). m_pendingItemStatesUpdate = true; return; } QMap > itemStates; createItemStatesList(itemStates); if (!itemStates.isEmpty()) { if (!m_silentUpdate) { emit infoMessage(i18nc("@info:status", "Updating version information...")); } m_updateItemStatesThread = new UpdateItemStatesThread(m_plugin, itemStates); connect(m_updateItemStatesThread, &UpdateItemStatesThread::finished, this, &VersionControlObserver::slotThreadFinished); connect(m_updateItemStatesThread, &UpdateItemStatesThread::finished, m_updateItemStatesThread, &UpdateItemStatesThread::deleteLater); m_updateItemStatesThread->start(); // slotThreadFinished() is called when finished } } int VersionControlObserver::createItemStatesList(QMap >& itemStates, const int firstIndex) { const int itemCount = m_model->count(); const int currentExpansionLevel = m_model->expandedParentsCount(firstIndex); QVector items; items.reserve(itemCount - firstIndex); int index; for (index = firstIndex; index < itemCount; ++index) { const int expansionLevel = m_model->expandedParentsCount(index); if (expansionLevel == currentExpansionLevel) { ItemState itemState; itemState.first = m_model->fileItem(index); itemState.second = KVersionControlPlugin::UnversionedVersion; items.append(itemState); } else if (expansionLevel > currentExpansionLevel) { // Sub folder index += createItemStatesList(itemStates, index) - 1; } else { break; } } if (items.count() > 0) { const QUrl& url = items.first().first.url(); itemStates.insert(url.adjusted(QUrl::RemoveFilename).path(), items); } return index - firstIndex; // number of processed items } KVersionControlPlugin* VersionControlObserver::searchPlugin(const QUrl& directory) const { static bool pluginsAvailable = true; static QList plugins; if (!pluginsAvailable) { // A searching for plugins has already been done, but no // plugins are installed return nullptr; } if (plugins.isEmpty()) { // No searching for plugins has been done yet. Query the KServiceTypeTrader for // all fileview version control plugins and remember them in 'plugins'. const QStringList enabledPlugins = VersionControlSettings::enabledPlugins(); const KService::List pluginServices = KServiceTypeTrader::self()->query(QStringLiteral("FileViewVersionControlPlugin")); for (KService::List::ConstIterator it = pluginServices.constBegin(); it != pluginServices.constEnd(); ++it) { if (enabledPlugins.contains((*it)->name())) { KVersionControlPlugin* plugin = (*it)->createInstance(); if (plugin) { plugins.append(plugin); } } } if (plugins.isEmpty()) { pluginsAvailable = false; return nullptr; } } // We use the number of upUrl() calls to find the best matching plugin // for the given directory. The smaller value, the better it is (0 is best). KVersionControlPlugin* bestPlugin = nullptr; int bestScore = INT_MAX; // Verify whether the current directory contains revision information // like .svn, .git, ... foreach (KVersionControlPlugin* plugin, plugins) { const QString fileName = directory.path() + '/' + plugin->fileName(); if (QFile::exists(fileName)) { // The score of this plugin is 0 (best), so we can just return this plugin, // instead of going through the plugin scoring procedure, we can't find a better one ;) return plugin; } // Version control systems like Git provide the version information // file only in the root directory. Check whether the version information file can // be found in one of the parent directories. For performance reasons this // step is only done, if the previous directory was marked as versioned by // m_versionedDirectory. Drawback: Until e. g. Git is recognized, the root directory // must be shown at least once. if (m_versionedDirectory) { QUrl dirUrl(directory); QUrl upUrl = KIO::upUrl(dirUrl); int upUrlCounter = 1; while ((upUrlCounter < bestScore) && (upUrl != dirUrl)) { const QString fileName = dirUrl.path() + '/' + plugin->fileName(); if (QFile::exists(fileName)) { if (upUrlCounter < bestScore) { bestPlugin = plugin; bestScore = upUrlCounter; } break; } dirUrl = upUrl; upUrl = KIO::upUrl(dirUrl); ++upUrlCounter; } } } return bestPlugin; } bool VersionControlObserver::isVersioned() const { return m_versionedDirectory && m_plugin; } diff --git a/src/views/viewproperties.cpp b/src/views/viewproperties.cpp index e2be18819..4444f64e7 100644 --- a/src/views/viewproperties.cpp +++ b/src/views/viewproperties.cpp @@ -1,497 +1,492 @@ /*************************************************************************** * Copyright (C) 2006-2010 by Peter Penz * * Copyright (C) 2006 by Aaron J. Seigo * * * * 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 "viewproperties.h" #include "dolphin_directoryviewpropertysettings.h" #include "dolphin_generalsettings.h" -#include #include "dolphindebug.h" #include -#include -#include -#include -#include namespace { const int AdditionalInfoViewPropertiesVersion = 1; const int NameRolePropertiesVersion = 2; const int DateRolePropertiesVersion = 4; const int CurrentViewPropertiesVersion = 4; // String representation to mark the additional properties of // the details view as customized by the user. See // ViewProperties::visibleRoles() for more information. const char CustomizedDetailsString[] = "CustomizedDetails"; // Filename that is used for storing the properties const char ViewPropertiesFileName[] = ".directory"; } ViewProperties::ViewProperties(const QUrl& url) : m_changedProps(false), m_autoSave(true), m_node(nullptr) { GeneralSettings* settings = GeneralSettings::self(); const bool useGlobalViewProps = settings->globalViewProps() || url.isEmpty(); bool useDetailsViewWithPath = false; // We try and save it to the file .directory in the directory being viewed. // If the directory is not writable by the user or the directory is not local, // we store the properties information in a local file. if (useGlobalViewProps) { m_filePath = destinationDir(QStringLiteral("global")); } else if (url.scheme().contains(QStringLiteral("search"))) { m_filePath = destinationDir(QStringLiteral("search/")) + directoryHashForUrl(url); useDetailsViewWithPath = true; } else if (url.scheme() == QLatin1String("trash")) { m_filePath = destinationDir(QStringLiteral("trash")); useDetailsViewWithPath = true; } else if (url.isLocalFile()) { m_filePath = url.toLocalFile(); const QFileInfo dirInfo(m_filePath); const QFileInfo fileInfo(m_filePath + QDir::separator() + ViewPropertiesFileName); // Check if the directory is writable and check if the ".directory" file exists and // is read- and writable. if (!dirInfo.isWritable() || (fileInfo.exists() && !(fileInfo.isReadable() && fileInfo.isWritable())) || !isPartOfHome(m_filePath)) { #ifdef Q_OS_WIN // m_filePath probably begins with C:/ - the colon is not a valid character for paths though m_filePath = QDir::separator() + m_filePath.remove(QLatin1Char(':')); #endif m_filePath = destinationDir(QStringLiteral("local")) + m_filePath; } } else { m_filePath = destinationDir(QStringLiteral("remote")) + m_filePath; } const QString file = m_filePath + QDir::separator() + ViewPropertiesFileName; m_node = new ViewPropertySettings(KSharedConfig::openConfig(file)); // If the .directory file does not exist or the timestamp is too old, // use default values instead. const bool useDefaultProps = (!useGlobalViewProps || useDetailsViewWithPath) && (!QFile::exists(file) || (m_node->timestamp() < settings->viewPropsTimestamp())); if (useDefaultProps) { if (useDetailsViewWithPath) { setViewMode(DolphinView::DetailsView); setVisibleRoles({"path"}); } else { // The global view-properties act as default for directories without // any view-property configuration. Constructing a ViewProperties // instance for an empty QUrl ensures that the global view-properties // are loaded. QUrl emptyUrl; ViewProperties defaultProps(emptyUrl); setDirProperties(defaultProps); m_changedProps = false; } } if (m_node->version() < CurrentViewPropertiesVersion) { // The view-properties have an outdated version. Convert the properties // to the changes of the current version. if (m_node->version() < AdditionalInfoViewPropertiesVersion) { convertAdditionalInfo(); Q_ASSERT(m_node->version() == AdditionalInfoViewPropertiesVersion); } if (m_node->version() < NameRolePropertiesVersion) { convertNameRoleToTextRole(); Q_ASSERT(m_node->version() == NameRolePropertiesVersion); } if (m_node->version() < DateRolePropertiesVersion) { convertDateRoleToModificationTimeRole(); Q_ASSERT(m_node->version() == DateRolePropertiesVersion); } m_node->setVersion(CurrentViewPropertiesVersion); } } ViewProperties::~ViewProperties() { if (m_changedProps && m_autoSave) { save(); } delete m_node; m_node = nullptr; } void ViewProperties::setViewMode(DolphinView::Mode mode) { if (m_node->viewMode() != mode) { m_node->setViewMode(mode); update(); } } DolphinView::Mode ViewProperties::viewMode() const { const int mode = qBound(0, m_node->viewMode(), 2); return static_cast(mode); } void ViewProperties::setPreviewsShown(bool show) { if (m_node->previewsShown() != show) { m_node->setPreviewsShown(show); update(); } } bool ViewProperties::previewsShown() const { return m_node->previewsShown(); } void ViewProperties::setHiddenFilesShown(bool show) { if (m_node->hiddenFilesShown() != show) { m_node->setHiddenFilesShown(show); update(); } } void ViewProperties::setGroupedSorting(bool grouped) { if (m_node->groupedSorting() != grouped) { m_node->setGroupedSorting(grouped); update(); } } bool ViewProperties::groupedSorting() const { return m_node->groupedSorting(); } bool ViewProperties::hiddenFilesShown() const { return m_node->hiddenFilesShown(); } void ViewProperties::setSortRole(const QByteArray& role) { if (m_node->sortRole() != role) { m_node->setSortRole(role); update(); } } QByteArray ViewProperties::sortRole() const { return m_node->sortRole().toLatin1(); } void ViewProperties::setSortOrder(Qt::SortOrder sortOrder) { if (m_node->sortOrder() != sortOrder) { m_node->setSortOrder(sortOrder); update(); } } Qt::SortOrder ViewProperties::sortOrder() const { return static_cast(m_node->sortOrder()); } void ViewProperties::setSortFoldersFirst(bool foldersFirst) { if (m_node->sortFoldersFirst() != foldersFirst) { m_node->setSortFoldersFirst(foldersFirst); update(); } } bool ViewProperties::sortFoldersFirst() const { return m_node->sortFoldersFirst(); } void ViewProperties::setVisibleRoles(const QList& roles) { if (roles == visibleRoles()) { return; } // See ViewProperties::visibleRoles() for the storage format // of the additional information. // Remove the old values stored for the current view-mode const QStringList oldVisibleRoles = m_node->visibleRoles(); const QString prefix = viewModePrefix(); QStringList newVisibleRoles = oldVisibleRoles; for (int i = newVisibleRoles.count() - 1; i >= 0; --i) { if (newVisibleRoles[i].startsWith(prefix)) { newVisibleRoles.removeAt(i); } } // Add the updated values for the current view-mode newVisibleRoles.reserve(roles.count()); foreach (const QByteArray& role, roles) { newVisibleRoles.append(prefix + role); } if (oldVisibleRoles != newVisibleRoles) { const bool markCustomizedDetails = (m_node->viewMode() == DolphinView::DetailsView) && !newVisibleRoles.contains(CustomizedDetailsString); if (markCustomizedDetails) { // The additional information of the details-view has been modified. Set a marker, // so that it is allowed to also show no additional information without doing the // fallback to show the size and date per default. newVisibleRoles.append(CustomizedDetailsString); } m_node->setVisibleRoles(newVisibleRoles); update(); } } QList ViewProperties::visibleRoles() const { // The shown additional information is stored for each view-mode separately as // string with the view-mode as prefix. Example: // // AdditionalInfo=Details_size,Details_date,Details_owner,Icons_size // // To get the representation as QList, the current // view-mode must be checked and the values of this mode added to the list. // // For the details-view a special case must be respected: Per default the size // and date should be shown without creating a .directory file. Only if // the user explictly has modified the properties of the details view (marked // by "CustomizedDetails"), also a details-view with no additional information // is accepted. QList roles{"text"}; // Iterate through all stored keys and append all roles that match to // the current view mode. const QString prefix = viewModePrefix(); const int prefixLength = prefix.length(); const QStringList visibleRoles = m_node->visibleRoles(); foreach (const QString& visibleRole, visibleRoles) { if (visibleRole.startsWith(prefix)) { const QByteArray role = visibleRole.right(visibleRole.length() - prefixLength).toLatin1(); if (role != "text") { roles.append(role); } } } // For the details view the size and date should be shown per default // until the additional information has been explicitly changed by the user const bool useDefaultValues = roles.count() == 1 // "text" && (m_node->viewMode() == DolphinView::DetailsView) && !visibleRoles.contains(CustomizedDetailsString); if (useDefaultValues) { roles.append("size"); roles.append("modificationtime"); } return roles; } void ViewProperties::setHeaderColumnWidths(const QList& widths) { if (m_node->headerColumnWidths() != widths) { m_node->setHeaderColumnWidths(widths); update(); } } QList ViewProperties::headerColumnWidths() const { return m_node->headerColumnWidths(); } void ViewProperties::setDirProperties(const ViewProperties& props) { setViewMode(props.viewMode()); setPreviewsShown(props.previewsShown()); setHiddenFilesShown(props.hiddenFilesShown()); setGroupedSorting(props.groupedSorting()); setSortRole(props.sortRole()); setSortOrder(props.sortOrder()); setSortFoldersFirst(props.sortFoldersFirst()); setVisibleRoles(props.visibleRoles()); setHeaderColumnWidths(props.headerColumnWidths()); m_node->setVersion(props.m_node->version()); } void ViewProperties::setAutoSaveEnabled(bool autoSave) { m_autoSave = autoSave; } bool ViewProperties::isAutoSaveEnabled() const { return m_autoSave; } void ViewProperties::update() { m_changedProps = true; m_node->setTimestamp(QDateTime::currentDateTime()); } void ViewProperties::save() { qCDebug(DolphinDebug) << "Saving view-properties to" << m_filePath; QDir dir; dir.mkpath(m_filePath); m_node->setVersion(CurrentViewPropertiesVersion); m_node->save(); m_changedProps = false; } bool ViewProperties::exist() const { const QString file = m_filePath + QDir::separator() + ViewPropertiesFileName; return QFile::exists(file); } QString ViewProperties::destinationDir(const QString& subDir) const { QString path = QStandardPaths::writableLocation(QStandardPaths::DataLocation); path.append("/view_properties/").append(subDir); return path; } QString ViewProperties::viewModePrefix() const { QString prefix; switch (m_node->viewMode()) { case DolphinView::IconsView: prefix = QStringLiteral("Icons_"); break; case DolphinView::CompactView: prefix = QStringLiteral("Compact_"); break; case DolphinView::DetailsView: prefix = QStringLiteral("Details_"); break; default: qCWarning(DolphinDebug) << "Unknown view-mode of the view properties"; } return prefix; } void ViewProperties::convertAdditionalInfo() { QStringList visibleRoles; const QStringList additionalInfo = m_node->additionalInfo(); if (!additionalInfo.isEmpty()) { // Convert the obsolete values like Icons_Size, Details_Date, ... // to Icons_size, Details_date, ... where the suffix just represents // the internal role. One special-case must be handled: "LinkDestination" // has been used for "destination". visibleRoles.reserve(additionalInfo.count()); foreach (const QString& info, additionalInfo) { QString visibleRole = info; int index = visibleRole.indexOf('_'); if (index >= 0 && index + 1 < visibleRole.length()) { ++index; if (visibleRole[index] == QLatin1Char('L')) { visibleRole.replace(QLatin1String("LinkDestination"), QLatin1String("destination")); } else { visibleRole[index] = visibleRole[index].toLower(); } } visibleRoles.append(visibleRole); } } m_node->setAdditionalInfo(QStringList()); m_node->setVisibleRoles(visibleRoles); m_node->setVersion(AdditionalInfoViewPropertiesVersion); update(); } void ViewProperties::convertNameRoleToTextRole() { QStringList visibleRoles = m_node->visibleRoles(); for (int i = 0; i < visibleRoles.count(); ++i) { if (visibleRoles[i].endsWith(QLatin1String("_name"))) { const int leftLength = visibleRoles[i].length() - 5; visibleRoles[i] = visibleRoles[i].left(leftLength) + "_text"; } } QString sortRole = m_node->sortRole(); if (sortRole == QLatin1String("name")) { sortRole = QStringLiteral("text"); } m_node->setVisibleRoles(visibleRoles); m_node->setSortRole(sortRole); m_node->setVersion(NameRolePropertiesVersion); update(); } void ViewProperties::convertDateRoleToModificationTimeRole() { QStringList visibleRoles = m_node->visibleRoles(); for (int i = 0; i < visibleRoles.count(); ++i) { if (visibleRoles[i].endsWith(QLatin1String("_date"))) { const int leftLength = visibleRoles[i].length() - 5; visibleRoles[i] = visibleRoles[i].left(leftLength) + "_modificationtime"; } } QString sortRole = m_node->sortRole(); if (sortRole == QLatin1String("date")) { sortRole = QStringLiteral("modificationtime"); } m_node->setVisibleRoles(visibleRoles); m_node->setSortRole(sortRole); m_node->setVersion(DateRolePropertiesVersion); update(); } bool ViewProperties::isPartOfHome(const QString& filePath) { // For performance reasons cache the path in a static QString // (see QDir::homePath() for more details) static QString homePath; if (homePath.isEmpty()) { homePath = QDir::homePath(); Q_ASSERT(!homePath.isEmpty()); } return filePath.startsWith(homePath); } QString ViewProperties::directoryHashForUrl(const QUrl& url) { const QByteArray hashValue = QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Sha1); QString hashString = hashValue.toBase64(); hashString.replace('/', '-'); return hashString; }