diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -69,6 +69,9 @@ kitemviews/kitemlistwidget.cpp kitemviews/kitemmodelbase.cpp kitemviews/kitemset.cpp + kitemviews/mountpointobserver.cpp + kitemviews/mountpointobservercache.cpp + kitemviews/spaceinfoobserver.cpp kitemviews/kstandarditem.cpp kitemviews/kstandarditemlistgroupheader.cpp kitemviews/kstandarditemlistwidget.cpp diff --git a/src/kitemviews/kstandarditemlistwidget.h b/src/kitemviews/kstandarditemlistwidget.h --- a/src/kitemviews/kstandarditemlistwidget.h +++ b/src/kitemviews/kstandarditemlistwidget.h @@ -31,6 +31,7 @@ class KItemListRoleEditor; class KItemListStyleOption; class KItemListView; +class SpaceInfoObserver; class DOLPHIN_EXPORT KStandardItemListWidgetInformant : public KItemListWidgetInformant { @@ -270,6 +271,10 @@ QPixmap m_overlay; QPixmap m_rating; + bool m_isMountPoint; + SpaceInfoObserver* m_spaceInfoObserver; + QPixmap m_progressBar; + KItemListRoleEditor* m_roleEditor; KItemListRoleEditor* m_oldRoleEditor; diff --git a/src/kitemviews/kstandarditemlistwidget.cpp b/src/kitemviews/kstandarditemlistwidget.cpp --- a/src/kitemviews/kstandarditemlistwidget.cpp +++ b/src/kitemviews/kstandarditemlistwidget.cpp @@ -42,6 +42,9 @@ #include #include +#include "spaceinfoobserver.h" +#include + // #define KSTANDARDITEMLISTWIDGET_DEBUG KStandardItemListWidgetInformant::KStandardItemListWidgetInformant() : @@ -273,6 +276,9 @@ m_additionalInfoTextColor(), m_overlay(), m_rating(), + m_isMountPoint(false), + m_spaceInfoObserver(0), + m_progressBar(), m_roleEditor(nullptr), m_oldRoleEditor(nullptr) { @@ -290,6 +296,10 @@ if (m_oldRoleEditor) { m_oldRoleEditor->deleteLater(); } + + if (m_spaceInfoObserver) { + m_spaceInfoObserver->deleteLater(); + } } void KStandardItemListWidget::setLayout(Layout layout) @@ -411,8 +421,13 @@ 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); + const QByteArray role = m_sortedVisibleRoles[i]; + if (role == "size" && !m_progressBar.isNull()) { + // skip + } else { + const TextInfo* textInfo = m_textInfo.value(role); + painter->drawStaticText(textInfo->pos, textInfo->staticText); + } } if (!m_rating.isNull()) { @@ -425,6 +440,18 @@ painter->drawPixmap(pos, m_rating); } + if (!m_progressBar.isNull()) { + const TextInfo* sizeTextInfo = m_textInfo.value("size"); + if (sizeTextInfo) { + QPointF pos = sizeTextInfo->pos; + const Qt::Alignment align = sizeTextInfo->staticText.textOption().alignment(); + if (align & Qt::AlignHCenter) { + pos.rx() += (size().width() - m_progressBar.width()) / 2 - 2; + } + painter->drawPixmap(pos, m_progressBar); + } + } + if (clipAdditionalInfoBounds) { painter->restore(); } @@ -687,6 +714,28 @@ const QUrl itemUrl = data().value("url").toUrl(); m_isCut = clipboard->isCut(itemUrl); + if (itemUrl.isLocalFile()) { + const QString urlPath = itemUrl.toLocalFile(); + KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(urlPath); + m_isMountPoint = (mp && mp->mountPoint() == urlPath); + if (m_isMountPoint) { + if (m_spaceInfoObserver) { + m_spaceInfoObserver->setUrl(itemUrl); + } else { + m_spaceInfoObserver = new SpaceInfoObserver(itemUrl); + } + // connect(m_spaceInfoObserver, &MountPointObserver::spaceInfoChanged, this, &KStandardItemListWidget::spaceInfoChanged); + } + } else { + m_isMountPoint = false; + // if (!m_spaceInfoObserver.isNull()) { + // disconnect(m_spaceInfoObserver, &MountPointObserver::spaceInfoChanged, this, &KStandardItemListWidget::spaceInfoChanged); + // } + if (m_spaceInfoObserver) { + // m_spaceInfoObserver->deleteLater(); + } + } + // The icon-state might depend from other roles and hence is // marked as dirty whenever a role has been changed dirtyRoles.insert("iconPixmap"); @@ -1106,9 +1155,72 @@ 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(); } + + if (m_isMountPoint) { + const KItemListStyleOption& option = styleOption(); + QSizeF progressBarSize = preferredRatingSize(option); + + const qreal availableWidth = (m_layout == DetailsLayout) + ? columnWidth("size") - columnPadding(option) + : size().width(); + if (progressBarSize.width() > availableWidth) { + progressBarSize.rwidth() = availableWidth; + } + m_progressBar = QPixmap(progressBarSize.toSize()); + m_progressBar.fill(Qt::transparent); + + QPainter painter(&m_progressBar); + const QRect rect(0, m_progressBar.height() * 2/10, m_progressBar.width(), m_progressBar.height() * 8/10); + // const int rating = data().value("rating").toInt(); + // KRatingPainter::paintRating(&painter, rect, Qt::AlignJustify | Qt::AlignVCenter, rating); + // painter->setPen(..) + + + // CapacityBar test + // https://github.com/qt/qtbase/blob/dev/src/widgets/widgets/qprogressbar.cpp#L405 + // https://github.com/qt/qtbase/blob/04eba7b538072e2811f074bf66fd41f27c90b35c/src/widgets/styles/qcommonstyle.cpp#L1403 + // const quint64 totalSpace = 1238909347; + // const quint64 availableSpace = 238909347; + + quint64 totalSpace = 0; + quint64 availableSpace = 0; + if (m_spaceInfoObserver) { + totalSpace = m_spaceInfoObserver->size(); + availableSpace = m_spaceInfoObserver->available(); + } + if (totalSpace == 0 && availableSpace == 0) { + // Empty progressbar + totalSpace = 1; + availableSpace = 1; + } else { + totalSpace = totalSpace < 1 ? 1 : totalSpace; // Don't divide by 0 + availableSpace = availableSpace > totalSpace ? totalSpace : availableSpace; // Don't go over 100% + } + const quint64 usedSpace = totalSpace - availableSpace; + const qreal ratio = (qreal)usedSpace / (qreal)totalSpace; + // const qreal ratio = 0.96; + + + const QPalette pal = palette(); + + painter.fillRect(rect, QColor::fromRgb(230, 230, 230)); // Background + painter.setPen(QColor::fromRgb(208, 208, 208)); + painter.drawRect(rect); // Outline + + const QRect fillRect(rect.x(), rect.y(), rect.width() * ratio, rect.height()); + if (ratio < 0.95) { // Fill + painter.fillRect(fillRect, QColor::fromRgb(38, 160, 218)); + } else { + painter.fillRect(fillRect, QColor::fromRgb(218, 38, 38)); + } + + } else if (!m_progressBar.isNull()) { + m_progressBar = QPixmap(); + } } void KStandardItemListWidget::updateIconsLayoutTextCache() @@ -1344,6 +1456,8 @@ // 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 (role == "size" && !m_progressBar.isNull()) { + // textInfo->pos.rx() += roleWidth; } else if (isRoleRightAligned(role)) { textInfo->pos.rx() += roleWidth - requiredWidth - columnWidthInc; } diff --git a/src/kitemviews/mountpointobserver.h b/src/kitemviews/mountpointobserver.h new file mode 100644 --- /dev/null +++ b/src/kitemviews/mountpointobserver.h @@ -0,0 +1,107 @@ +/*************************************************************************** + * 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 * + ***************************************************************************/ + +#ifndef MOUNTPOINTOBSERVER_H +#define MOUNTPOINTOBSERVER_H + +#include + +#include +#include + +/** + * A MountPointObserver can be used to determine the free space on a mount + * point. It will then check the free space periodically, and emit the signal + * spaceInfoChanged() if the return value of spaceInfo() has changed. + * + * Since multiple users which watch paths on the same mount point can share + * a MountPointObserver, it is not possible to create a MountPointObserver + * manually. Instead, the function observerForPath(QString&) should be called, + * which returns either an existing or a newly created MountPointObserver for + * the mount point where the path is mounted. observerForPath(QString&) looks + * for a suitable MountPointObserver in an internal cache and creates new + * MountPointObservers and adds them to the cache if necessary. + * + * Reference counting is used to keep track of the number of users, and to + * decide when the object can be deleted. A user of this class should call + * the method ref() when it starts using it, and deref() when it does not need + * the MountPointObserver any more. + * + * The object will not be deleted immediately if the reference count reaches + * zero. The object will only be destroyed when the next periodic update of + * the free space information happens, and the reference count is still zero. + * This approach makes it possible to re-use the object if a new user requests + * the free space for the same mount point before the next update. + */ +class MountPointObserver : public QObject +{ + Q_OBJECT + + explicit MountPointObserver(const QUrl& url, QObject* parent = 0); + virtual ~MountPointObserver() {} + +public: + /** + * Call this function to indicate that the caller intends to continue using this object. An + * internal reference count is increased then. When the observer is not needed any more, + * deref() should be called, which decreases the reference count again. + */ + void ref() { ++m_referenceCount; } + + /** + * This function can be used to indicate that the caller does not need this MountPointObserver + * any more. Internally, a reference count is decreased. If the reference count is zero while + * update() is called, the object deletes itself. + */ + void deref() + { + --m_referenceCount; + Q_ASSERT(m_referenceCount >= 0); + } + + /** + * Returns a MountPointObserver for the given \a url. If the caller intends to continue using + * the returned object, it must call its ref() method. + */ + static MountPointObserver* observerForUrl(const QUrl& url); + +signals: + /** + * This signal is emitted when the size has been retrieved. + */ + void spaceInfoChanged(quint64 size, quint64 available); + +public slots: + /** + * If this slot is invoked, MountPointObserver starts a new driveSize job + * to get the drive's size. + */ + void update(); + +private slots: + void freeSpaceResult(KIO::Job* job, KIO::filesize_t size, KIO::filesize_t available); + +private: + const QUrl m_url; + int m_referenceCount; + + friend class MountPointObserverCache; +}; + +#endif diff --git a/src/kitemviews/mountpointobserver.cpp b/src/kitemviews/mountpointobserver.cpp new file mode 100644 --- /dev/null +++ b/src/kitemviews/mountpointobserver.cpp @@ -0,0 +1,55 @@ +/*************************************************************************** + * 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 + +#include "mountpointobserver.h" +#include "mountpointobservercache.h" + +MountPointObserver::MountPointObserver(const QUrl& url, QObject* parent) : + QObject(parent), + m_url(url), + m_referenceCount(0) +{ +} + +MountPointObserver* MountPointObserver::observerForUrl(const QUrl& url) +{ + MountPointObserver* observer = MountPointObserverCache::instance()->observerForUrl(url); + return observer; +} + +void MountPointObserver::update() +{ + if (m_referenceCount == 0) { + delete this; + } else { + KIO::FileSystemFreeSpaceJob* job = KIO::fileSystemFreeSpace(m_url); + connect(job, &KIO::FileSystemFreeSpaceJob::result, this, &MountPointObserver::freeSpaceResult); + } +} + +void MountPointObserver::freeSpaceResult(KIO::Job* job, KIO::filesize_t size, KIO::filesize_t available) +{ + if (!job->error()) { + emit spaceInfoChanged(size, available); + } else { + emit spaceInfoChanged(0, 0); + } +} diff --git a/src/kitemviews/mountpointobservercache.h b/src/kitemviews/mountpointobservercache.h new file mode 100644 --- /dev/null +++ b/src/kitemviews/mountpointobservercache.h @@ -0,0 +1,58 @@ +/*************************************************************************** + * 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 * + ***************************************************************************/ + +#ifndef MOUNTPOINTOBSERVERCACHE_H +#define MOUNTPOINTOBSERVERCACHE_H + +#include +#include + +class MountPointObserver; +class QTimer; + +class MountPointObserverCache : public QObject +{ + Q_OBJECT + + MountPointObserverCache(); + virtual ~MountPointObserverCache(); + +public: + static MountPointObserverCache* instance(); + + /** + * Returns a MountPointObserver for the given \a url. A new observer is created if necessary. + */ + MountPointObserver* observerForUrl(const QUrl& url); + +private slots: + /** + * Removes the given \a observer from the cache. + */ + void slotObserverDestroyed(QObject* observer); + +private: + QHash m_observerForMountPoint; + QHash m_mountPointForObserver; + QTimer* m_updateTimer; + + friend class MountPointObserverCacheSingleton; +}; + +#endif diff --git a/src/kitemviews/mountpointobservercache.cpp b/src/kitemviews/mountpointobservercache.cpp new file mode 100644 --- /dev/null +++ b/src/kitemviews/mountpointobservercache.cpp @@ -0,0 +1,103 @@ +/*************************************************************************** + * 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 "mountpointobservercache.h" + +#include "mountpointobserver.h" + +#include + +#include + +class MountPointObserverCacheSingleton +{ +public: + MountPointObserverCache instance; +}; +Q_GLOBAL_STATIC(MountPointObserverCacheSingleton, s_MountPointObserverCache) + + +MountPointObserverCache::MountPointObserverCache() : + m_observerForMountPoint(), + m_mountPointForObserver(), + m_updateTimer(0) +{ + m_updateTimer = new QTimer(this); +} + +MountPointObserverCache::~MountPointObserverCache() +{ +} + +MountPointObserverCache* MountPointObserverCache::instance() +{ + return &s_MountPointObserverCache->instance; +} + +MountPointObserver* MountPointObserverCache::observerForUrl(const QUrl& url) +{ + QUrl cachedObserverUrl; + // If the url is a local path we can extract the root dir by checking the mount points. + if (url.isLocalFile()) { + // Try to share the observer with other paths that have the same mount point. + KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(url.toLocalFile()); + if (mountPoint) { + cachedObserverUrl = QUrl::fromLocalFile(mountPoint->mountPoint()); + } else { + // Even if determining the mount point failed, the observer might still + // be able to retrieve information about the url. + cachedObserverUrl = url; + } + } else { + cachedObserverUrl = url; + } + + MountPointObserver* observer = m_observerForMountPoint.value(cachedObserverUrl); + if (!observer) { + observer = new MountPointObserver(cachedObserverUrl, this); + m_observerForMountPoint.insert(cachedObserverUrl, observer); + m_mountPointForObserver.insert(observer, cachedObserverUrl); + Q_ASSERT(m_observerForMountPoint.count() == m_mountPointForObserver.count()); + + connect(observer, &MountPointObserver::destroyed, this, &MountPointObserverCache::slotObserverDestroyed); + + if (!m_updateTimer->isActive()) { + m_updateTimer->start(10000); + } + + connect(m_updateTimer, &QTimer::timeout, observer, &MountPointObserver::update); + } + + return observer; +} + +void MountPointObserverCache::slotObserverDestroyed(QObject* observer) +{ + Q_ASSERT(m_mountPointForObserver.contains(observer)); + const QUrl& url = m_mountPointForObserver.value(observer); + Q_ASSERT(m_observerForMountPoint.contains(url)); + m_observerForMountPoint.remove(url); + m_mountPointForObserver.remove(observer); + + Q_ASSERT(m_observerForMountPoint.count() == m_mountPointForObserver.count()); + + if (m_mountPointForObserver.isEmpty()) { + m_updateTimer->stop(); + } +} diff --git a/src/kitemviews/spaceinfoobserver.h b/src/kitemviews/spaceinfoobserver.h new file mode 100644 --- /dev/null +++ b/src/kitemviews/spaceinfoobserver.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * 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 * + ***************************************************************************/ + +#ifndef SPACEINFOOBSERVER_H +#define SPACEINFOOBSERVER_H + +#include + +#include + +class QUrl; +class MountPointObserver; + +class SpaceInfoObserver : public QObject +{ + Q_OBJECT + +public: + explicit SpaceInfoObserver(const QUrl& url, QObject* parent = 0); + virtual ~SpaceInfoObserver(); + + quint64 size() const; + quint64 available() const; + + void setUrl(const QUrl& url); + +signals: + /** + * This signal is emitted when the size or available space changes. + */ + void valuesChanged(); + +private slots: + void spaceInfoChanged(quint64 size, quint64 available); + +private: + MountPointObserver* m_mountPointObserver; + + quint64 m_dataSize; + quint64 m_dataAvailable; +}; + +#endif diff --git a/src/kitemviews/spaceinfoobserver.cpp b/src/kitemviews/spaceinfoobserver.cpp new file mode 100644 --- /dev/null +++ b/src/kitemviews/spaceinfoobserver.cpp @@ -0,0 +1,86 @@ +/*************************************************************************** + * 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(0), + 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 = 0; + } +} + +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 = 0; + spaceInfoChanged(0, 0); + } + + 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(); + } +}