kdelibs/kio/kfile/kfileplacesview.cpp

1200 lines
39 KiB
C++
Raw Normal View History

2014-11-13 01:04:59 +02:00
/* This file is part of the KDE project
Copyright (C) 2007 Kevin Ottens <ervin@kde.org>
Copyright (C) 2008 Rafael Fernández López <ereslibre@kde.org>
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 <QtCore/QTimeLine>
#include <QtCore/QTimer>
#include <QtGui/QPainter>
#include <QtGui/QAbstractItemDelegate>
#include <QtGui/qevent.h>
2014-11-13 01:04:59 +02:00
#include <QtGui/QApplication>
#include <QtGui/QScrollBar>
#include <kdebug.h>
#include <kmenu.h>
#include <kcomponentdata.h>
#include <kdirnotify.h>
#include <kglobalsettings.h>
#include <kiconloader.h>
#include <klocale.h>
#include <ksettings.h>
2014-11-13 01:04:59 +02:00
#include <kmessagebox.h>
#include <knotification.h>
#include <kio/job.h>
#include <kio/jobuidelegate.h>
#include <kjob.h>
#include <kcapacitybar.h>
#include <kdiskfreespaceinfo.h>
#include <solid/storageaccess.h>
#include <solid/storagedrive.h>
#include <solid/storagevolume.h>
#include <solid/opticaldrive.h>
#include <solid/opticaldisc.h>
#include "kfileplaceeditdialog.h"
#include "kfileplacesmodel.h"
#define LATERAL_MARGIN 4
#define CAPACITYBAR_HEIGHT 6
class KFilePlacesViewDelegate : public QAbstractItemDelegate
{
Q_OBJECT
2014-11-13 01:04:59 +02:00
public:
KFilePlacesViewDelegate(KFilePlacesView *parent);
virtual ~KFilePlacesViewDelegate();
virtual QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const;
virtual void paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;
int iconSize() const;
void setIconSize(int newSize);
void addAppearingItem(const QModelIndex &index);
void setAppearingItemProgress(qreal value);
void addDisappearingItem(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;
private:
int m_iconSize;
QList<QPersistentModelIndex> m_appearingItems;
int m_appearingIconSize;
qreal m_appearingOpacity;
QList<QPersistentModelIndex> m_disappearingItems;
int m_disappearingIconSize;
qreal m_disappearingOpacity;
bool m_showHoverIndication;
QMap<QPersistentModelIndex, QTimeLine*> m_timeLineMap;
QMap<QTimeLine*, QPersistentModelIndex> m_timeLineInverseMap;
};
KFilePlacesViewDelegate::KFilePlacesViewDelegate(KFilePlacesView *parent) :
QAbstractItemDelegate(parent),
m_iconSize(48),
m_appearingIconSize(0),
m_appearingOpacity(0.0),
m_disappearingIconSize(0),
m_disappearingOpacity(0.0),
m_showHoverIndication(true)
{
}
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;
}
const KFilePlacesModel *filePlacesModel = static_cast<const KFilePlacesModel*>(index.model());
Solid::Device device = filePlacesModel->deviceForIndex(index);
return QSize(option.rect.width(), option.fontMetrics.height() / 2 + qMax(iconSize, option.fontMetrics.height()));
}
void KFilePlacesViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
painter->save();
if (m_appearingItems.contains(index)) {
painter->setOpacity(m_appearingOpacity);
} else if (m_disappearingItems.contains(index)) {
painter->setOpacity(m_disappearingOpacity);
}
QStyleOptionViewItem opt = option;
2014-11-13 01:04:59 +02:00
if (!m_showHoverIndication) {
opt.state &= ~QStyle::State_MouseOver;
}
QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter);
const KFilePlacesModel *placesModel = static_cast<const KFilePlacesModel*>(index.model());
bool isLTR = option.direction == Qt::LeftToRight;
QIcon icon = index.model()->data(index, Qt::DecorationRole).value<QIcon>();
QPixmap pm = icon.pixmap(m_iconSize, m_iconSize);
QPoint point(isLTR ? option.rect.left() + LATERAL_MARGIN
: option.rect.right() - LATERAL_MARGIN - m_iconSize, option.rect.top() + (option.rect.height() - m_iconSize) / 2);
painter->drawPixmap(point, pm);
if (option.state & QStyle::State_Selected) {
QPalette::ColorGroup cg = QPalette::Active;
if (!(option.state & QStyle::State_Enabled)) {
cg = QPalette::Disabled;
} else if (!(option.state & QStyle::State_Active)) {
cg = QPalette::Inactive;
}
painter->setPen(option.palette.color(cg, QPalette::HighlightedText));
}
QRect rectText;
const KUrl url = placesModel->url(index);
bool drawCapacityBar = false;
if (url.isLocalFile()) {
const QString mountPointPath = placesModel->url(index).toLocalFile();
const KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo(mountPointPath);
drawCapacityBar = info.size() != 0 &&
placesModel->data(index, KFilePlacesModel::CapacityBarRecommendedRole).toBool();
if (drawCapacityBar && contentsOpacity(index) > 0)
{
painter->save();
painter->setOpacity(painter->opacity() * contentsOpacity(index));
int height = option.fontMetrics.height() + CAPACITYBAR_HEIGHT;
rectText = QRect(isLTR ? m_iconSize + LATERAL_MARGIN * 2 + option.rect.left()
: 0, option.rect.top() + (option.rect.height() / 2 - height / 2), option.rect.width() - m_iconSize - LATERAL_MARGIN * 2, option.fontMetrics.height());
painter->drawText(rectText, Qt::AlignLeft | Qt::AlignTop, option.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 + option.rect.left()
: 0, option.rect.top(), option.rect.width() - m_iconSize - LATERAL_MARGIN * 2, option.rect.height());
painter->drawText(rectText, Qt::AlignLeft | Qt::AlignVCenter, option.fontMetrics.elidedText(index.model()->data(index).toString(), Qt::ElideRight, rectText.width()));
if (drawCapacityBar && contentsOpacity(index) > 0) {
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::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, 0);
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, 0);
}
qreal KFilePlacesViewDelegate::contentsOpacity(const QModelIndex &index) const
{
QTimeLine *timeLine = fadeAnimationForIndex(index);
if (timeLine) {
return timeLine->currentValue();
}
return 0;
}
class KFilePlacesView::Private
{
public:
Private(KFilePlacesView *parent) : q(parent), watcher(new KFilePlacesEventWatcher(q)) { }
enum FadeType {
FadeIn = 0,
FadeOut
};
KFilePlacesView * const q;
KUrl currentUrl;
bool autoResizeItems;
bool showAll;
bool smoothItemResizing;
bool dropOnPlace;
bool dragging;
Solid::StorageAccess *lastClickedStorage;
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);
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_trashUpdated(KJob *job);
void _k_capacityBarFadeValueChanged();
void _k_triggerDevicePolling();
QTimeLine adaptItemsTimeline;
int oldSize, endSize;
QTimeLine itemAppearTimeline;
QTimeLine itemDisappearTimeline;
KFilePlacesEventWatcher *const watcher;
KFilePlacesViewDelegate *delegate;
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 = 0;
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.setEasingCurve(QEasingCurve(QEasingCurve::InOutSine));
2014-11-13 01:04:59 +02:00
connect(&d->itemAppearTimeline, SIGNAL(valueChanged(qreal)),
this, SLOT(_k_itemAppearUpdate(qreal)));
d->itemAppearTimeline.setDuration(500);
d->itemAppearTimeline.setUpdateInterval(5);
d->itemAppearTimeline.setEasingCurve(QEasingCurve(QEasingCurve::InOutSine));
2014-11-13 01:04:59 +02:00
connect(&d->itemDisappearTimeline, SIGNAL(valueChanged(qreal)),
this, SLOT(_k_itemDisappearUpdate(qreal)));
d->itemDisappearTimeline.setDuration(500);
d->itemDisappearTimeline.setUpdateInterval(5);
d->itemDisappearTimeline.setEasingCurve(QEasingCurve(QEasingCurve::InOutSine));
2014-11-13 01:04:59 +02:00
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 KUrl &url)
{
KUrl oldUrl = d->currentUrl;
KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(model());
if (placesModel==0) 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<KFilePlacesViewDelegate*>(itemDelegate());
delegate->addDisappearingItem(current);
if (d->itemDisappearTimeline.state()!=QTimeLine::Running) {
delegate->setDisappearingItemProgress(0.0);
d->itemDisappearTimeline.start();
}
}
if (current!=index && placesModel->isHidden(index) && !d->showAll) {
KFilePlacesViewDelegate *delegate = static_cast<KFilePlacesViewDelegate*>(itemDelegate());
delegate->addAppearingItem(index);
if (d->itemAppearTimeline.state()!=QTimeLine::Running) {
delegate->setAppearingItemProgress(0.0);
d->itemAppearTimeline.start();
}
setRowHidden(index.row(), false);
}
d->currentUrl = url;
selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
} else {
d->currentUrl = KUrl();
selectionModel()->clear();
}
if (!current.isValid()) {
d->updateHiddenRows();
}
}
void KFilePlacesView::setShowAll(bool showAll)
{
KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(model());
if (placesModel==0) return;
d->showAll = showAll;
KFilePlacesViewDelegate *delegate = static_cast<KFilePlacesViewDelegate*>(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);
}
}
if (d->itemAppearTimeline.state()!=QTimeLine::Running) {
delegate->setAppearingItemProgress(0.0);
d->itemAppearTimeline.start();
}
} else {
for (int i=0; i<rowCount; ++i) {
QModelIndex index = placesModel->index(i, 0);
if (index!=current && placesModel->isHidden(index)) {
delegate->addDisappearingItem(index);
}
}
if (d->itemDisappearTimeline.state()!=QTimeLine::Running) {
delegate->setDisappearingItemProgress(0.0);
d->itemDisappearTimeline.start();
}
}
}
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<KFilePlacesModel*>(model());
KFilePlacesViewDelegate *delegate = qobject_cast<KFilePlacesViewDelegate*>(itemDelegate());
2014-11-13 01:04:59 +02:00
if (placesModel==0) return;
QModelIndex index = indexAt(event->pos());
QString label = placesModel->text(index).replace('&',"&&");
KMenu menu;
QAction *edit = 0;
QAction *hide = 0;
QAction *emptyTrash = 0;
QAction *eject = 0;
QAction *teardown = 0;
QAction *add = 0;
QAction *mainSeparator = 0;
if (index.isValid()) {
if (!placesModel->isDevice(index)) {
if (placesModel->url(index) == KUrl("trash:/")) {
emptyTrash = menu.addAction(KIcon("trash-empty"), i18nc("@action:inmenu", "Empty Trash"));
KSettings trashConfig("trashrc", KSettings::SimpleConfig);
emptyTrash->setEnabled(!trashConfig.boolean("Status/Empty", true));
2014-11-13 01:04:59 +02:00
menu.addSeparator();
}
add = menu.addAction(KIcon("document-new"), i18n("Add Entry..."));
mainSeparator = menu.addSeparator();
edit = menu.addAction(KIcon("document-properties"), i18n("&Edit Entry '%1'...", label));
} else {
eject = placesModel->ejectActionForIndex(index);
if (eject!=0) {
eject->setParent(&menu);
menu.addAction(eject);
}
teardown = placesModel->teardownActionForIndex(index);
if (teardown!=0) {
teardown->setParent(&menu);
menu.addAction(teardown);
}
if (teardown!=0 || eject!=0) {
mainSeparator = menu.addSeparator();
}
}
if (add == 0) {
add = menu.addAction(KIcon("document-new"), i18n("Add Entry..."));
}
hide = menu.addAction(i18n("&Hide Entry '%1'", label));
hide->setCheckable(true);
hide->setChecked(placesModel->isHidden(index));
} else {
add = menu.addAction(KIcon("document-new"), i18n("Add Entry..."));
}
QAction *showAll = 0;
if (placesModel->hiddenCount()>0) {
showAll = new QAction(i18n("&Show All Entries"), &menu);
showAll->setCheckable(true);
showAll->setChecked(d->showAll);
if (mainSeparator == 0) {
mainSeparator = menu.addSeparator();
}
menu.insertAction(mainSeparator, showAll);
}
QAction* remove = 0;
if (index.isValid() && !placesModel->isDevice(index)) {
remove = menu.addAction( KIcon("edit-delete"), i18n("&Remove Entry '%1'", label));
}
menu.addActions(actions());
if (menu.isEmpty()) {
return;
}
QAction *result = menu.exec(event->globalPos());
if (emptyTrash != 0 && result == emptyTrash) {
const QString text = i18nc("@info", "Do you really want to empty the Trash? All items will be deleted.");
const bool del = KMessageBox::warningContinueCancel(window(),
text,
QString(),
KGuiItem(i18nc("@action:button", "Empty Trash"),
KIcon("user-trash"))
) == KMessageBox::Continue;
if (del) {
QByteArray packedArgs;
QDataStream stream(&packedArgs, QIODevice::WriteOnly);
stream << int(1);
KIO::Job *job = KIO::special(KUrl("trash:/"), packedArgs);
KNotification::event("kde/TrashEmptied");
2014-11-13 01:04:59 +02:00
job->ui()->setWindow(parentWidget());
connect(job, SIGNAL(result(KJob*)), SLOT(_k_trashUpdated(KJob*)));
}
} else if (edit != 0 && result == edit) {
KBookmark bookmark = placesModel->bookmarkForIndex(index);
KUrl url = bookmark.url();
QString label = bookmark.text();
QString iconName = bookmark.icon();
bool appLocal = !bookmark.metaDataItem("OnlyInApp").isEmpty();
if (KFilePlaceEditDialog::getInformation(true, url, label,
iconName, false, appLocal, 64, this))
{
QString appName;
if (appLocal) appName = KGlobal::mainComponent().componentName();
placesModel->editPlace(index, label, url, iconName, appName);
}
} else if (remove != 0 && result == remove) {
placesModel->removePlace(index);
} else if (hide != 0 && result == hide) {
placesModel->setPlaceHidden(index, hide->isChecked());
QModelIndex current = placesModel->closestItem(d->currentUrl);
if (index!=current && !d->showAll && hide->isChecked()) {
delegate->addDisappearingItem(index);
if (d->itemDisappearTimeline.state()!=QTimeLine::Running) {
delegate->setDisappearingItemProgress(0.0);
d->itemDisappearTimeline.start();
}
}
} else if (showAll != 0 && result == showAll) {
setShowAll(showAll->isChecked());
} else if (teardown != 0 && result == teardown) {
placesModel->requestTeardown(index);
} else if (eject != 0 && result == eject) {
placesModel->requestEject(index);
} else if (add != 0 && result == add) {
KUrl url = d->currentUrl;
QString label;
QString iconName = "folder";
bool appLocal = true;
if (KFilePlaceEditDialog::getInformation(true, url, label,
iconName, true, appLocal, 64, this))
{
QString appName;
if (appLocal) appName = KGlobal::mainComponent().componentName();
placesModel->addPlace(label, url, iconName, appName, 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 = qobject_cast<KFilePlacesViewDelegate*>(itemDelegate());
2014-11-13 01:04:59 +02:00
delegate->setShowHoverIndication(false);
d->dropRect = QRect();
}
void KFilePlacesView::dragLeaveEvent(QDragLeaveEvent *event)
{
QListView::dragLeaveEvent(event);
d->dragging = false;
KFilePlacesViewDelegate *delegate = qobject_cast<KFilePlacesViewDelegate*>(itemDelegate());
2014-11-13 01:04:59 +02:00
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<KFilePlacesModel*>(model());
Q_ASSERT(placesModel != 0);
emit urlsDropped(placesModel->url(index), event, this);
event->acceptProposedAction();
}
}
QListView::dropEvent(event);
d->dragging = false;
KFilePlacesViewDelegate *delegate = qobject_cast<KFilePlacesViewDelegate*>(itemDelegate());
2014-11-13 01:04:59 +02:00
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;
2014-11-13 01:04:59 +02:00
opt.initFrom(this);
opt.rect = itemRect;
opt.state = QStyle::State_Enabled | QStyle::State_MouseOver;
style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, &painter, this);
}
}
}
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(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
d->watcher, SLOT(currentIndexChanged(QModelIndex)));
}
void KFilePlacesView::rowsInserted(const QModelIndex &parent, int start, int end)
{
QListView::rowsInserted(parent, start, end);
setUrl(d->currentUrl);
KFilePlacesViewDelegate *delegate = qobject_cast<KFilePlacesViewDelegate*>(itemDelegate());
2014-11-13 01:04:59 +02:00
KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(model());
for (int i=start; i<=end; ++i) {
QModelIndex index = placesModel->index(i, 0, parent);
if (d->showAll || !placesModel->isHidden(index)) {
delegate->addAppearingItem(index);
} else {
setRowHidden(i, true);
}
}
if (d->itemAppearTimeline.state()!=QTimeLine::Running) {
delegate->setAppearingItemProgress(0.0);
d->itemAppearTimeline.start();
}
d->adaptItemSize();
}
QSize KFilePlacesView::sizeHint() const
{
KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(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::setCurrentIndex(const QModelIndex &index)
{
KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(q->model());
if (placesModel==0) return;
KUrl url = placesModel->url(index);
if (url.isValid()) {
currentUrl = url;
updateHiddenRows();
emit q->urlChanged(url);
if (showAll) {
q->setShowAll(false);
}
} else {
q->setUrl(currentUrl);
}
}
void KFilePlacesView::Private::adaptItemSize()
{
KFilePlacesViewDelegate *delegate = qobject_cast<KFilePlacesViewDelegate*>(q->itemDelegate());
2014-11-13 01:04:59 +02:00
if (!delegate) return;
if (!autoResizeItems) {
int size = q->iconSize().width(); // Assume width == height
delegate->setIconSize(size);
q->scheduleDelayedItemsLayout();
return;
}
KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(q->model());
if (placesModel==0) 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, 0, q) + 1;
const int maxWidth = q->viewport()->width() - textWidth - 4 * margin - 1;
const int maxHeight = ((q->height() - (fm.height() / 2) * rowCount) / 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<KFilePlacesModel*>(q->model());
if (placesModel==0) 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();
}
void KFilePlacesView::Private::_k_placeClicked(const QModelIndex &index)
{
KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(q->model());
if (placesModel==0) 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<KFilePlacesModel*>(q->model());
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 = qobject_cast<KFilePlacesViewDelegate*>(q->itemDelegate());
2014-11-13 01:04:59 +02:00
delegate->setIconSize(size);
q->scheduleDelayedItemsLayout();
}
void KFilePlacesView::Private::_k_itemAppearUpdate(qreal value)
{
KFilePlacesViewDelegate *delegate = qobject_cast<KFilePlacesViewDelegate*>(q->itemDelegate());
2014-11-13 01:04:59 +02:00
delegate->setAppearingItemProgress(value);
q->scheduleDelayedItemsLayout();
}
void KFilePlacesView::Private::_k_itemDisappearUpdate(qreal value)
{
KFilePlacesViewDelegate *delegate = qobject_cast<KFilePlacesViewDelegate*>(q->itemDelegate());
2014-11-13 01:04:59 +02:00
delegate->setDisappearingItemProgress(value);
if (value>=1.0) {
updateHiddenRows();
}
q->scheduleDelayedItemsLayout();
}
void KFilePlacesView::Private::_k_enableSmoothItemResizing()
{
smoothItemResizing = true;
}
void KFilePlacesView::Private::_k_trashUpdated(KJob *job)
{
if (job->error()) {
static_cast<KIO::Job*>(job)->ui()->showErrorMessage();
}
org::kde::KDirNotify::emitFilesAdded("trash:/");
}
void KFilePlacesView::Private::_k_capacityBarFadeValueChanged()
{
const QModelIndex index = delegate->indexForFadeAnimation(static_cast<QTimeLine*>(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<const KFilePlacesModel*>(hoveredIndex.model());
if (placesModel->isDevice(hoveredIndex)) {
q->update(hoveredIndex);
}
}
const QModelIndex focusedIndex = watcher->focusedIndex();
if (focusedIndex.isValid() && focusedIndex != hoveredIndex) {
const KFilePlacesModel *placesModel = static_cast<const KFilePlacesModel*>(focusedIndex.model());
if (placesModel->isDevice(focusedIndex)) {
q->update(focusedIndex);
}
}
}
void KFilePlacesView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
QListView::dataChanged(topLeft, bottomRight);
d->adaptItemSize();
}
#include "kfileplacesview.moc"
#include "moc_kfileplacesview.cpp"
#include "moc_kfileplacesview_p.cpp"