diff --git a/src/filewidgets/CMakeLists.txt b/src/filewidgets/CMakeLists.txt index 982b0b1b..d9410b59 100644 --- a/src/filewidgets/CMakeLists.txt +++ b/src/filewidgets/CMakeLists.txt @@ -1,106 +1,107 @@ project(KIOFileWidgets) find_package(KF5Bookmarks ${KF5_DEP_VERSION} REQUIRED) find_package(KF5XmlGui ${KF5_DEP_VERSION} REQUIRED) configure_file(config-kiofilewidgets.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kiofilewidgets.h) set(kiofilewidgets_SRCS kstatusbarofflineindicator.cpp kfilemetapreview.cpp kimagefilepreview.cpp kpreviewwidgetbase.cpp krecentdirs.cpp defaultviewadapter.cpp kdiroperator.cpp kdiroperatordetailview.cpp kdirsortfilterproxymodel.cpp #used in combination with kdirmodel.cpp kencodingfiledialog.cpp kfilebookmarkhandler.cpp kfilecopytomenu.cpp kfilecustomdialog.cpp kfilefiltercombo.cpp kfilewidget.cpp kfilewidgetdocktitlebar.cpp kfileplacesitem.cpp kfileplacesmodel.cpp kfileplacesview.cpp kfileplaceeditdialog.cpp kfilepreviewgenerator.cpp knameandurlinputdialog.cpp knewfilemenu.cpp kurlnavigatordropdownbutton.cpp kurlnavigatorbuttonbase.cpp kurlnavigatorbutton.cpp kurlnavigatorplacesselector.cpp kurlnavigatorprotocolcombo.cpp kurlnavigatortogglebutton.cpp kurlnavigator.cpp kurlnavigatormenu.cpp kurlnavigatorpathselectoreventfilter.cpp ) qt5_add_resources(kiofilewidgets_SRCS ../new_file_templates/templates.qrc) add_library(KF5KIOFileWidgets ${kiofilewidgets_SRCS}) generate_export_header(KF5KIOFileWidgets BASE_NAME KIOFileWidgets) add_library(KF5::KIOFileWidgets ALIAS KF5KIOFileWidgets) target_include_directories(KF5KIOFileWidgets INTERFACE "$") target_link_libraries(KF5KIOFileWidgets PUBLIC KF5::KIOWidgets KF5::Bookmarks # in KFilePlacesModel's API KF5::ItemViews # kdirsortfilterproxymodel KF5::XmlGui # for KActionCollection, used by KFileWidget/KDirOperator KF5::Solid # KFilePlacesModel/KFilePlacesView PRIVATE KF5::IconThemes # KIconLoader KF5::I18n ) set_target_properties(KF5KIOFileWidgets PROPERTIES VERSION ${KIO_VERSION_STRING} SOVERSION ${KIO_SOVERSION} EXPORT_NAME KIOFileWidgets ) ecm_generate_headers(KIOFileWidgets_HEADERS HEADER_NAMES KAbstractViewAdapter KImageFilePreview KPreviewWidgetBase KRecentDirs KStatusBarOfflineIndicator KDirOperator KDirSortFilterProxyModel KFileCopyToMenu KFileFilterCombo + KFilePlaceEditDialog KFilePlacesModel KFilePlacesView KFilePreviewGenerator KFileWidget KUrlNavigator KNewFileMenu KNameAndUrlInputDialog KEncodingFileDialog REQUIRED_HEADERS KIOFileWidgets_HEADERS ) install(TARGETS KF5KIOFileWidgets EXPORT KF5KIOTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${KIOFileWidgets_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/kiofilewidgets_export.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOFileWidgets COMPONENT Devel) # make available to ecm_add_qch in parent folder set(KIOFileWidgets_QCH_SOURCES ${KIOFileWidgets_HEADERS} PARENT_SCOPE) include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME KIOFileWidgets LIB_NAME KF5KIOFileWidgets DEPS "KIOWidgets KBookmarks KXmlGui Solid" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOFileWidgets) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/src/filewidgets/kfileplaceeditdialog.cpp b/src/filewidgets/kfileplaceeditdialog.cpp index ed53dde0..bd65dda1 100644 --- a/src/filewidgets/kfileplaceeditdialog.cpp +++ b/src/filewidgets/kfileplaceeditdialog.cpp @@ -1,222 +1,222 @@ /* This file is part of the KDE libraries Copyright (C) 2001,2002,2003 Carsten Pfeiffer Copyright (C) 2007 Kevin Ottens library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation, version 2. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "kfileplaceeditdialog_p.h" +#include "kfileplaceeditdialog.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 bool KFilePlaceEditDialog::getInformation(bool allowGlobal, QUrl &url, QString &label, QString &icon, bool isAddingNewPlace, bool &appLocal, int iconSize, QWidget *parent) { KFilePlaceEditDialog *dialog = new KFilePlaceEditDialog(allowGlobal, url, label, icon, isAddingNewPlace, appLocal, iconSize, parent); if (dialog->exec() == QDialog::Accepted) { // set the return parameters url = dialog->url(); label = dialog->label(); if (dialog->isIconEditable()) { icon = dialog->icon(); } appLocal = dialog->applicationLocal(); delete dialog; return true; } delete dialog; return false; } KFilePlaceEditDialog::KFilePlaceEditDialog(bool allowGlobal, const QUrl &url, const QString &label, const QString &icon, bool isAddingNewPlace, bool appLocal, int iconSize, QWidget *parent) : QDialog(parent), m_iconButton(nullptr) { if (isAddingNewPlace) { setWindowTitle(i18n("Add Places Entry")); } else { setWindowTitle(i18n("Edit Places Entry")); } setModal(true); QVBoxLayout *box = new QVBoxLayout(this); QFormLayout *layout = new QFormLayout(); box->addLayout(layout); QString whatsThisText = i18n("This is the text that will appear in the Places panel.

" "The label should consist of one or two words " "that will help you remember what this entry refers to. " "If you do not enter a label, it will be derived from " "the location's URL.
"); m_labelEdit = new QLineEdit(this); layout->addRow(i18n("L&abel:"), m_labelEdit); m_labelEdit->setText(label); m_labelEdit->setPlaceholderText(i18n("Enter descriptive label here")); m_labelEdit->setWhatsThis(whatsThisText); layout->labelForField(m_labelEdit)->setWhatsThis(whatsThisText); whatsThisText = i18n("This is the location associated with the entry. Any valid URL may be used. For example:

" "%1
http://www.kde.org
ftp://ftp.kde.org/pub/kde/stable

" "By clicking on the button next to the text edit box you can browse to an " "appropriate URL.
", QDir::homePath()); m_urlEdit = new KUrlRequester(url, this); m_urlEdit->setMode(KFile::Directory); layout->addRow(i18n("&Location:"), m_urlEdit); m_urlEdit->setWhatsThis(whatsThisText); layout->labelForField(m_urlEdit)->setWhatsThis(whatsThisText); // Room for at least 40 chars (average char width is half of height) m_urlEdit->setMinimumWidth(m_urlEdit->fontMetrics().height() * (40 / 2)); if (url.scheme() != QLatin1String("trash")) { whatsThisText = i18n("This is the icon that will appear in the Places panel.

" "Click on the button to select a different icon.
"); m_iconButton = new KIconButton(this); layout->addRow(i18n("Choose an &icon:"), m_iconButton); m_iconButton->setObjectName(QStringLiteral("icon button")); m_iconButton->setIconSize(iconSize); m_iconButton->setIconType(KIconLoader::NoGroup, KIconLoader::Place); if (icon.isEmpty()) { m_iconButton->setIcon(KIO::iconNameForUrl(url)); } else { m_iconButton->setIcon(icon); } m_iconButton->setWhatsThis(whatsThisText); layout->labelForField(m_iconButton)->setWhatsThis(whatsThisText); } if (allowGlobal) { QString appName; appName = QGuiApplication::applicationDisplayName(); if (appName.isEmpty()) { appName = QCoreApplication::applicationName(); } m_appLocal = new QCheckBox(i18n("&Only show when using this application (%1)", appName), this); m_appLocal->setChecked(appLocal); m_appLocal->setWhatsThis(i18n("Select this setting if you want this " "entry to show only when using the current application (%1).

" "If this setting is not selected, the entry will be available in all " "applications.
", appName)); box->addWidget(m_appLocal); } else { m_appLocal = nullptr; } connect(m_urlEdit->lineEdit(), &QLineEdit::textChanged, this, &KFilePlaceEditDialog::urlChanged); if (!label.isEmpty()) { // editing existing entry m_labelEdit->setFocus(); } else { // new entry m_urlEdit->setFocus(); } m_buttonBox = new QDialogButtonBox(this); m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); box->addWidget(m_buttonBox); setLayout(box); } KFilePlaceEditDialog::~KFilePlaceEditDialog() { } void KFilePlaceEditDialog::urlChanged(const QString &text) { m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty()); } QUrl KFilePlaceEditDialog::url() const { return m_urlEdit->url(); } QString KFilePlaceEditDialog::label() const { if (!m_labelEdit->text().isEmpty()) { return m_labelEdit->text(); } // derive descriptive label from the URL QUrl url = m_urlEdit->url(); if (!url.fileName().isEmpty()) { return url.fileName(); } if (!url.host().isEmpty()) { return url.host(); } return url.scheme(); } QString KFilePlaceEditDialog::icon() const { return m_iconButton->icon(); } bool KFilePlaceEditDialog::applicationLocal() const { if (!m_appLocal) { return true; } return m_appLocal->isChecked(); } bool KFilePlaceEditDialog::isIconEditable() const { return m_iconButton; } diff --git a/src/filewidgets/kfileplaceeditdialog_p.h b/src/filewidgets/kfileplaceeditdialog.h similarity index 94% rename from src/filewidgets/kfileplaceeditdialog_p.h rename to src/filewidgets/kfileplaceeditdialog.h index 26048aed..e7ad63b4 100644 --- a/src/filewidgets/kfileplaceeditdialog_p.h +++ b/src/filewidgets/kfileplaceeditdialog.h @@ -1,156 +1,156 @@ /* This file is part of the KDE libraries Copyright (C) 2001,2002,2003 Carsten Pfeiffer Copyright (C) 2007 Kevin Ottens library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation, version 2. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef KFILEPLACEEDITDIALOG_P_H -#define KFILEPLACEEDITDIALOG_P_H +#ifndef KFILEPLACEEDITDIALOG_H +#define KFILEPLACEEDITDIALOG_H -// Not exported anymore, only used internally. -//#include "kiofilewidgets_export.h" +#include "kiofilewidgets_export.h" #include #include #include class QCheckBox; class QDialogButtonBox; class QLineEdit; class KIconButton; class KUrlRequester; /** * @class KFilePlaceEditDialog kfileplaceeditdialog.h * - * A dialog that allows editing entries of a KUrlBar ( KUrlBarItem). + * A dialog that allows editing entries of a KFilePlacesModel. * The dialog offers to configure a given url, label and icon. * See the class-method getInformation() for easy usage. * * @author Carsten Pfeiffer + * @since 5.53 */ -class KFilePlaceEditDialog : public QDialog +class KIOFILEWIDGETS_EXPORT KFilePlaceEditDialog : public QDialog { Q_OBJECT public: /** * A convenience method to show the dialog and retrieve all the * properties via the given parameters. The parameters are used to * initialize the dialog and then return the user-configured values. * * @p allowGlobal if you set this to true, the dialog will have a checkbox * for the user to decide if he wants the entry to be * available globally or just for the current application. * @p url the url of the item * @p label a short, translated description of the item * @p icon an icon for the item * @p appLocal tells whether the item should be local for this application * or be available globally * @p iconSize determines the size of the icon that is shown/selectable * @p parent the parent-widget for the dialog * * If you leave the icon empty, the default icon for the given url will be * used (KMimeType::pixmapForUrl()). */ static bool getInformation(bool allowGlobal, QUrl &url, QString &label, QString &icon, bool isAddingNewPlace, bool &appLocal, int iconSize, QWidget *parent = nullptr); /** * Constructs a KFilePlaceEditDialog. * * @p allowGlobal if you set this to true, the dialog will have a checkbox * for the user to decide if he wants the entry to be * available globally or just for the current application. * @p url the url of the item * @p label a short, translated description of the item * @p icon an icon for the item * @p appLocal tells whether the item should be local for this application * or be available globally * @p iconSize determines the size of the icon that is shown/selectable * @p parent the parent-widget for the dialog * * If you leave the icon empty, the default icon for the given url will be * used (KMimeType::pixmapForUrl()). */ KFilePlaceEditDialog(bool allowGlobal, const QUrl &url, const QString &label, const QString &icon, bool isAddingNewPlace, bool appLocal = true, int iconSize = KIconLoader::SizeMedium, QWidget *parent = nullptr); /** * Destroys the dialog. */ ~KFilePlaceEditDialog(); /** * @returns the configured url */ QUrl url() const; /** * @returns the configured label */ QString label() const; /** * @returns the configured icon */ QString icon() const; /** * @returns whether the item's icon is editable, because all icons are not * (e.g. the Trash can display two icons, representing its full and empty states, and * it is simpler to make these icons non-editable rather than to provide an interface to edit them both) */ bool isIconEditable() const; /** * @returns whether the item should be local to the application or global. * If allowGlobal was set to false in the constructor, this will always * return true. */ bool applicationLocal() const; public Q_SLOTS: void urlChanged(const QString &); private: /** * The KUrlRequester used for editing the url */ KUrlRequester *m_urlEdit; /** * The QLineEdit used for editing the label */ QLineEdit *m_labelEdit; /** * The KIconButton to configure the icon */ KIconButton *m_iconButton; /** * The QCheckBox to modify the local/global setting */ QCheckBox *m_appLocal; QDialogButtonBox *m_buttonBox; }; -#endif // KFILEPLACEEDITDIALOG_P_H +#endif // KFILEPLACEEDITDIALOG_H diff --git a/src/filewidgets/kfileplacesview.cpp b/src/filewidgets/kfileplacesview.cpp index fddac5df..fcc5da1a 100644 --- a/src/filewidgets/kfileplacesview.cpp +++ b/src/filewidgets/kfileplacesview.cpp @@ -1,1479 +1,1479 @@ /* This file is part of the KDE project Copyright (C) 2007 Kevin Ottens Copyright (C) 2008 Rafael Fernández López This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kfileplacesview.h" #include "kfileplacesview_p.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 "kfileplaceeditdialog_p.h" +#include "kfileplaceeditdialog.h" #include "kfileplacesmodel.h" #define LATERAL_MARGIN 4 #define CAPACITYBAR_HEIGHT 6 class KFilePlacesViewDelegate : public QAbstractItemDelegate { Q_OBJECT public: explicit KFilePlacesViewDelegate(KFilePlacesView *parent); ~KFilePlacesViewDelegate() override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; int iconSize() const; void setIconSize(int newSize); void addAppearingItem(const QModelIndex &index); void setAppearingItemProgress(qreal value); void addDisappearingItem(const QModelIndex &index); void addDisappearingItemGroup(const QModelIndex &index); void setDisappearingItemProgress(qreal value); void setShowHoverIndication(bool show); void addFadeAnimation(const QModelIndex &index, QTimeLine *timeLine); void removeFadeAnimation(const QModelIndex &index); QModelIndex indexForFadeAnimation(QTimeLine *timeLine) const; QTimeLine *fadeAnimationForIndex(const QModelIndex &index) const; qreal contentsOpacity(const QModelIndex &index) const; bool pointIsHeaderArea(const QPoint &pos); void startDrag(); int sectionHeaderHeight() const; private: QString groupNameFromIndex(const QModelIndex &index) const; QModelIndex previousVisibleIndex(const QModelIndex &index) const; bool indexIsSectionHeader(const QModelIndex &index) const; void drawSectionHeader(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; QColor textColor(const QStyleOption &option) const; QColor baseColor(const QStyleOption &option) const; QColor mixedColor(const QColor &c1, const QColor &c2, int c1Percent) const; KFilePlacesView *m_view; int m_iconSize; QList m_appearingItems; int m_appearingIconSize; qreal m_appearingOpacity; QList m_disappearingItems; int m_disappearingIconSize; qreal m_disappearingOpacity; bool m_showHoverIndication; mutable bool m_dragStarted; QMap m_timeLineMap; QMap m_timeLineInverseMap; }; KFilePlacesViewDelegate::KFilePlacesViewDelegate(KFilePlacesView *parent) : QAbstractItemDelegate(parent), m_view(parent), m_iconSize(48), m_appearingIconSize(0), m_appearingOpacity(0.0), m_disappearingIconSize(0), m_disappearingOpacity(0.0), m_showHoverIndication(true), m_dragStarted(false) { } KFilePlacesViewDelegate::~KFilePlacesViewDelegate() { } QSize KFilePlacesViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { int iconSize = m_iconSize; if (m_appearingItems.contains(index)) { iconSize = m_appearingIconSize; } else if (m_disappearingItems.contains(index)) { iconSize = m_disappearingIconSize; } int height = option.fontMetrics.height() / 2 + qMax(iconSize, option.fontMetrics.height()); if (indexIsSectionHeader(index)) { height += sectionHeaderHeight(); } return QSize(option.rect.width(), height); } void KFilePlacesViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { painter->save(); QStyleOptionViewItem opt = option; // draw header when necessary if (indexIsSectionHeader(index)) { // If we are drawing the floating element used by drag/drop, do not draw the header if (!m_dragStarted) { drawSectionHeader(painter, opt, index); } // Move the target rect to the actual item rect const int headerHeight = sectionHeaderHeight(); opt.rect.translate(0, headerHeight); opt.rect.setHeight(opt.rect.height() - headerHeight); } m_dragStarted = false; // draw item if (m_appearingItems.contains(index)) { painter->setOpacity(m_appearingOpacity); } else if (m_disappearingItems.contains(index)) { painter->setOpacity(m_disappearingOpacity); } if (!m_showHoverIndication) { opt.state &= ~QStyle::State_MouseOver; } QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter); const KFilePlacesModel *placesModel = static_cast(index.model()); bool isLTR = opt.direction == Qt::LeftToRight; QIcon icon = index.model()->data(index, Qt::DecorationRole).value(); QPixmap pm = icon.pixmap(m_iconSize, m_iconSize, (opt.state & QStyle::State_Selected) && (opt.state & QStyle::State_Active) ? QIcon::Selected : QIcon::Normal); QPoint point(isLTR ? opt.rect.left() + LATERAL_MARGIN : opt.rect.right() - LATERAL_MARGIN - m_iconSize, opt.rect.top() + (opt.rect.height() - m_iconSize) / 2); painter->drawPixmap(point, pm); if (opt.state & QStyle::State_Selected) { QPalette::ColorGroup cg = QPalette::Active; if (!(opt.state & QStyle::State_Enabled)) { cg = QPalette::Disabled; } else if (!(opt.state & QStyle::State_Active)) { cg = QPalette::Inactive; } painter->setPen(opt.palette.color(cg, QPalette::HighlightedText)); } QRect rectText; bool drawCapacityBar = false; if (placesModel->data(index, KFilePlacesModel::CapacityBarRecommendedRole).toBool()) { const QUrl url = placesModel->url(index); if (url.isLocalFile() && contentsOpacity(index) > 0) { const QString mountPointPath = url.toLocalFile(); const KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo(mountPointPath); drawCapacityBar = info.size() != 0; if (drawCapacityBar) { painter->save(); painter->setOpacity(painter->opacity() * contentsOpacity(index)); int height = opt.fontMetrics.height() + CAPACITYBAR_HEIGHT; rectText = QRect(isLTR ? m_iconSize + LATERAL_MARGIN * 2 + opt.rect.left() : 0, opt.rect.top() + (opt.rect.height() / 2 - height / 2), opt.rect.width() - m_iconSize - LATERAL_MARGIN * 2, opt.fontMetrics.height()); painter->drawText(rectText, Qt::AlignLeft | Qt::AlignTop, opt.fontMetrics.elidedText(index.model()->data(index).toString(), Qt::ElideRight, rectText.width())); QRect capacityRect(isLTR ? rectText.x() : LATERAL_MARGIN, rectText.bottom() - 1, rectText.width() - LATERAL_MARGIN, CAPACITYBAR_HEIGHT); KCapacityBar capacityBar(KCapacityBar::DrawTextInline); capacityBar.setValue((info.used() * 100) / info.size()); capacityBar.drawCapacityBar(painter, capacityRect); painter->restore(); painter->save(); painter->setOpacity(painter->opacity() * (1 - contentsOpacity(index))); } } } rectText = QRect(isLTR ? m_iconSize + LATERAL_MARGIN * 2 + opt.rect.left() : 0, opt.rect.top(), opt.rect.width() - m_iconSize - LATERAL_MARGIN * 2, opt.rect.height()); painter->drawText(rectText, Qt::AlignLeft | Qt::AlignVCenter, opt.fontMetrics.elidedText(index.model()->data(index).toString(), Qt::ElideRight, rectText.width())); if (drawCapacityBar) { painter->restore(); } painter->restore(); } int KFilePlacesViewDelegate::iconSize() const { return m_iconSize; } void KFilePlacesViewDelegate::setIconSize(int newSize) { m_iconSize = newSize; } void KFilePlacesViewDelegate::addAppearingItem(const QModelIndex &index) { m_appearingItems << index; } void KFilePlacesViewDelegate::setAppearingItemProgress(qreal value) { if (value <= 0.25) { m_appearingOpacity = 0.0; m_appearingIconSize = iconSize() * value * 4; if (m_appearingIconSize >= m_iconSize) { m_appearingIconSize = m_iconSize; } } else { m_appearingIconSize = m_iconSize; m_appearingOpacity = (value - 0.25) * 4 / 3; if (value >= 1.0) { m_appearingItems.clear(); } } } void KFilePlacesViewDelegate::addDisappearingItem(const QModelIndex &index) { m_disappearingItems << index; } void KFilePlacesViewDelegate::addDisappearingItemGroup(const QModelIndex &index) { const KFilePlacesModel *placesModel = static_cast(index.model()); const QModelIndexList indexesGroup = placesModel->groupIndexes(placesModel->groupType(index)); m_disappearingItems.reserve(m_disappearingItems.count() + indexesGroup.count()); std::transform(indexesGroup.begin(), indexesGroup.end(), std::back_inserter(m_disappearingItems), [](const QModelIndex &idx){ return QPersistentModelIndex(idx); }); } void KFilePlacesViewDelegate::setDisappearingItemProgress(qreal value) { value = 1.0 - value; if (value <= 0.25) { m_disappearingOpacity = 0.0; m_disappearingIconSize = iconSize() * value * 4; if (m_disappearingIconSize >= m_iconSize) { m_disappearingIconSize = m_iconSize; } if (value <= 0.0) { m_disappearingItems.clear(); } } else { m_disappearingIconSize = m_iconSize; m_disappearingOpacity = (value - 0.25) * 4 / 3; } } void KFilePlacesViewDelegate::setShowHoverIndication(bool show) { m_showHoverIndication = show; } void KFilePlacesViewDelegate::addFadeAnimation(const QModelIndex &index, QTimeLine *timeLine) { m_timeLineMap.insert(index, timeLine); m_timeLineInverseMap.insert(timeLine, index); } void KFilePlacesViewDelegate::removeFadeAnimation(const QModelIndex &index) { QTimeLine *timeLine = m_timeLineMap.value(index, nullptr); m_timeLineMap.remove(index); m_timeLineInverseMap.remove(timeLine); } QModelIndex KFilePlacesViewDelegate::indexForFadeAnimation(QTimeLine *timeLine) const { return m_timeLineInverseMap.value(timeLine, QModelIndex()); } QTimeLine *KFilePlacesViewDelegate::fadeAnimationForIndex(const QModelIndex &index) const { return m_timeLineMap.value(index, nullptr); } qreal KFilePlacesViewDelegate::contentsOpacity(const QModelIndex &index) const { QTimeLine *timeLine = fadeAnimationForIndex(index); if (timeLine) { return timeLine->currentValue(); } return 0; } bool KFilePlacesViewDelegate::pointIsHeaderArea(const QPoint &pos) { // we only accept drag events starting from item body, ignore drag request from header QModelIndex index = m_view->indexAt(pos); if (!index.isValid()) { return false; } if (indexIsSectionHeader(index)) { const QRect vRect = m_view->visualRect(index); const int delegateY = pos.y() - vRect.y(); if (delegateY <= sectionHeaderHeight()) { return true; } } return false; } void KFilePlacesViewDelegate::startDrag() { m_dragStarted = true; } QString KFilePlacesViewDelegate::groupNameFromIndex(const QModelIndex &index) const { if (index.isValid()) { return index.data(KFilePlacesModel::GroupRole).toString(); } else { return QString(); } } QModelIndex KFilePlacesViewDelegate::previousVisibleIndex(const QModelIndex &index) const { if (index.row() == 0) { return QModelIndex(); } const QAbstractItemModel *model = index.model(); QModelIndex prevIndex = model->index(index.row() - 1, index.column(), index.parent()); while (m_view->isRowHidden(prevIndex.row())) { if (prevIndex.row() == 0) { return QModelIndex(); } prevIndex = model->index(prevIndex.row() - 1, index.column(), index.parent()); } return prevIndex; } bool KFilePlacesViewDelegate::indexIsSectionHeader(const QModelIndex &index) const { if (m_view->isRowHidden(index.row())) { return false; } if (index.row() == 0) { return true; } const auto groupName = groupNameFromIndex(index); const auto previousGroupName = groupNameFromIndex(previousVisibleIndex(index)); return groupName != previousGroupName; } void KFilePlacesViewDelegate::drawSectionHeader(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { const KFilePlacesModel *placesModel = static_cast(index.model()); const QString groupLabel = index.data(KFilePlacesModel::GroupRole).toString(); const QString category = placesModel->isGroupHidden(index) ? i18n("%1 (hidden)", groupLabel) : groupLabel; QRect textRect(option.rect); textRect.setLeft(textRect.left() + 3); /* Take spacing into account: The spacing to the previous section compensates for the spacing to the first item.*/ textRect.setY(textRect.y() /* + qMax(2, m_view->spacing()) - qMax(2, m_view->spacing())*/); textRect.setHeight(sectionHeaderHeight()); painter->save(); // based on dolphin colors const QColor c1 = textColor(option); const QColor c2 = baseColor(option); QColor penColor = mixedColor(c1, c2, 60); painter->setPen(penColor); painter->drawText(textRect, Qt::AlignLeft | Qt::AlignBottom, category); painter->restore(); } QColor KFilePlacesViewDelegate::textColor(const QStyleOption &option) const { const QPalette::ColorGroup group = m_view->isActiveWindow() ? QPalette::Active : QPalette::Inactive; return option.palette.color(group, QPalette::WindowText); } QColor KFilePlacesViewDelegate::baseColor(const QStyleOption &option) const { const QPalette::ColorGroup group = m_view->isActiveWindow() ? QPalette::Active : QPalette::Inactive; return option.palette.color(group, QPalette::Window); } QColor KFilePlacesViewDelegate::mixedColor(const QColor& c1, const QColor& c2, int c1Percent) const { Q_ASSERT(c1Percent >= 0 && c1Percent <= 100); const int c2Percent = 100 - c1Percent; return QColor((c1.red() * c1Percent + c2.red() * c2Percent) / 100, (c1.green() * c1Percent + c2.green() * c2Percent) / 100, (c1.blue() * c1Percent + c2.blue() * c2Percent) / 100); } int KFilePlacesViewDelegate::sectionHeaderHeight() const { // Account for the spacing between header and item return QApplication::fontMetrics().height() + qMax(2, m_view->spacing()); } class Q_DECL_HIDDEN KFilePlacesView::Private { public: explicit Private(KFilePlacesView *parent) : q(parent) , watcher(new KFilePlacesEventWatcher(q)) {} enum FadeType { FadeIn = 0, FadeOut }; KFilePlacesView *const q; QUrl currentUrl; bool autoResizeItems; bool showAll; bool smoothItemResizing; bool dropOnPlace; bool dragging; Solid::StorageAccess *lastClickedStorage = nullptr; QPersistentModelIndex lastClickedIndex; QRect dropRect; void setCurrentIndex(const QModelIndex &index); void adaptItemSize(); void updateHiddenRows(); bool insertAbove(const QRect &itemRect, const QPoint &pos) const; bool insertBelow(const QRect &itemRect, const QPoint &pos) const; int insertIndicatorHeight(int itemHeight) const; void fadeCapacityBar(const QModelIndex &index, FadeType fadeType); int sectionsCount() const; void addDisappearingItem(KFilePlacesViewDelegate *delegate, const QModelIndex &index); void triggerItemAppearingAnimation(); void triggerItemDisappearingAnimation(); void _k_placeClicked(const QModelIndex &index); void _k_placeEntered(const QModelIndex &index); void _k_placeLeft(const QModelIndex &index); void _k_storageSetupDone(const QModelIndex &index, bool success); void _k_adaptItemsUpdate(qreal value); void _k_itemAppearUpdate(qreal value); void _k_itemDisappearUpdate(qreal value); void _k_enableSmoothItemResizing(); void _k_capacityBarFadeValueChanged(); void _k_triggerDevicePolling(); QTimeLine adaptItemsTimeline; int oldSize, endSize; QTimeLine itemAppearTimeline; QTimeLine itemDisappearTimeline; KFilePlacesEventWatcher *const watcher; KFilePlacesViewDelegate *delegate = nullptr; QTimer pollDevices; int pollingRequestCount; }; KFilePlacesView::KFilePlacesView(QWidget *parent) : QListView(parent), d(new Private(this)) { d->showAll = false; d->smoothItemResizing = false; d->dropOnPlace = false; d->autoResizeItems = true; d->dragging = false; d->lastClickedStorage = nullptr; d->pollingRequestCount = 0; d->delegate = new KFilePlacesViewDelegate(this); setSelectionRectVisible(false); setSelectionMode(SingleSelection); setDragEnabled(true); setAcceptDrops(true); setMouseTracking(true); setDropIndicatorShown(false); setFrameStyle(QFrame::NoFrame); setResizeMode(Adjust); setItemDelegate(d->delegate); QPalette palette = viewport()->palette(); palette.setColor(viewport()->backgroundRole(), Qt::transparent); palette.setColor(viewport()->foregroundRole(), palette.color(QPalette::WindowText)); viewport()->setPalette(palette); connect(this, SIGNAL(clicked(QModelIndex)), this, SLOT(_k_placeClicked(QModelIndex))); // Note: Don't connect to the activated() signal, as the behavior when it is // committed depends on the used widget style. The click behavior of // KFilePlacesView should be style independent. connect(&d->adaptItemsTimeline, SIGNAL(valueChanged(qreal)), this, SLOT(_k_adaptItemsUpdate(qreal))); d->adaptItemsTimeline.setDuration(500); d->adaptItemsTimeline.setUpdateInterval(5); d->adaptItemsTimeline.setCurveShape(QTimeLine::EaseInOutCurve); connect(&d->itemAppearTimeline, SIGNAL(valueChanged(qreal)), this, SLOT(_k_itemAppearUpdate(qreal))); d->itemAppearTimeline.setDuration(500); d->itemAppearTimeline.setUpdateInterval(5); d->itemAppearTimeline.setCurveShape(QTimeLine::EaseInOutCurve); connect(&d->itemDisappearTimeline, SIGNAL(valueChanged(qreal)), this, SLOT(_k_itemDisappearUpdate(qreal))); d->itemDisappearTimeline.setDuration(500); d->itemDisappearTimeline.setUpdateInterval(5); d->itemDisappearTimeline.setCurveShape(QTimeLine::EaseInOutCurve); viewport()->installEventFilter(d->watcher); connect(d->watcher, SIGNAL(entryEntered(QModelIndex)), this, SLOT(_k_placeEntered(QModelIndex))); connect(d->watcher, SIGNAL(entryLeft(QModelIndex)), this, SLOT(_k_placeLeft(QModelIndex))); d->pollDevices.setInterval(5000); connect(&d->pollDevices, SIGNAL(timeout()), this, SLOT(_k_triggerDevicePolling())); // FIXME: this is necessary to avoid flashes of black with some widget styles. // could be a bug in Qt (e.g. QAbstractScrollArea) or KFilePlacesView, but has not // yet been tracked down yet. until then, this works and is harmlessly enough. // in fact, some QStyle (Oxygen, Skulpture, others?) do this already internally. // See br #242358 for more information verticalScrollBar()->setAttribute(Qt::WA_OpaquePaintEvent, false); } KFilePlacesView::~KFilePlacesView() { delete d; } void KFilePlacesView::setDropOnPlaceEnabled(bool enabled) { d->dropOnPlace = enabled; } bool KFilePlacesView::isDropOnPlaceEnabled() const { return d->dropOnPlace; } void KFilePlacesView::setAutoResizeItemsEnabled(bool enabled) { d->autoResizeItems = enabled; } bool KFilePlacesView::isAutoResizeItemsEnabled() const { return d->autoResizeItems; } void KFilePlacesView::setUrl(const QUrl &url) { KFilePlacesModel *placesModel = qobject_cast(model()); if (placesModel == nullptr) { return; } QModelIndex index = placesModel->closestItem(url); QModelIndex current = selectionModel()->currentIndex(); if (index.isValid()) { if (current != index && placesModel->isHidden(current) && !d->showAll) { KFilePlacesViewDelegate *delegate = static_cast(itemDelegate()); d->addDisappearingItem(delegate, current); } if (current != index && placesModel->isHidden(index) && !d->showAll) { KFilePlacesViewDelegate *delegate = static_cast(itemDelegate()); delegate->addAppearingItem(index); d->triggerItemAppearingAnimation(); setRowHidden(index.row(), false); } d->currentUrl = url; selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); } else { d->currentUrl = QUrl(); selectionModel()->clear(); } if (!current.isValid()) { d->updateHiddenRows(); } } void KFilePlacesView::setShowAll(bool showAll) { KFilePlacesModel *placesModel = qobject_cast(model()); if (placesModel == nullptr) { return; } d->showAll = showAll; KFilePlacesViewDelegate *delegate = static_cast(itemDelegate()); int rowCount = placesModel->rowCount(); QModelIndex current = placesModel->closestItem(d->currentUrl); if (showAll) { d->updateHiddenRows(); for (int i = 0; i < rowCount; ++i) { QModelIndex index = placesModel->index(i, 0); if (index != current && placesModel->isHidden(index)) { delegate->addAppearingItem(index); } } d->triggerItemAppearingAnimation(); } else { for (int i = 0; i < rowCount; ++i) { QModelIndex index = placesModel->index(i, 0); if (index != current && placesModel->isHidden(index)) { delegate->addDisappearingItem(index); } } d->triggerItemDisappearingAnimation(); } } void KFilePlacesView::keyPressEvent(QKeyEvent *event) { QListView::keyPressEvent(event); if ((event->key() == Qt::Key_Return) || (event->key() == Qt::Key_Enter)) { d->_k_placeClicked(currentIndex()); } } void KFilePlacesView::contextMenuEvent(QContextMenuEvent *event) { KFilePlacesModel *placesModel = qobject_cast(model()); if (!placesModel) { return; } KFilePlacesViewDelegate *delegate = static_cast(itemDelegate()); QModelIndex index = indexAt(event->pos()); const QString label = placesModel->text(index).replace(QLatin1Char('&'), QLatin1String("&&")); const QUrl placeUrl = placesModel->url(index); QMenu menu; QAction *edit = nullptr; QAction *hide = nullptr; QAction *emptyTrash = nullptr; QAction *eject = nullptr; QAction *teardown = nullptr; QAction *add = nullptr; QAction *mainSeparator = nullptr; QAction *hideSection = nullptr; QAction *properties = nullptr; QAction *mount = nullptr; const bool clickOverHeader = delegate->pointIsHeaderArea(event->pos()); if (clickOverHeader) { const KFilePlacesModel::GroupType type = placesModel->groupType(index); hideSection = menu.addAction(QIcon::fromTheme(QStringLiteral("hint")), i18n("Hide Section")); hideSection->setCheckable(true); hideSection->setChecked(placesModel->isGroupHidden(type)); } else if (index.isValid()) { if (!placesModel->isDevice(index)) { if (placeUrl.toString() == QLatin1String("trash:/")) { emptyTrash = menu.addAction(QIcon::fromTheme(QStringLiteral("trash-empty")), i18nc("@action:inmenu", "Empty Trash")); KConfig trashConfig(QStringLiteral("trashrc"), KConfig::SimpleConfig); emptyTrash->setEnabled(!trashConfig.group("Status").readEntry("Empty", true)); menu.addSeparator(); } add = menu.addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Add Entry...")); mainSeparator = menu.addSeparator(); } else { eject = placesModel->ejectActionForIndex(index); if (eject != nullptr) { eject->setParent(&menu); menu.addAction(eject); } teardown = placesModel->teardownActionForIndex(index); if (teardown != nullptr) { // Disable teardown option for root and home partitions bool teardownEnabled = placeUrl != QUrl::fromLocalFile(QDir::rootPath()); if (teardownEnabled) { KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(QDir::homePath()); if (mountPoint && placeUrl == QUrl::fromLocalFile(mountPoint->mountPoint())) { teardownEnabled = false; } } teardown->setEnabled(teardownEnabled); teardown->setParent(&menu); menu.addAction(teardown); } if (placesModel->setupNeeded(index)) { mount = menu.addAction(QIcon::fromTheme(QStringLiteral("media-mount")), i18nc("@action:inmenu", "Mount")); } if (teardown != nullptr || eject != nullptr || mount != nullptr) { mainSeparator = menu.addSeparator(); } } if (add == nullptr) { add = menu.addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Add Entry...")); } if (placeUrl.isLocalFile()) { properties = menu.addAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n("Properties")); } if (!placesModel->isDevice(index)) { edit = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-entry")), i18n("&Edit Entry '%1'...", label)); } hide = menu.addAction(QIcon::fromTheme(QStringLiteral("hint")), i18n("&Hide Entry '%1'", label)); hide->setCheckable(true); hide->setChecked(placesModel->isHidden(index)); // if a parent is hidden no interaction should be possible with children, show it first to do so hide->setEnabled(!placesModel->isGroupHidden(placesModel->groupType(index))); } else { add = menu.addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Add Entry...")); } QAction *showAll = nullptr; if (placesModel->hiddenCount() > 0) { showAll = new QAction(QIcon::fromTheme(QStringLiteral("visibility")), i18n("&Show All Entries"), &menu); showAll->setCheckable(true); showAll->setChecked(d->showAll); if (mainSeparator == nullptr) { mainSeparator = menu.addSeparator(); } menu.insertAction(mainSeparator, showAll); } QAction *remove = nullptr; if (!clickOverHeader && index.isValid() && !placesModel->isDevice(index)) { remove = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("&Remove Entry '%1'", label)); } menu.addActions(actions()); if (menu.isEmpty()) { return; } QAction *result = menu.exec(event->globalPos()); if (emptyTrash && (result == 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()); job->uiDelegate()->setAutoErrorHandlingEnabled(true); } } else if (properties && (result == properties)) { KPropertiesDialog::showDialog(placeUrl, this); } else if (edit && (result == edit)) { KBookmark bookmark = placesModel->bookmarkForIndex(index); QUrl url = bookmark.url(); QString label = bookmark.text(); QString iconName = bookmark.icon(); bool appLocal = !bookmark.metaDataItem(QStringLiteral("OnlyInApp")).isEmpty(); if (KFilePlaceEditDialog::getInformation(true, url, label, iconName, false, appLocal, 64, this)) { QString appName; if (appLocal) { appName = QCoreApplication::instance()->applicationName(); } placesModel->editPlace(index, label, url, iconName, appName); } } else if (remove && (result == remove)) { placesModel->removePlace(index); } else if (hideSection && (result == hideSection)) { const KFilePlacesModel::GroupType type = placesModel->groupType(index); placesModel->setGroupHidden(type, hideSection->isChecked()); if (!d->showAll && hideSection->isChecked()) { delegate->addDisappearingItemGroup(index); d->triggerItemDisappearingAnimation(); } } else if (hide && (result == hide)) { placesModel->setPlaceHidden(index, hide->isChecked()); QModelIndex current = placesModel->closestItem(d->currentUrl); if (index != current && !d->showAll && hide->isChecked()) { delegate->addDisappearingItem(index); d->triggerItemDisappearingAnimation(); } } else if (showAll && (result == showAll)) { setShowAll(showAll->isChecked()); } else if (teardown && (result == teardown)) { placesModel->requestTeardown(index); } else if (eject && (result == eject)) { placesModel->requestEject(index); } else if (add && (result == add)) { QUrl url = d->currentUrl; QString label; QString iconName = QStringLiteral("folder"); bool appLocal = true; if (KFilePlaceEditDialog::getInformation(true, url, label, iconName, true, appLocal, 64, this)) { QString appName; if (appLocal) { appName = QCoreApplication::instance()->applicationName(); } placesModel->addPlace(label, url, iconName, appName, index); } } else if (mount && (result == mount)) { placesModel->requestSetup(index); } index = placesModel->closestItem(d->currentUrl); selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); } void KFilePlacesView::resizeEvent(QResizeEvent *event) { QListView::resizeEvent(event); d->adaptItemSize(); } void KFilePlacesView::showEvent(QShowEvent *event) { QListView::showEvent(event); QTimer::singleShot(100, this, SLOT(_k_enableSmoothItemResizing())); } void KFilePlacesView::hideEvent(QHideEvent *event) { QListView::hideEvent(event); d->smoothItemResizing = false; } void KFilePlacesView::dragEnterEvent(QDragEnterEvent *event) { QListView::dragEnterEvent(event); d->dragging = true; KFilePlacesViewDelegate *delegate = static_cast(itemDelegate()); delegate->setShowHoverIndication(false); d->dropRect = QRect(); } void KFilePlacesView::dragLeaveEvent(QDragLeaveEvent *event) { QListView::dragLeaveEvent(event); d->dragging = false; KFilePlacesViewDelegate *delegate = static_cast(itemDelegate()); delegate->setShowHoverIndication(true); setDirtyRegion(d->dropRect); } void KFilePlacesView::dragMoveEvent(QDragMoveEvent *event) { QListView::dragMoveEvent(event); // update the drop indicator const QPoint pos = event->pos(); const QModelIndex index = indexAt(pos); setDirtyRegion(d->dropRect); if (index.isValid()) { const QRect rect = visualRect(index); const int gap = d->insertIndicatorHeight(rect.height()); if (d->insertAbove(rect, pos)) { // indicate that the item will be inserted above the current place d->dropRect = QRect(rect.left(), rect.top() - gap / 2, rect.width(), gap); } else if (d->insertBelow(rect, pos)) { // indicate that the item will be inserted below the current place d->dropRect = QRect(rect.left(), rect.bottom() + 1 - gap / 2, rect.width(), gap); } else { // indicate that the item be dropped above the current place d->dropRect = rect; } } setDirtyRegion(d->dropRect); } void KFilePlacesView::dropEvent(QDropEvent *event) { const QPoint pos = event->pos(); const QModelIndex index = indexAt(pos); if (index.isValid()) { const QRect rect = visualRect(index); if (!d->insertAbove(rect, pos) && !d->insertBelow(rect, pos)) { KFilePlacesModel *placesModel = qobject_cast(model()); Q_ASSERT(placesModel != nullptr); emit urlsDropped(placesModel->url(index), event, this); event->acceptProposedAction(); } } QListView::dropEvent(event); d->dragging = false; KFilePlacesViewDelegate *delegate = static_cast(itemDelegate()); delegate->setShowHoverIndication(true); } void KFilePlacesView::paintEvent(QPaintEvent *event) { QListView::paintEvent(event); if (d->dragging && !d->dropRect.isEmpty()) { // draw drop indicator QPainter painter(viewport()); const QModelIndex index = indexAt(d->dropRect.topLeft()); const QRect itemRect = visualRect(index); const bool drawInsertIndicator = !d->dropOnPlace || d->dropRect.height() <= d->insertIndicatorHeight(itemRect.height()); if (drawInsertIndicator) { // draw indicator for inserting items QBrush blendedBrush = viewOptions().palette.brush(QPalette::Normal, QPalette::Highlight); QColor color = blendedBrush.color(); const int y = (d->dropRect.top() + d->dropRect.bottom()) / 2; const int thickness = d->dropRect.height() / 2; Q_ASSERT(thickness >= 1); int alpha = 255; const int alphaDec = alpha / (thickness + 1); for (int i = 0; i < thickness; i++) { color.setAlpha(alpha); alpha -= alphaDec; painter.setPen(color); painter.drawLine(d->dropRect.left(), y - i, d->dropRect.right(), y - i); painter.drawLine(d->dropRect.left(), y + i, d->dropRect.right(), y + i); } } else { // draw indicator for copying/moving/linking to items QStyleOptionViewItem opt; opt.initFrom(this); opt.rect = itemRect; opt.state = QStyle::State_Enabled | QStyle::State_MouseOver; style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, &painter, this); } } } void KFilePlacesView::startDrag(Qt::DropActions supportedActions) { KFilePlacesViewDelegate *delegate = static_cast(itemDelegate()); delegate->startDrag(); QListView::startDrag(supportedActions); } void KFilePlacesView::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { KFilePlacesViewDelegate *delegate = static_cast(itemDelegate()); // does not accept drags from section header area if (delegate->pointIsHeaderArea(event->pos())) { return; } } QListView::mousePressEvent(event); } void KFilePlacesView::setModel(QAbstractItemModel *model) { QListView::setModel(model); d->updateHiddenRows(); // Uses Qt::QueuedConnection to delay the time when the slot will be // called. In case of an item move the remove+add will be done before // we adapt the item size (otherwise we'd get it wrong as we'd execute // it after the remove only). connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(adaptItemSize()), Qt::QueuedConnection); connect(selectionModel(), &QItemSelectionModel::currentChanged, d->watcher, &KFilePlacesEventWatcher::currentIndexChanged); } void KFilePlacesView::rowsInserted(const QModelIndex &parent, int start, int end) { QListView::rowsInserted(parent, start, end); setUrl(d->currentUrl); KFilePlacesViewDelegate *delegate = static_cast(itemDelegate()); KFilePlacesModel *placesModel = static_cast(model()); for (int i = start; i <= end; ++i) { QModelIndex index = placesModel->index(i, 0, parent); if (d->showAll || !placesModel->isHidden(index)) { delegate->addAppearingItem(index); d->triggerItemAppearingAnimation(); } else { setRowHidden(i, true); } } d->triggerItemAppearingAnimation(); d->adaptItemSize(); } QSize KFilePlacesView::sizeHint() const { KFilePlacesModel *placesModel = qobject_cast(model()); if (!placesModel) { return QListView::sizeHint(); } const int height = QListView::sizeHint().height(); QFontMetrics fm = d->q->fontMetrics(); int textWidth = 0; for (int i = 0; i < placesModel->rowCount(); ++i) { QModelIndex index = placesModel->index(i, 0); if (!placesModel->isHidden(index)) { textWidth = qMax(textWidth, fm.width(index.data(Qt::DisplayRole).toString())); } } const int iconSize = KIconLoader::global()->currentSize(KIconLoader::Small) + 3 * LATERAL_MARGIN; return QSize(iconSize + textWidth + fm.height() / 2, height); } void KFilePlacesView::Private::addDisappearingItem(KFilePlacesViewDelegate *delegate, const QModelIndex &index) { delegate->addDisappearingItem(index); if (itemDisappearTimeline.state() != QTimeLine::Running) { delegate->setDisappearingItemProgress(0.0); itemDisappearTimeline.start(); } } void KFilePlacesView::Private::setCurrentIndex(const QModelIndex &index) { KFilePlacesModel *placesModel = qobject_cast(q->model()); if (placesModel == nullptr) { return; } QUrl url = placesModel->url(index); if (url.isValid()) { currentUrl = url; updateHiddenRows(); emit q->urlChanged(KFilePlacesModel::convertedUrl(url)); if (showAll) { q->setShowAll(false); } } else { q->setUrl(currentUrl); } } void KFilePlacesView::Private::adaptItemSize() { KFilePlacesViewDelegate *delegate = static_cast(q->itemDelegate()); if (!autoResizeItems) { const int size = q->iconSize().width(); // Assume width == height delegate->setIconSize(size); q->scheduleDelayedItemsLayout(); return; } KFilePlacesModel *placesModel = qobject_cast(q->model()); if (placesModel == nullptr) { return; } int rowCount = placesModel->rowCount(); if (!showAll) { rowCount -= placesModel->hiddenCount(); QModelIndex current = placesModel->closestItem(currentUrl); if (placesModel->isHidden(current)) { rowCount++; } } if (rowCount == 0) { return; // We've nothing to display anyway } const int minSize = IconSize(KIconLoader::Small); const int maxSize = 64; int textWidth = 0; QFontMetrics fm = q->fontMetrics(); for (int i = 0; i < placesModel->rowCount(); ++i) { QModelIndex index = placesModel->index(i, 0); if (!placesModel->isHidden(index)) { textWidth = qMax(textWidth, fm.width(index.data(Qt::DisplayRole).toString())); } } const int margin = q->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, q) + 1; const int maxWidth = q->viewport()->width() - textWidth - 4 * margin - 1; const int totalItemsHeight = (fm.height() / 2) * rowCount; const int totalSectionsHeight = delegate->sectionHeaderHeight() * sectionsCount(); const int maxHeight = ((q->height() - totalSectionsHeight - totalItemsHeight) / rowCount) - 1; int size = qMin(maxHeight, maxWidth); if (size < minSize) { size = minSize; } else if (size > maxSize) { size = maxSize; } else { // Make it a multiple of 16 size &= ~0xf; } if (size == delegate->iconSize()) { return; } if (smoothItemResizing) { oldSize = delegate->iconSize(); endSize = size; if (adaptItemsTimeline.state() != QTimeLine::Running) { adaptItemsTimeline.start(); } } else { delegate->setIconSize(size); q->scheduleDelayedItemsLayout(); } } void KFilePlacesView::Private::updateHiddenRows() { KFilePlacesModel *placesModel = qobject_cast(q->model()); if (placesModel == nullptr) { return; } int rowCount = placesModel->rowCount(); QModelIndex current = placesModel->closestItem(currentUrl); for (int i = 0; i < rowCount; ++i) { QModelIndex index = placesModel->index(i, 0); if (index != current && placesModel->isHidden(index) && !showAll) { q->setRowHidden(i, true); } else { q->setRowHidden(i, false); } } adaptItemSize(); } bool KFilePlacesView::Private::insertAbove(const QRect &itemRect, const QPoint &pos) const { if (dropOnPlace) { return pos.y() < itemRect.top() + insertIndicatorHeight(itemRect.height()) / 2; } return pos.y() < itemRect.top() + (itemRect.height() / 2); } bool KFilePlacesView::Private::insertBelow(const QRect &itemRect, const QPoint &pos) const { if (dropOnPlace) { return pos.y() > itemRect.bottom() - insertIndicatorHeight(itemRect.height()) / 2; } return pos.y() >= itemRect.top() + (itemRect.height() / 2); } int KFilePlacesView::Private::insertIndicatorHeight(int itemHeight) const { const int min = 4; const int max = 12; int height = itemHeight / 4; if (height < min) { height = min; } else if (height > max) { height = max; } return height; } void KFilePlacesView::Private::fadeCapacityBar(const QModelIndex &index, FadeType fadeType) { QTimeLine *timeLine = delegate->fadeAnimationForIndex(index); delete timeLine; delegate->removeFadeAnimation(index); timeLine = new QTimeLine(250, q); connect(timeLine, SIGNAL(valueChanged(qreal)), q, SLOT(_k_capacityBarFadeValueChanged())); if (fadeType == FadeIn) { timeLine->setDirection(QTimeLine::Forward); timeLine->setCurrentTime(0); } else { timeLine->setDirection(QTimeLine::Backward); timeLine->setCurrentTime(250); } delegate->addFadeAnimation(index, timeLine); timeLine->start(); } int KFilePlacesView::Private::sectionsCount() const { int count = 0; QString prevSection; const int rowCount = q->model()->rowCount(); for(int i = 0; i < rowCount; i++) { if (!q->isRowHidden(i)) { const QModelIndex index = q->model()->index(i, 0); const QString sectionName = index.data(KFilePlacesModel::GroupRole).toString(); if (prevSection != sectionName) { prevSection = sectionName; count++; } } } return count; } void KFilePlacesView::Private::triggerItemAppearingAnimation() { if (itemAppearTimeline.state() != QTimeLine::Running) { delegate->setAppearingItemProgress(0.0); itemAppearTimeline.start(); } } void KFilePlacesView::Private::triggerItemDisappearingAnimation() { if (itemDisappearTimeline.state() != QTimeLine::Running) { delegate->setDisappearingItemProgress(0.0); itemDisappearTimeline.start(); } } void KFilePlacesView::Private::_k_placeClicked(const QModelIndex &index) { KFilePlacesModel *placesModel = qobject_cast(q->model()); if (placesModel == nullptr) { return; } lastClickedIndex = QPersistentModelIndex(); if (placesModel->setupNeeded(index)) { QObject::connect(placesModel, SIGNAL(setupDone(QModelIndex,bool)), q, SLOT(_k_storageSetupDone(QModelIndex,bool))); lastClickedIndex = index; placesModel->requestSetup(index); return; } setCurrentIndex(index); } void KFilePlacesView::Private::_k_placeEntered(const QModelIndex &index) { fadeCapacityBar(index, FadeIn); pollingRequestCount++; if (pollingRequestCount == 1) { pollDevices.start(); } } void KFilePlacesView::Private::_k_placeLeft(const QModelIndex &index) { fadeCapacityBar(index, FadeOut); pollingRequestCount--; if (!pollingRequestCount) { pollDevices.stop(); } } void KFilePlacesView::Private::_k_storageSetupDone(const QModelIndex &index, bool success) { if (index != lastClickedIndex) { return; } KFilePlacesModel *placesModel = qobject_cast(q->model()); if (placesModel) { QObject::disconnect(placesModel, SIGNAL(setupDone(QModelIndex,bool)), q, SLOT(_k_storageSetupDone(QModelIndex,bool))); } if (success) { setCurrentIndex(lastClickedIndex); } else { q->setUrl(currentUrl); } lastClickedIndex = QPersistentModelIndex(); } void KFilePlacesView::Private::_k_adaptItemsUpdate(qreal value) { int add = (endSize - oldSize) * value; int size = oldSize + add; KFilePlacesViewDelegate *delegate = static_cast(q->itemDelegate()); delegate->setIconSize(size); q->scheduleDelayedItemsLayout(); } void KFilePlacesView::Private::_k_itemAppearUpdate(qreal value) { KFilePlacesViewDelegate *delegate = static_cast(q->itemDelegate()); delegate->setAppearingItemProgress(value); q->scheduleDelayedItemsLayout(); } void KFilePlacesView::Private::_k_itemDisappearUpdate(qreal value) { KFilePlacesViewDelegate *delegate = static_cast(q->itemDelegate()); delegate->setDisappearingItemProgress(value); if (value >= 1.0) { updateHiddenRows(); } q->scheduleDelayedItemsLayout(); } void KFilePlacesView::Private::_k_enableSmoothItemResizing() { smoothItemResizing = true; } void KFilePlacesView::Private::_k_capacityBarFadeValueChanged() { const QModelIndex index = delegate->indexForFadeAnimation(static_cast(q->sender())); if (!index.isValid()) { return; } q->update(index); } void KFilePlacesView::Private::_k_triggerDevicePolling() { const QModelIndex hoveredIndex = watcher->hoveredIndex(); if (hoveredIndex.isValid()) { const KFilePlacesModel *placesModel = static_cast(hoveredIndex.model()); if (placesModel->isDevice(hoveredIndex)) { q->update(hoveredIndex); } } const QModelIndex focusedIndex = watcher->focusedIndex(); if (focusedIndex.isValid() && focusedIndex != hoveredIndex) { const KFilePlacesModel *placesModel = static_cast(focusedIndex.model()); if (placesModel->isDevice(focusedIndex)) { q->update(focusedIndex); } } } void KFilePlacesView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { QListView::dataChanged(topLeft, bottomRight, roles); d->adaptItemSize(); } #include "moc_kfileplacesview.cpp" #include "moc_kfileplacesview_p.cpp" #include "kfileplacesview.moc"