diff options
| author | Peter Penz <[email protected]> | 2010-07-24 21:45:49 +0000 |
|---|---|---|
| committer | Peter Penz <[email protected]> | 2010-07-24 21:45:49 +0000 |
| commit | 652d08c9242ed51d86dba3b2afda9d3b2e9a9cd7 (patch) | |
| tree | f65face40917d355fa74f593e04df9865f5a1552 /src/views | |
| parent | 935cc8a03bcb6088ff520d7161db14b9b6a29044 (diff) | |
Sourcecode hierarchy cleanup: Create folder "views" and move view related sources to it
svn path=/trunk/KDE/kdebase/apps/; revision=1154146
Diffstat (limited to 'src/views')
30 files changed, 8658 insertions, 0 deletions
diff --git a/src/views/dolphincategorydrawer.cpp b/src/views/dolphincategorydrawer.cpp new file mode 100644 index 000000000..59743b7f5 --- /dev/null +++ b/src/views/dolphincategorydrawer.cpp @@ -0,0 +1,378 @@ +/* + * This file is part of the KDE project + * Copyright (C) 2007 Rafael Fernández López <[email protected]> + * + * This 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; either + * version 2 of the License, or (at your option) any later version. + * + * 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 "dolphincategorydrawer.h" + +#include <config-nepomuk.h> + +#include <QPainter> +#include <QFile> +#include <QDir> +#include <QApplication> +#include <QStyleOption> + +#ifdef HAVE_NEPOMUK +#include <nepomuk/kratingpainter.h> +#endif + +#include <kiconloader.h> +#include <kiconeffect.h> +#include <kcategorizedsortfilterproxymodel.h> +#include <qimageblitz.h> +#include <kuser.h> +#include <kcategorizedview.h> + +#include "dolphinview.h" +#include "dolphinmodel.h" + +#define HORIZONTAL_HINT 3 + +DolphinCategoryDrawer::DolphinCategoryDrawer(KCategorizedView *view) + : KCategoryDrawerV3(view) + , hotSpotPressed(NoneHotSpot) + , selectAll(KIconLoader::global()->loadIcon("list-add", KIconLoader::Desktop, 16)) + , selectAllHovered(KIconLoader::global()->iconEffect()->apply(selectAll, KIconLoader::Desktop, KIconLoader::ActiveState)) + , selectAllDisabled(KIconLoader::global()->iconEffect()->apply(selectAll, KIconLoader::Desktop, KIconLoader::DisabledState)) + , unselectAll(KIconLoader::global()->loadIcon("list-remove", KIconLoader::Desktop, 16)) + , unselectAllHovered(KIconLoader::global()->iconEffect()->apply(unselectAll, KIconLoader::Desktop, KIconLoader::ActiveState)) + , unselectAllDisabled(KIconLoader::global()->iconEffect()->apply(unselectAll, KIconLoader::Desktop, KIconLoader::DisabledState)) +{ +} + +DolphinCategoryDrawer::~DolphinCategoryDrawer() +{ +} + +bool DolphinCategoryDrawer::allCategorySelected(const QString &category) const +{ + const QModelIndexList list = view()->block(category); + foreach (const QModelIndex &index, list) { + if (!view()->selectionModel()->isSelected(index)) { + return false; + } + } + return true; +} + +bool DolphinCategoryDrawer::someCategorySelected(const QString &category) const +{ + const QModelIndexList list = view()->block(category); + foreach (const QModelIndex &index, list) { + if (view()->selectionModel()->isSelected(index)) { + return true; + } + } + return false; +} + +void DolphinCategoryDrawer::drawCategory(const QModelIndex &index, int sortRole, + const QStyleOption &option, QPainter *painter) const +{ + Q_UNUSED(sortRole); + painter->setRenderHint(QPainter::Antialiasing); + + if (!index.isValid()) { + return; + } + + const QString category = index.model()->data(index, KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); + const QRect optRect = option.rect; + QFont font(QApplication::font()); + font.setBold(true); + const QFontMetrics fontMetrics = QFontMetrics(font); + + QColor outlineColor = option.palette.text().color(); + outlineColor.setAlphaF(0.35); + + //BEGIN: top left corner + { + painter->save(); + painter->setPen(outlineColor); + const QPointF topLeft(optRect.topLeft()); + QRectF arc(topLeft, QSizeF(4, 4)); + arc.translate(0.5, 0.5); + painter->drawArc(arc, 1440, 1440); + painter->restore(); + } + //END: top left corner + + //BEGIN: left vertical line + { + QPoint start(optRect.topLeft()); + start.ry() += 3; + QPoint verticalGradBottom(optRect.topLeft()); + verticalGradBottom.ry() += fontMetrics.height() + 5; + QLinearGradient gradient(start, verticalGradBottom); + gradient.setColorAt(0, outlineColor); + gradient.setColorAt(1, Qt::transparent); + painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); + } + //END: left vertical line + + //BEGIN: horizontal line + { + QPoint start(optRect.topLeft()); + start.rx() += 3; + QPoint horizontalGradTop(optRect.topLeft()); + horizontalGradTop.rx() += optRect.width() - 6; + painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor); + } + //END: horizontal line + + //BEGIN: top right corner + { + painter->save(); + painter->setPen(outlineColor); + QPointF topRight(optRect.topRight()); + topRight.rx() -= 4; + QRectF arc(topRight, QSizeF(4, 4)); + arc.translate(0.5, 0.5); + painter->drawArc(arc, 0, 1440); + painter->restore(); + } + //END: top right corner + + //BEGIN: right vertical line + { + QPoint start(optRect.topRight()); + start.ry() += 3; + QPoint verticalGradBottom(optRect.topRight()); + verticalGradBottom.ry() += fontMetrics.height() + 5; + QLinearGradient gradient(start, verticalGradBottom); + gradient.setColorAt(0, outlineColor); + gradient.setColorAt(1, Qt::transparent); + painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); + } + //END: right vertical line + + const int iconSize = KIconLoader::global()->currentSize(KIconLoader::Small); + + //BEGIN: select/unselect all + { + if (this->category == category) { + QRect iconAllRect(option.rect); + iconAllRect.setTop(iconAllRect.top() + 4); + iconAllRect.setLeft(iconAllRect.right() - 16 - 7); + iconAllRect.setSize(QSize(iconSize, iconSize)); + if (!allCategorySelected(category)) { + if (iconAllRect.contains(pos)) { + painter->drawPixmap(iconAllRect, selectAllHovered); + } else { + painter->drawPixmap(iconAllRect, selectAll); + } + } else { + painter->drawPixmap(iconAllRect, selectAllDisabled); + } + QRect iconNoneRect(option.rect); + iconNoneRect.setTop(iconNoneRect.top() + 4); + iconNoneRect.setLeft(iconNoneRect.right() - 16 * 2 - 7 * 2); + iconNoneRect.setSize(QSize(iconSize, iconSize)); + if (someCategorySelected(category)) { + if (iconNoneRect.contains(pos)) { + painter->drawPixmap(iconNoneRect, unselectAllHovered); + } else { + painter->drawPixmap(iconNoneRect, unselectAll); + } + } else { + painter->drawPixmap(iconNoneRect, unselectAllDisabled); + } + } + } + //END: select/unselect all + + //BEGIN: category information + { + bool paintIcon; + QPixmap icon; + switch (index.column()) { + case KDirModel::Owner: { + paintIcon = true; + KUser user(category); + const QString faceIconPath = user.faceIconPath(); + if (faceIconPath.isEmpty()) { + icon = KIconLoader::global()->loadIcon("user-identity", KIconLoader::NoGroup, iconSize); + } else { + icon = QPixmap::fromImage(QImage(faceIconPath).scaledToHeight(iconSize, Qt::SmoothTransformation)); + } + } + break; + case KDirModel::Type: { + paintIcon = true; + const KCategorizedSortFilterProxyModel *proxyModel = static_cast<const KCategorizedSortFilterProxyModel*>(index.model()); + const DolphinModel *model = static_cast<const DolphinModel*>(proxyModel->sourceModel()); + KFileItem item = model->itemForIndex(proxyModel->mapToSource(index)); + // This is the only way of getting the icon right. Others will fail on corner + // cases like the item representing this group has been set a different icon, + // so the group icon drawn is that one particularly. This way assures the drawn + // icon is the one of the mimetype of the group itself. (ereslibre) + icon = KIconLoader::global()->loadMimeTypeIcon(item.mimeTypePtr()->iconName(), KIconLoader::NoGroup, iconSize); + } + break; + default: + paintIcon = false; + } + + if (paintIcon) { + QRect iconRect(option.rect); + iconRect.setTop(iconRect.top() + 4); + iconRect.setLeft(iconRect.left() + 7); + iconRect.setSize(QSize(iconSize, iconSize)); + + painter->drawPixmap(iconRect, icon); + } + + //BEGIN: text + { + QRect textRect(option.rect); + textRect.setTop(textRect.top() + 7); + textRect.setLeft(textRect.left() + 7 + (paintIcon ? (iconSize + 6) : 0)); + textRect.setHeight(qMax(fontMetrics.height(), iconSize)); + textRect.setRight(textRect.right() - 7); + textRect.setBottom(textRect.bottom() - 5); // only one pixel separation here (no gradient) + + painter->save(); + painter->setFont(font); + QColor penColor(option.palette.text().color()); + penColor.setAlphaF(0.6); + painter->setPen(penColor); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, category); + painter->restore(); + } + //END: text + } + //BEGIN: category information +} + +int DolphinCategoryDrawer::categoryHeight(const QModelIndex &index, const QStyleOption &) const +{ + int iconSize = KIconLoader::global()->currentSize(KIconLoader::Small); + QFont font(QApplication::font()); + font.setBold(true); + const QFontMetrics fontMetrics = QFontMetrics(font); + int heightWithoutIcon = fontMetrics.height() + (iconSize / 4) * 2 + 1; /* 1 pixel-width gradient */ + bool paintIcon; + + switch (index.column()) { + case KDirModel::Owner: + case KDirModel::Type: + paintIcon = true; + break; + default: + paintIcon = false; + } + + if (paintIcon) { + return qMax(heightWithoutIcon + 5, iconSize + 1 /* 1 pixel-width gradient */ + + 5 /* top and bottom separation */); + } + + return heightWithoutIcon + 5; +} + +void DolphinCategoryDrawer::mouseButtonPressed(const QModelIndex &index, const QRect &blockRect, QMouseEvent *event) +{ + if (!index.isValid()) { + event->ignore(); + return; + } + const QString category = index.model()->data(index, KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); + int iconSize = KIconLoader::global()->currentSize(KIconLoader::Small); + if (this->category == category) { + QRect iconAllRect(blockRect); + iconAllRect.setTop(iconAllRect.top() + 4); + iconAllRect.setLeft(iconAllRect.right() - 16 - 7); + iconAllRect.setSize(QSize(iconSize, iconSize)); + if (iconAllRect.contains(pos)) { + event->accept(); + hotSpotPressed = SelectAllHotSpot; + categoryPressed = index; + return; + } + QRect iconNoneRect(blockRect); + iconNoneRect.setTop(iconNoneRect.top() + 4); + iconNoneRect.setLeft(iconNoneRect.right() - 16 * 2 - 7 * 2); + iconNoneRect.setSize(QSize(iconSize, iconSize)); + if (iconNoneRect.contains(pos)) { + event->accept(); + hotSpotPressed = UnselectAllHotSpot; + categoryPressed = index; + return; + } + } + event->ignore(); +} + +void DolphinCategoryDrawer::mouseButtonReleased(const QModelIndex &index, const QRect &blockRect, QMouseEvent *event) +{ + if (!index.isValid() || hotSpotPressed == NoneHotSpot || categoryPressed != index) { + event->ignore(); + return; + } + categoryPressed = QModelIndex(); + const QString category = index.model()->data(index, KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); + int iconSize = KIconLoader::global()->currentSize(KIconLoader::Small); + if (this->category == category) { + QRect iconAllRect(blockRect); + iconAllRect.setTop(iconAllRect.top() + 4); + iconAllRect.setLeft(iconAllRect.right() - 16 - 7); + iconAllRect.setSize(QSize(iconSize, iconSize)); + if (iconAllRect.contains(pos)) { + if (hotSpotPressed == SelectAllHotSpot) { + event->accept(); + emit actionRequested(SelectAll, index); + } else { + event->ignore(); + hotSpotPressed = NoneHotSpot; + } + return; + } + QRect iconNoneRect(blockRect); + iconNoneRect.setTop(iconNoneRect.top() + 4); + iconNoneRect.setLeft(iconNoneRect.right() - 16 * 2 - 7 * 2); + iconNoneRect.setSize(QSize(iconSize, iconSize)); + if (iconNoneRect.contains(pos)) { + if (hotSpotPressed == UnselectAllHotSpot) { + event->accept(); + emit actionRequested(UnselectAll, index); + } else { + event->ignore(); + hotSpotPressed = NoneHotSpot; + } + return; + } + } + event->ignore(); +} + +void DolphinCategoryDrawer::mouseMoved(const QModelIndex &index, const QRect &, QMouseEvent *event) +{ + event->ignore(); + if (!index.isValid()) { + return; + } + pos = event->pos(); + category = index.model()->data(index, KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); +} + +void DolphinCategoryDrawer::mouseLeft(const QModelIndex &, const QRect &) +{ + pos = QPoint(); + category = QString(); +} diff --git a/src/views/dolphincategorydrawer.h b/src/views/dolphincategorydrawer.h new file mode 100644 index 000000000..d9849727e --- /dev/null +++ b/src/views/dolphincategorydrawer.h @@ -0,0 +1,85 @@ +/* This file is part of the KDE project + * Copyright (C) 2007 Rafael Fernández López <[email protected]> + * + * This 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; either + * version 2 of the License, or (at your option) any later version. + * + * 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 DOLPHINCATEGORYDRAWER_H +#define DOLPHINCATEGORYDRAWER_H + +#include <kcategorydrawer.h> + +#include <QStyleOption> +#include <QModelIndex> + +#include <libdolphin_export.h> + +class LIBDOLPHINPRIVATE_EXPORT DolphinCategoryDrawer + : public KCategoryDrawerV3 +{ +public: + using KCategoryDrawerV2::mouseButtonPressed; + using KCategoryDrawerV2::mouseButtonReleased; + + enum Action { + SelectAll = 0, + UnselectAll + }; + + DolphinCategoryDrawer(KCategorizedView *view); + + virtual ~DolphinCategoryDrawer(); + + bool allCategorySelected(const QString &category) const; + + bool someCategorySelected(const QString &category) const; + + virtual void drawCategory(const QModelIndex &index, int sortRole, + const QStyleOption &option, QPainter *painter) const; + + virtual int categoryHeight(const QModelIndex &index, const QStyleOption &option) const; + +protected: + virtual void mouseButtonPressed(const QModelIndex &index, const QRect &blockRect, QMouseEvent *event); + + virtual void mouseButtonReleased(const QModelIndex &index, const QRect &blockRect, QMouseEvent *event); + + virtual void mouseMoved(const QModelIndex &index, const QRect &blockRect, QMouseEvent *event); + + virtual void mouseLeft(const QModelIndex &index,const QRect &blockRect); + +private: + enum HotSpot { + NoneHotSpot = 0, + SelectAllHotSpot, + UnselectAllHotSpot + }; + + HotSpot hotSpotPressed; + QModelIndex categoryPressed; + + QPixmap selectAll; + QPixmap selectAllHovered; + QPixmap selectAllDisabled; + QPixmap unselectAll; + QPixmap unselectAllHovered; + QPixmap unselectAllDisabled; + + QPoint pos; + QString category; +}; + +#endif // DOLPHINCATEGORYDRAWER_H diff --git a/src/views/dolphincolumnview.cpp b/src/views/dolphincolumnview.cpp new file mode 100644 index 000000000..7e2b522b4 --- /dev/null +++ b/src/views/dolphincolumnview.cpp @@ -0,0 +1,466 @@ +/*************************************************************************** + * Copyright (C) 2007-2009 by Peter Penz <[email protected]> * + * * + * 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 "dolphincolumnview.h" + +#include "dolphinmodel.h" +#include "dolphincolumnviewcontainer.h" +#include "dolphinviewcontroller.h" +#include "dolphindirlister.h" +#include "dolphinsortfilterproxymodel.h" +#include "settings/dolphinsettings.h" +#include "dolphinviewautoscroller.h" +#include "dolphin_columnmodesettings.h" +#include "dolphin_generalsettings.h" +#include "draganddrophelper.h" +#include "folderexpander.h" +#include "tooltips/tooltipmanager.h" +#include "viewextensionsfactory.h" +#include "viewmodecontroller.h" +#include "zoomlevelinfo.h" + +#include <kcolorscheme.h> +#include <kdirlister.h> +#include <kfileitem.h> +#include <kio/previewjob.h> +#include <kiconeffect.h> +#include <kjob.h> +#include <konqmimedata.h> + +#include <QApplication> +#include <QClipboard> +#include <QPainter> +#include <QPoint> +#include <QScrollBar> + +DolphinColumnView::DolphinColumnView(QWidget* parent, + DolphinColumnViewContainer* container, + const KUrl& url) : + QListView(parent), + m_active(false), + m_container(container), + m_extensionsFactory(0), + m_url(url), + m_childUrl(), + m_font(), + m_decorationSize(), + m_dirLister(0), + m_dolphinModel(0), + m_proxyModel(0), + m_dropRect() +{ + setMouseTracking(true); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + setSelectionBehavior(SelectItems); + setSelectionMode(QAbstractItemView::ExtendedSelection); + setDragDropMode(QAbstractItemView::DragDrop); + setDropIndicatorShown(false); + setSelectionRectVisible(true); + setEditTriggers(QAbstractItemView::NoEditTriggers); + + setVerticalScrollMode(QListView::ScrollPerPixel); + setHorizontalScrollMode(QListView::ScrollPerPixel); + + // apply the column mode settings to the widget + const ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings(); + Q_ASSERT(settings != 0); + + if (settings->useSystemFont()) { + m_font = KGlobalSettings::generalFont(); + } else { + m_font = QFont(settings->fontFamily(), + qRound(settings->fontSize()), + settings->fontWeight(), + settings->italicFont()); + m_font.setPointSizeF(settings->fontSize()); + } + + connect(this, SIGNAL(viewportEntered()), + m_container->m_dolphinViewController, SLOT(emitViewportEntered())); + connect(this, SIGNAL(entered(const QModelIndex&)), + this, SLOT(slotEntered(const QModelIndex&))); + + const DolphinView* dolphinView = m_container->m_dolphinViewController->view(); + connect(dolphinView, SIGNAL(showPreviewChanged()), + this, SLOT(slotShowPreviewChanged())); + + m_dirLister = new DolphinDirLister(); + m_dirLister->setAutoUpdate(true); + m_dirLister->setMainWindow(window()); + m_dirLister->setDelayedMimeTypes(true); + const bool showHiddenFiles = m_container->m_dolphinViewController->view()->showHiddenFiles(); + m_dirLister->setShowingDotFiles(showHiddenFiles); + + m_dolphinModel = new DolphinModel(this); + m_dolphinModel->setDirLister(m_dirLister); + m_dolphinModel->setDropsAllowed(DolphinModel::DropOnDirectory); + + m_proxyModel = new DolphinSortFilterProxyModel(this); + m_proxyModel->setSourceModel(m_dolphinModel); + m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + + m_proxyModel->setSorting(dolphinView->sorting()); + m_proxyModel->setSortOrder(dolphinView->sortOrder()); + m_proxyModel->setSortFoldersFirst(dolphinView->sortFoldersFirst()); + + setModel(m_proxyModel); + + connect(KGlobalSettings::self(), SIGNAL(kdisplayFontChanged()), + this, SLOT(updateFont())); + + const ViewModeController* viewModeController = m_container->m_viewModeController; + connect(viewModeController, SIGNAL(zoomLevelChanged(int)), + this, SLOT(setZoomLevel(int))); + const QString nameFilter = viewModeController->nameFilter(); + if (!nameFilter.isEmpty()) { + m_proxyModel->setFilterFixedString(nameFilter); + } + + updateDecorationSize(dolphinView->showPreview()); + updateBackground(); + + DolphinViewController* dolphinViewController = m_container->m_dolphinViewController; + m_extensionsFactory = new ViewExtensionsFactory(this, dolphinViewController, viewModeController); + + m_dirLister->openUrl(url, KDirLister::NoFlags); +} + +DolphinColumnView::~DolphinColumnView() +{ + delete m_proxyModel; + m_proxyModel = 0; + delete m_dolphinModel; + m_dolphinModel = 0; + m_dirLister = 0; // deleted by m_dolphinModel +} + +void DolphinColumnView::setActive(bool active) +{ + if (m_active != active) { + m_active = active; + + if (active) { + activate(); + } else { + deactivate(); + } + } +} + +void DolphinColumnView::updateBackground() +{ + // TODO: The alpha-value 150 is copied from DolphinView::setActive(). When + // cleaning up the cut-indication of DolphinColumnView with the code from + // DolphinView a common helper-class should be available which can be shared + // by all view implementations -> no hardcoded value anymore + const QPalette::ColorRole role = viewport()->backgroundRole(); + QColor color = viewport()->palette().color(role); + color.setAlpha((m_active && m_container->m_active) ? 255 : 150); + + QPalette palette = viewport()->palette(); + palette.setColor(role, color); + viewport()->setPalette(palette); + + update(); +} + +KFileItem DolphinColumnView::itemAt(const QPoint& pos) const +{ + KFileItem item; + const QModelIndex index = indexAt(pos); + if (index.isValid() && (index.column() == DolphinModel::Name)) { + const QModelIndex dolphinModelIndex = m_proxyModel->mapToSource(index); + item = m_dolphinModel->itemForIndex(dolphinModelIndex); + } + return item; +} + +void DolphinColumnView::setSelectionModel(QItemSelectionModel* model) +{ + // If a change of the selection is done although the view is not active + // (e. g. by the selection markers), the column must be activated. This + // is done by listening to the current selectionChanged() signal. + if (selectionModel() != 0) { + disconnect(selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), + this, SLOT(requestActivation())); + } + + QListView::setSelectionModel(model); + + connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), + this, SLOT(requestActivation())); +} + +QStyleOptionViewItem DolphinColumnView::viewOptions() const +{ + QStyleOptionViewItem viewOptions = QListView::viewOptions(); + viewOptions.font = m_font; + viewOptions.fontMetrics = QFontMetrics(m_font); + viewOptions.decorationSize = m_decorationSize; + viewOptions.showDecorationSelected = true; + return viewOptions; +} + +void DolphinColumnView::startDrag(Qt::DropActions supportedActions) +{ + DragAndDropHelper::instance().startDrag(this, supportedActions, m_container->m_dolphinViewController); +} + +void DolphinColumnView::dragEnterEvent(QDragEnterEvent* event) +{ + if (DragAndDropHelper::instance().isMimeDataSupported(event->mimeData())) { + event->acceptProposedAction(); + requestActivation(); + } +} + +void DolphinColumnView::dragLeaveEvent(QDragLeaveEvent* event) +{ + QListView::dragLeaveEvent(event); + setDirtyRegion(m_dropRect); +} + +void DolphinColumnView::dragMoveEvent(QDragMoveEvent* event) +{ + QListView::dragMoveEvent(event); + + // TODO: remove this code when the issue #160611 is solved in Qt 4.4 + const QModelIndex index = indexAt(event->pos()); + setDirtyRegion(m_dropRect); + + m_dropRect.setSize(QSize()); // set as invalid + if (index.isValid()) { + m_container->m_dolphinViewController->setItemView(this); + const KFileItem item = m_container->m_dolphinViewController->itemForIndex(index); + if (!item.isNull() && item.isDir()) { + m_dropRect = visualRect(index); + } + } + setDirtyRegion(m_dropRect); + + if (DragAndDropHelper::instance().isMimeDataSupported(event->mimeData())) { + // accept url drops, independently from the destination item + event->acceptProposedAction(); + } +} + +void DolphinColumnView::dropEvent(QDropEvent* event) +{ + const QModelIndex index = indexAt(event->pos()); + m_container->m_dolphinViewController->setItemView(this); + const KFileItem item = m_container->m_dolphinViewController->itemForIndex(index); + m_container->m_dolphinViewController->indicateDroppedUrls(item, url(), event); + QListView::dropEvent(event); +} + +void DolphinColumnView::paintEvent(QPaintEvent* event) +{ + if (!m_childUrl.isEmpty()) { + // indicate the shown URL of the next column by highlighting the shown folder item + const QModelIndex dirIndex = m_dolphinModel->indexForUrl(m_childUrl); + const QModelIndex proxyIndex = m_proxyModel->mapFromSource(dirIndex); + if (proxyIndex.isValid() && !selectionModel()->isSelected(proxyIndex)) { + const QRect itemRect = visualRect(proxyIndex); + QPainter painter(viewport()); + QColor color = KColorScheme(QPalette::Active, KColorScheme::View).foreground().color(); + color.setAlpha(32); + painter.setPen(Qt::NoPen); + painter.setBrush(color); + painter.drawRect(itemRect); + } + } + + QListView::paintEvent(event); +} + +void DolphinColumnView::mousePressEvent(QMouseEvent* event) +{ + requestActivation(); + if (!indexAt(event->pos()).isValid()) { + if (QApplication::mouseButtons() & Qt::MidButton) { + m_container->m_dolphinViewController->replaceUrlByClipboard(); + } + } else if (event->button() == Qt::LeftButton) { + // TODO: see comment in DolphinIconsView::mousePressEvent() + setState(QAbstractItemView::DraggingState); + } + QListView::mousePressEvent(event); +} + +void DolphinColumnView::keyPressEvent(QKeyEvent* event) +{ + QListView::keyPressEvent(event); + + DolphinViewController* controller = m_container->m_dolphinViewController; + controller->handleKeyPressEvent(event); + switch (event->key()) { + case Qt::Key_Right: { + // Special key handling for the column: A Key_Right should + // open a new column for the currently selected folder. + const QModelIndex index = currentIndex(); + const KFileItem item = controller->itemForIndex(index); + if (!item.isNull() && item.isDir()) { + controller->emitItemTriggered(item); + } + break; + } + + case Qt::Key_Escape: + selectionModel()->setCurrentIndex(selectionModel()->currentIndex(), + QItemSelectionModel::Current | + QItemSelectionModel::Clear); + break; + + default: + break; + } +} + +void DolphinColumnView::contextMenuEvent(QContextMenuEvent* event) +{ + requestActivation(); + QListView::contextMenuEvent(event); + m_container->m_dolphinViewController->triggerContextMenuRequest(event->pos()); +} + +void DolphinColumnView::wheelEvent(QWheelEvent* event) +{ + const int step = m_decorationSize.height(); + verticalScrollBar()->setSingleStep(step); + QListView::wheelEvent(event); +} + +void DolphinColumnView::leaveEvent(QEvent* event) +{ + QListView::leaveEvent(event); + // if the mouse is above an item and moved very fast outside the widget, + // no viewportEntered() signal might be emitted although the mouse has been moved + // above the viewport + m_container->m_dolphinViewController->emitViewportEntered(); +} + +void DolphinColumnView::currentChanged(const QModelIndex& current, const QModelIndex& previous) +{ + QListView::currentChanged(current, previous); + m_extensionsFactory->handleCurrentIndexChange(current, previous); +} + +void DolphinColumnView::setZoomLevel(int level) +{ + const int size = ZoomLevelInfo::iconSizeForZoomLevel(level); + ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings(); + + const bool showPreview = m_container->m_dolphinViewController->view()->showPreview(); + if (showPreview) { + settings->setPreviewSize(size); + } else { + settings->setIconSize(size); + } + + updateDecorationSize(showPreview); +} + +void DolphinColumnView::slotEntered(const QModelIndex& index) +{ + m_container->m_dolphinViewController->setItemView(this); + m_container->m_dolphinViewController->emitItemEntered(index); +} + +void DolphinColumnView::requestActivation() +{ + m_container->m_dolphinViewController->requestActivation(); + if (!m_active) { + m_container->requestActivation(this); + selectionModel()->clear(); + } +} + +void DolphinColumnView::updateFont() +{ + const ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings(); + Q_ASSERT(settings != 0); + + if (settings->useSystemFont()) { + m_font = KGlobalSettings::generalFont(); + } +} + +void DolphinColumnView::slotShowPreviewChanged() +{ + const DolphinView* view = m_container->m_dolphinViewController->view(); + updateDecorationSize(view->showPreview()); +} + +void DolphinColumnView::activate() +{ + setFocus(Qt::OtherFocusReason); + + if (KGlobalSettings::singleClick()) { + connect(this, SIGNAL(clicked(const QModelIndex&)), + m_container->m_dolphinViewController, SLOT(triggerItem(const QModelIndex&))); + } else { + connect(this, SIGNAL(doubleClicked(const QModelIndex&)), + m_container->m_dolphinViewController, SLOT(triggerItem(const QModelIndex&))); + } + + if (selectionModel() && selectionModel()->currentIndex().isValid()) { + selectionModel()->setCurrentIndex(selectionModel()->currentIndex(), QItemSelectionModel::SelectCurrent); + } + + updateBackground(); +} + +void DolphinColumnView::deactivate() +{ + clearFocus(); + if (KGlobalSettings::singleClick()) { + disconnect(this, SIGNAL(clicked(const QModelIndex&)), + m_container->m_dolphinViewController, SLOT(triggerItem(const QModelIndex&))); + } else { + disconnect(this, SIGNAL(doubleClicked(const QModelIndex&)), + m_container->m_dolphinViewController, SLOT(triggerItem(const QModelIndex&))); + } + + // It is important to disconnect the connection to requestActivation() temporary, otherwise the internal + // clearing of the selection would result in activating the column again. + disconnect(selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), + this, SLOT(requestActivation())); + const QModelIndex current = selectionModel()->currentIndex(); + selectionModel()->clear(); + selectionModel()->setCurrentIndex(current, QItemSelectionModel::NoUpdate); + connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), + this, SLOT(requestActivation())); + + updateBackground(); +} + +void DolphinColumnView::updateDecorationSize(bool showPreview) +{ + ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings(); + const int iconSize = showPreview ? settings->previewSize() : settings->iconSize(); + const QSize size(iconSize, iconSize); + setIconSize(size); + + m_decorationSize = size; + + doItemsLayout(); +} + +#include "dolphincolumnview.moc" diff --git a/src/views/dolphincolumnview.h b/src/views/dolphincolumnview.h new file mode 100644 index 000000000..64feac4f9 --- /dev/null +++ b/src/views/dolphincolumnview.h @@ -0,0 +1,170 @@ +/*************************************************************************** + * Copyright (C) 2007-2009 by Peter Penz <[email protected]> * + * * + * 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 DOLPHINCOLUMNVIEW_H +#define DOLPHINCOLUMNVIEW_H + +#include "dolphinview.h" + +#include <QFont> +#include <QListView> +#include <QSize> +#include <QStyleOption> + +#include <kurl.h> + +class DolphinColumnViewContainer; +class DolphinModel; +class DolphinSortFilterProxyModel; +class DolphinDirLister; +class KFileItem; +class SelectionManager; +class ViewExtensionsFactory; + +/** + * Represents one column inside the DolphinColumnViewContainer. + */ +class DolphinColumnView : public QListView +{ + Q_OBJECT + +public: + DolphinColumnView(QWidget* parent, + DolphinColumnViewContainer* container, + const KUrl& url); + virtual ~DolphinColumnView(); + + /** + * An active column is defined as column, which shows the same URL + * as indicated by the URL navigator. The active column is usually + * drawn in a lighter color. All operations are applied to this column. + */ + void setActive(bool active); + bool isActive() const; + + /** + * Sets the directory URL of the child column that is shown next to + * this column. This property is only used for a visual indication + * of the shown directory, it does not trigger a loading of the model. + */ + void setChildUrl(const KUrl& url); + KUrl childUrl() const; + + /** Sets the directory URL that is shown inside the column widget. */ + void setUrl(const KUrl& url); + + /** Returns the directory URL that is shown inside the column widget. */ + KUrl url() const; + + /** + * Updates the background color dependent from the activation state + * \a isViewActive of the column view. + */ + void updateBackground(); + + /** + * Returns the item on the position \a pos. The KFileItem instance + * is null if no item is below the position. + */ + KFileItem itemAt(const QPoint& pos) const; + + virtual void setSelectionModel(QItemSelectionModel* model); + +protected: + virtual QStyleOptionViewItem viewOptions() const; + virtual void startDrag(Qt::DropActions supportedActions); + virtual void dragEnterEvent(QDragEnterEvent* event); + virtual void dragLeaveEvent(QDragLeaveEvent* event); + virtual void dragMoveEvent(QDragMoveEvent* event); + virtual void dropEvent(QDropEvent* event); + virtual void paintEvent(QPaintEvent* event); + virtual void mousePressEvent(QMouseEvent* event); + virtual void keyPressEvent(QKeyEvent* event); + virtual void contextMenuEvent(QContextMenuEvent* event); + virtual void wheelEvent(QWheelEvent* event); + virtual void leaveEvent(QEvent* event); + virtual void currentChanged(const QModelIndex& current, const QModelIndex& previous); + +private slots: + void setZoomLevel(int level); + + void slotEntered(const QModelIndex& index); + void requestActivation(); + void updateFont(); + + void slotShowPreviewChanged(); + +private: + /** Used by DolphinColumnView::setActive(). */ + void activate(); + + /** Used by DolphinColumnView::setActive(). */ + void deactivate(); + + void updateDecorationSize(bool showPreview); + +private: + bool m_active; + DolphinColumnViewContainer* m_container; + SelectionManager* m_selectionManager; + ViewExtensionsFactory* m_extensionsFactory; + KUrl m_url; // URL of the directory that is shown + KUrl m_childUrl; // URL of the next column that is shown + + QFont m_font; + QSize m_decorationSize; + + DolphinDirLister* m_dirLister; + DolphinModel* m_dolphinModel; + DolphinSortFilterProxyModel* m_proxyModel; + + QRect m_dropRect; + + friend class DolphinColumnViewContainer; +}; + +inline bool DolphinColumnView::isActive() const +{ + return m_active; +} + +inline void DolphinColumnView::setChildUrl(const KUrl& url) +{ + m_childUrl = url; +} + +inline KUrl DolphinColumnView::childUrl() const +{ + return m_childUrl; +} + +inline void DolphinColumnView::setUrl(const KUrl& url) +{ + if (url != m_url) { + m_url = url; + //reload(); + } +} + +inline KUrl DolphinColumnView::url() const +{ + return m_url; +} + +#endif diff --git a/src/views/dolphincolumnviewcontainer.cpp b/src/views/dolphincolumnviewcontainer.cpp new file mode 100644 index 000000000..344d38d8a --- /dev/null +++ b/src/views/dolphincolumnviewcontainer.cpp @@ -0,0 +1,416 @@ +/*************************************************************************** + * Copyright (C) 2007-2009 by Peter Penz <[email protected]> * + * * + * 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 "dolphincolumnviewcontainer.h" + +#include "dolphin_columnmodesettings.h" + +#include "dolphincolumnview.h" +#include "dolphinviewcontroller.h" +#include "dolphinsortfilterproxymodel.h" +#include "draganddrophelper.h" +#include "settings/dolphinsettings.h" +#include "viewmodecontroller.h" + +#include <QPoint> +#include <QScrollBar> +#include <QTimeLine> +#include <QTimer> + +DolphinColumnViewContainer::DolphinColumnViewContainer(QWidget* parent, + DolphinViewController* dolphinViewController, + const ViewModeController* viewModeController) : + QScrollArea(parent), + m_dolphinViewController(dolphinViewController), + m_viewModeController(viewModeController), + m_active(false), + m_index(-1), + m_contentX(0), + m_columns(), + m_emptyViewport(0), + m_animation(0), + m_dragSource(0), + m_activeUrlTimer(0) +{ + Q_ASSERT(dolphinViewController != 0); + Q_ASSERT(viewModeController != 0); + + setAcceptDrops(true); + setFocusPolicy(Qt::NoFocus); + setFrameShape(QFrame::NoFrame); + setLayoutDirection(Qt::LeftToRight); + + connect(viewModeController, SIGNAL(activationChanged(bool)), + this, SLOT(updateColumnsBackground(bool))); + + connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), + this, SLOT(moveContentHorizontally(int))); + + m_animation = new QTimeLine(500, this); + connect(m_animation, SIGNAL(frameChanged(int)), horizontalScrollBar(), SLOT(setValue(int))); + + m_activeUrlTimer = new QTimer(this); + m_activeUrlTimer->setSingleShot(true); + m_activeUrlTimer->setInterval(200); + connect(m_activeUrlTimer, SIGNAL(timeout()), + this, SLOT(updateActiveUrl())); + + DolphinColumnView* column = new DolphinColumnView(viewport(), this, viewModeController->url()); + m_columns.append(column); + requestActivation(column); + + m_emptyViewport = new QFrame(viewport()); + m_emptyViewport->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); + + updateColumnsBackground(true); + +} + +DolphinColumnViewContainer::~DolphinColumnViewContainer() +{ + delete m_dragSource; + m_dragSource = 0; +} + +KUrl DolphinColumnViewContainer::rootUrl() const +{ + return m_columns[0]->url(); +} + +QAbstractItemView* DolphinColumnViewContainer::activeColumn() const +{ + return m_columns[m_index]; +} + +void DolphinColumnViewContainer::showColumn(const KUrl& url) +{ + if (!rootUrl().isParentOf(url)) { + removeAllColumns(); + m_columns[0]->setUrl(url); + return; + } + + int columnIndex = 0; + foreach (DolphinColumnView* column, m_columns) { + if (column->url() == url) { + // the column represents already the requested URL, hence activate it + requestActivation(column); + layoutColumns(); + return; + } else if (!column->url().isParentOf(url)) { + // the column is no parent of the requested URL, hence + // just delete all remaining columns + if (columnIndex > 0) { + QList<DolphinColumnView*>::iterator start = m_columns.begin() + columnIndex; + QList<DolphinColumnView*>::iterator end = m_columns.end(); + for (QList<DolphinColumnView*>::iterator it = start; it != end; ++it) { + deleteColumn(*it); + } + m_columns.erase(start, end); + + const int maxIndex = m_columns.count() - 1; + Q_ASSERT(maxIndex >= 0); + if (m_index > maxIndex) { + m_index = maxIndex; + } + break; + } + } + ++columnIndex; + } + + // Create missing columns. Assuming that the path is "/home/peter/Temp/" and + // the target path is "/home/peter/Temp/a/b/c/", then the columns "a", "b" and + // "c" will be created. + const int lastIndex = m_columns.count() - 1; + Q_ASSERT(lastIndex >= 0); + + const KUrl& activeUrl = m_columns[lastIndex]->url(); + Q_ASSERT(activeUrl.isParentOf(url)); + Q_ASSERT(activeUrl != url); + + QString path = activeUrl.url(KUrl::AddTrailingSlash); + const QString targetPath = url.url(KUrl::AddTrailingSlash); + + columnIndex = lastIndex; + int slashIndex = path.count('/'); + bool hasSubPath = (slashIndex >= 0); + while (hasSubPath) { + const QString subPath = targetPath.section('/', slashIndex, slashIndex); + if (subPath.isEmpty()) { + hasSubPath = false; + } else { + path += subPath + '/'; + ++slashIndex; + + const KUrl childUrl = KUrl(path); + m_columns[columnIndex]->setChildUrl(childUrl); + columnIndex++; + + DolphinColumnView* column = new DolphinColumnView(viewport(), this, childUrl); + m_columns.append(column); + + // Before invoking layoutColumns() the column must be set visible temporary. + // To prevent a flickering the initial geometry is set to a hidden position. + column->setGeometry(QRect(-1, -1, 1, 1)); + column->show(); + layoutColumns(); + updateScrollBar(); + } + } + + requestActivation(m_columns[columnIndex]); +} + +void DolphinColumnViewContainer::mousePressEvent(QMouseEvent* event) +{ + m_dolphinViewController->requestActivation(); + QScrollArea::mousePressEvent(event); +} + +void DolphinColumnViewContainer::keyPressEvent(QKeyEvent* event) +{ + if (event->key() == Qt::Key_Left) { + if (m_index > 0) { + requestActivation(m_columns[m_index - 1]); + } + } else { + QScrollArea::keyPressEvent(event); + } +} + +void DolphinColumnViewContainer::resizeEvent(QResizeEvent* event) +{ + QScrollArea::resizeEvent(event); + layoutColumns(); + updateScrollBar(); + assureVisibleActiveColumn(); +} + +void DolphinColumnViewContainer::wheelEvent(QWheelEvent* event) +{ + // let Ctrl+wheel events propagate to the DolphinView for icon zooming + if ((event->modifiers() & Qt::ControlModifier) == Qt::ControlModifier) { + event->ignore(); + } else { + QScrollArea::wheelEvent(event); + } +} + +void DolphinColumnViewContainer::moveContentHorizontally(int x) +{ + m_contentX = isRightToLeft() ? +x : -x; + layoutColumns(); +} + +void DolphinColumnViewContainer::updateColumnsBackground(bool active) +{ + if (active == m_active) { + return; + } + + m_active = active; + + // dim the background of the viewport + const QPalette::ColorRole role = viewport()->backgroundRole(); + QColor background = viewport()->palette().color(role); + background.setAlpha(0); // make background transparent + + QPalette palette = viewport()->palette(); + palette.setColor(role, background); + viewport()->setPalette(palette); + + foreach (DolphinColumnView* column, m_columns) { + column->updateBackground(); + } +} + +void DolphinColumnViewContainer::updateActiveUrl() +{ + const KUrl activeUrl = m_columns[m_index]->url(); + m_dolphinViewController->requestUrlChange(activeUrl); +} + +void DolphinColumnViewContainer::layoutColumns() +{ + const int gap = 4; + + ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings(); + const int columnWidth = settings->columnWidth(); + + QRect emptyViewportRect; + if (isRightToLeft()) { + int x = viewport()->width() - columnWidth + m_contentX; + foreach (DolphinColumnView* column, m_columns) { + column->setGeometry(QRect(x, 0, columnWidth - gap, viewport()->height())); + x -= columnWidth; + } + emptyViewportRect = QRect(0, 0, x + columnWidth - gap, viewport()->height()); + } else { + int x = m_contentX; + foreach (DolphinColumnView* column, m_columns) { + column->setGeometry(QRect(x, 0, columnWidth - gap, viewport()->height())); + x += columnWidth; + } + emptyViewportRect = QRect(x, 0, viewport()->width() - x - gap, viewport()->height()); + } + + if (emptyViewportRect.isValid()) { + m_emptyViewport->show(); + m_emptyViewport->setGeometry(emptyViewportRect); + } else { + m_emptyViewport->hide(); + } +} + +void DolphinColumnViewContainer::updateScrollBar() +{ + ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings(); + const int contentWidth = m_columns.count() * settings->columnWidth(); + + horizontalScrollBar()->setPageStep(contentWidth); + horizontalScrollBar()->setRange(0, contentWidth - viewport()->width()); +} + +void DolphinColumnViewContainer::assureVisibleActiveColumn() +{ + const int viewportWidth = viewport()->width(); + const int x = activeColumn()->x(); + + ColumnModeSettings* settings = DolphinSettings::instance().columnModeSettings(); + const int width = settings->columnWidth(); + + if (x + width > viewportWidth) { + const int newContentX = m_contentX - x - width + viewportWidth; + if (isRightToLeft()) { + m_animation->setFrameRange(m_contentX, newContentX); + } else { + m_animation->setFrameRange(-m_contentX, -newContentX); + } + if (m_animation->state() != QTimeLine::Running) { + m_animation->start(); + } + } else if (x < 0) { + const int newContentX = m_contentX - x; + if (isRightToLeft()) { + m_animation->setFrameRange(m_contentX, newContentX); + } else { + m_animation->setFrameRange(-m_contentX, -newContentX); + } + if (m_animation->state() != QTimeLine::Running) { + m_animation->start(); + } + } +} + +void DolphinColumnViewContainer::requestActivation(DolphinColumnView* column) +{ + if (m_dolphinViewController->itemView() != column) { + m_dolphinViewController->setItemView(column); + } + if (focusProxy() != column) { + setFocusProxy(column); + } + + if (column->isActive()) { + assureVisibleActiveColumn(); + } else { + // Deactivate the currently active column + if (m_index >= 0) { + m_columns[m_index]->setActive(false); + } + + // Get the index of the column that should get activated + int index = 0; + foreach (DolphinColumnView* currColumn, m_columns) { + if (currColumn == column) { + break; + } + ++index; + } + + Q_ASSERT(index != m_index); + Q_ASSERT(index < m_columns.count()); + + // Activate the requested column + m_index = index; + m_columns[m_index]->setActive(true); + + assureVisibleActiveColumn(); + m_activeUrlTimer->start(); // calls slot updateActiveUrl() + } +} + +void DolphinColumnViewContainer::removeAllColumns() +{ + QList<DolphinColumnView*>::iterator start = m_columns.begin() + 1; + QList<DolphinColumnView*>::iterator end = m_columns.end(); + for (QList<DolphinColumnView*>::iterator it = start; it != end; ++it) { + deleteColumn(*it); + } + m_columns.erase(start, end); + m_index = 0; + m_columns[0]->setActive(true); + assureVisibleActiveColumn(); +} + +QPoint DolphinColumnViewContainer::columnPosition(DolphinColumnView* column, const QPoint& point) const +{ + const QPoint topLeft = column->frameGeometry().topLeft(); + return QPoint(point.x() - topLeft.x(), point.y() - topLeft.y()); +} + +void DolphinColumnViewContainer::deleteColumn(DolphinColumnView* column) +{ + if (column == 0) { + return; + } + + if (m_dolphinViewController->itemView() == column) { + m_dolphinViewController->setItemView(0); + } + // deleteWhenNotDragSource(column) does not necessarily delete column, + // and we want its preview generator destroyed immediately. + column->hide(); + // Prevent automatic destruction of column when this DolphinColumnViewContainer + // is destroyed. + column->setParent(0); + column->disconnect(); + + if (DragAndDropHelper::instance().isDragSource(column)) { + // The column is a drag source (the feature "Open folders + // during drag operations" is used). Deleting the view + // during an ongoing drag operation is not allowed, so + // this will postponed. + if (m_dragSource != 0) { + // the old stored view is obviously not the drag source anymore + m_dragSource->deleteLater(); + m_dragSource = 0; + } + column->hide(); + column->setParent(0); + column->disconnect(); + + m_dragSource = column; + } else { + column->deleteLater(); + } +} + +#include "dolphincolumnviewcontainer.moc" diff --git a/src/views/dolphincolumnviewcontainer.h b/src/views/dolphincolumnviewcontainer.h new file mode 100644 index 000000000..c67fb3cff --- /dev/null +++ b/src/views/dolphincolumnviewcontainer.h @@ -0,0 +1,147 @@ +/*************************************************************************** + * Copyright (C) 2007-2009 by Peter Penz <[email protected]> * + * * + * 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 DOLPHINCOLUMNVIEWCONTAINER_H +#define DOLPHINCOLUMNVIEWCONTAINER_H + +#include "dolphinview.h" + +#include <kurl.h> + +#include <QList> +#include <QScrollArea> +#include <QString> + +class DolphinColumnView; +class DolphinViewController; +class QFrame; +class QTimeLine; +class QTimer; + +/** + * @brief Represents a container for columns represented as instances + * of DolphinColumnView. + * + * @see DolphinColumnView + */ +class DolphinColumnViewContainer : public QScrollArea +{ + Q_OBJECT + +public: + /** + * @param parent Parent widget. + * @param dolphinViewController Allows the DolphinColumnView to control the + * DolphinView in a limited way. + * @param viewModeController Controller that is used by the DolphinView + * to control the DolphinColumnView. The DolphinColumnView + * only has read access to the controller. + * @param model Directory that is shown. + */ + explicit DolphinColumnViewContainer(QWidget* parent, + DolphinViewController* dolphinViewController, + const ViewModeController* viewModeController); + virtual ~DolphinColumnViewContainer(); + + KUrl rootUrl() const; + + QAbstractItemView* activeColumn() const; + + /** + * Shows the column which represents the URL \a url. If the column + * is already shown, it gets activated, otherwise it will be created. + */ + void showColumn(const KUrl& url); + +protected: + virtual void mousePressEvent(QMouseEvent* event); + virtual void keyPressEvent(QKeyEvent* event); + virtual void resizeEvent(QResizeEvent* event); + virtual void wheelEvent(QWheelEvent* event); + +private slots: + /** + * Moves the content of the columns view to represent + * the scrollbar position \a x. + */ + void moveContentHorizontally(int x); + + /** + * Updates the background color of the columns to respect + * the current activation state \a active. + */ + void updateColumnsBackground(bool active); + + /** + * Tells the Dolphin controller to update the active URL + * to m_activeUrl. The slot is called asynchronously with a + * small delay, as this prevents a flickering when a directory + * from an inactive column gets selected. + */ + void updateActiveUrl(); + +private: + void layoutColumns(); + void updateScrollBar(); + + /** + * Assures that the currently active column is fully visible + * by adjusting the horizontal position of the content. + */ + void assureVisibleActiveColumn(); + + /** + * Request the activation for the column \a column. It is assured + * that the columns gets fully visible by adjusting the horizontal + * position of the content. + */ + void requestActivation(DolphinColumnView* column); + + /** Removes all columns except of the root column. */ + void removeAllColumns(); + + /** + * Returns the position of the point \a point relative to the column + * \a column. + */ + QPoint columnPosition(DolphinColumnView* column, const QPoint& point) const; + + /** + * Deletes the column. If the itemview of the controller is set to the column, + * the controllers itemview is set to 0. + */ + void deleteColumn(DolphinColumnView* column); + +private: + DolphinViewController* m_dolphinViewController; + const ViewModeController* m_viewModeController; + bool m_active; + int m_index; + int m_contentX; + QList<DolphinColumnView*> m_columns; + QFrame* m_emptyViewport; + QTimeLine* m_animation; + QAbstractItemView* m_dragSource; + + QTimer* m_activeUrlTimer; + + friend class DolphinColumnView; +}; + +#endif diff --git a/src/views/dolphindetailsview.cpp b/src/views/dolphindetailsview.cpp new file mode 100644 index 000000000..961bd7872 --- /dev/null +++ b/src/views/dolphindetailsview.cpp @@ -0,0 +1,1113 @@ +/*************************************************************************** + * Copyright (C) 2006 by Peter Penz ([email protected]) * + * Copyright (C) 2008 by Simon St. James ([email protected]) * + * * + * 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 "dolphindetailsview.h" + +#include "additionalinfoaccessor.h" +#include "dolphinmodel.h" +#include "dolphinviewcontroller.h" +#include "dolphinfileitemdelegate.h" +#include "settings/dolphinsettings.h" +#include "dolphinsortfilterproxymodel.h" +#include "dolphinviewautoscroller.h" +#include "draganddrophelper.h" +#include "viewextensionsfactory.h" +#include "viewmodecontroller.h" +#include "viewproperties.h" +#include "zoomlevelinfo.h" + +#include "dolphin_detailsmodesettings.h" +#include "dolphin_generalsettings.h" + +#include <kdirmodel.h> +#include <klocale.h> +#include <kmenu.h> + +#include <QAction> +#include <QApplication> +#include <QHeaderView> +#include <QRubberBand> +#include <QPainter> +#include <QScrollBar> + +DolphinDetailsView::DolphinDetailsView(QWidget* parent, + DolphinViewController* dolphinViewController, + const ViewModeController* viewModeController, + DolphinSortFilterProxyModel* proxyModel) : + QTreeView(parent), + m_autoResize(true), + m_expandingTogglePressed(false), + m_keyPressed(false), + m_useDefaultIndexAt(true), + m_ignoreScrollTo(false), + m_dolphinViewController(dolphinViewController), + m_viewModeController(viewModeController), + m_extensionsFactory(0), + m_expandableFoldersAction(0), + m_expandedUrls(), + m_font(), + m_decorationSize(), + m_band() +{ + const DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings(); + Q_ASSERT(settings != 0); + Q_ASSERT(dolphinViewController != 0); + Q_ASSERT(viewModeController != 0); + + setLayoutDirection(Qt::LeftToRight); + setAcceptDrops(true); + setSortingEnabled(true); + setUniformRowHeights(true); + setSelectionBehavior(SelectItems); + setDragDropMode(QAbstractItemView::DragDrop); + setDropIndicatorShown(false); + setAlternatingRowColors(true); + setRootIsDecorated(settings->expandableFolders()); + setItemsExpandable(settings->expandableFolders()); + setEditTriggers(QAbstractItemView::NoEditTriggers); + setModel(proxyModel); + + setMouseTracking(true); + + const ViewProperties props(viewModeController->url()); + setSortIndicatorSection(props.sorting()); + setSortIndicatorOrder(props.sortOrder()); + + QHeaderView* headerView = header(); + connect(headerView, SIGNAL(sectionClicked(int)), + this, SLOT(synchronizeSortingState(int))); + headerView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(headerView, SIGNAL(customContextMenuRequested(const QPoint&)), + this, SLOT(configureSettings(const QPoint&))); + connect(headerView, SIGNAL(sectionResized(int, int, int)), + this, SLOT(slotHeaderSectionResized(int, int, int))); + connect(headerView, SIGNAL(sectionHandleDoubleClicked(int)), + this, SLOT(disableAutoResizing())); + + connect(parent, SIGNAL(sortingChanged(DolphinView::Sorting)), + this, SLOT(setSortIndicatorSection(DolphinView::Sorting))); + connect(parent, SIGNAL(sortOrderChanged(Qt::SortOrder)), + this, SLOT(setSortIndicatorOrder(Qt::SortOrder))); + + connect(this, SIGNAL(clicked(const QModelIndex&)), + dolphinViewController, SLOT(requestTab(const QModelIndex&))); + if (KGlobalSettings::singleClick()) { + connect(this, SIGNAL(clicked(const QModelIndex&)), + dolphinViewController, SLOT(triggerItem(const QModelIndex&))); + } else { + connect(this, SIGNAL(doubleClicked(const QModelIndex&)), + dolphinViewController, SLOT(triggerItem(const QModelIndex&))); + } + + connect(this, SIGNAL(entered(const QModelIndex&)), + this, SLOT(slotEntered(const QModelIndex&))); + connect(this, SIGNAL(viewportEntered()), + dolphinViewController, SLOT(emitViewportEntered())); + connect(viewModeController, SIGNAL(zoomLevelChanged(int)), + this, SLOT(setZoomLevel(int))); + connect(dolphinViewController->view(), SIGNAL(additionalInfoChanged()), + this, SLOT(updateColumnVisibility())); + connect(viewModeController, SIGNAL(activationChanged(bool)), + this, SLOT(slotActivationChanged(bool))); + + if (settings->useSystemFont()) { + m_font = KGlobalSettings::generalFont(); + } else { + m_font = QFont(settings->fontFamily(), + qRound(settings->fontSize()), + settings->fontWeight(), + settings->italicFont()); + m_font.setPointSizeF(settings->fontSize()); + } + + setVerticalScrollMode(QTreeView::ScrollPerPixel); + setHorizontalScrollMode(QTreeView::ScrollPerPixel); + + const DolphinView* view = dolphinViewController->view(); + connect(view, SIGNAL(showPreviewChanged()), + this, SLOT(slotShowPreviewChanged())); + + + setFocus(); + viewport()->installEventFilter(this); + + connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)), + this, SLOT(slotGlobalSettingsChanged(int))); + + m_useDefaultIndexAt = false; + + m_expandableFoldersAction = new QAction(i18nc("@option:check", "Expandable Folders"), this); + m_expandableFoldersAction->setCheckable(true); + connect(m_expandableFoldersAction, SIGNAL(toggled(bool)), + this, SLOT(setFoldersExpandable(bool))); + + connect(this, SIGNAL(expanded(const QModelIndex&)), this, SLOT(slotExpanded(const QModelIndex&))); + connect(this, SIGNAL(collapsed(const QModelIndex&)), this, SLOT(slotCollapsed(const QModelIndex&))); + + updateDecorationSize(view->showPreview()); + + m_extensionsFactory = new ViewExtensionsFactory(this, dolphinViewController, viewModeController); + m_extensionsFactory->fileItemDelegate()->setMinimizedNameColumn(true); + m_extensionsFactory->setAutoFolderExpandingEnabled(settings->expandableFolders()); +} + +DolphinDetailsView::~DolphinDetailsView() +{ +} + +QSet<KUrl> DolphinDetailsView::expandedUrls() const +{ + return m_expandedUrls; +} + +QRegion DolphinDetailsView::visualRegionForSelection(const QItemSelection& selection) const +{ + // We have to make sure that the visualRect of each model index is inside the region. + // QTreeView::visualRegionForSelection does not do it right because it assumes implicitly + // that all visualRects have the same width, which is in general not the case here. + QRegion selectionRegion; + const QModelIndexList indexes = selection.indexes(); + + foreach(const QModelIndex& index, indexes) { + selectionRegion += visualRect(index); + } + + return selectionRegion; +} + +bool DolphinDetailsView::event(QEvent* event) +{ + switch (event->type()) { + case QEvent::Polish: + header()->setResizeMode(QHeaderView::Interactive); + updateColumnVisibility(); + break; + + case QEvent::FocusOut: + // If a key-press triggers an action that e. g. opens a dialog, the + // widget gets no key-release event. Assure that the pressed state + // is reset to prevent accidently setting the current index during a selection. + m_keyPressed = false; + break; + + default: + break; + } + + return QTreeView::event(event); +} + +QStyleOptionViewItem DolphinDetailsView::viewOptions() const +{ + QStyleOptionViewItem viewOptions = QTreeView::viewOptions(); + viewOptions.font = m_font; + viewOptions.fontMetrics = QFontMetrics(m_font); + viewOptions.showDecorationSelected = true; + viewOptions.decorationSize = m_decorationSize; + return viewOptions; +} + +void DolphinDetailsView::contextMenuEvent(QContextMenuEvent* event) +{ + QTreeView::contextMenuEvent(event); + + DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings(); + m_expandableFoldersAction->setChecked(settings->expandableFolders()); + m_dolphinViewController->triggerContextMenuRequest(event->pos(), + QList<QAction*>() << m_expandableFoldersAction); +} + +void DolphinDetailsView::mousePressEvent(QMouseEvent* event) +{ + m_dolphinViewController->requestActivation(); + + const QModelIndex current = currentIndex(); + QTreeView::mousePressEvent(event); + + m_expandingTogglePressed = isAboveExpandingToggle(event->pos()); + + const QModelIndex index = indexAt(event->pos()); + const bool updateState = index.isValid() && + (index.column() == DolphinModel::Name) && + (event->button() == Qt::LeftButton); + if (updateState) { + setState(QAbstractItemView::DraggingState); + } + + if (!index.isValid() || (index.column() != DolphinModel::Name)) { + // the mouse press is done somewhere outside the filename column + if (QApplication::mouseButtons() & Qt::MidButton) { + m_dolphinViewController->replaceUrlByClipboard(); + } + + const Qt::KeyboardModifiers mod = QApplication::keyboardModifiers(); + if (!m_expandingTogglePressed && !(mod & Qt::ShiftModifier) && !(mod & Qt::ControlModifier)) { + clearSelection(); + } + + // restore the current index, other columns are handled as viewport area. + // setCurrentIndex(...) implicitly calls scrollTo(...), which we want to ignore. + m_ignoreScrollTo = true; + selectionModel()->setCurrentIndex(current, QItemSelectionModel::Current); + m_ignoreScrollTo = false; + + if ((event->button() == Qt::LeftButton) && !m_expandingTogglePressed) { + // Inform Qt about what we are doing - otherwise it starts dragging items around! + setState(DragSelectingState); + m_band.show = true; + // Incremental update data will not be useful - start from scratch. + m_band.ignoreOldInfo = true; + const QPoint scrollPos(horizontalScrollBar()->value(), verticalScrollBar()->value()); + m_band.origin = event->pos() + scrollPos; + m_band.destination = m_band.origin; + m_band.originalSelection = selectionModel()->selection(); + } + } +} + +void DolphinDetailsView::mouseMoveEvent(QMouseEvent* event) +{ + if (m_expandingTogglePressed) { + // Per default QTreeView starts either a selection or a drag operation when dragging + // the expanding toggle button (Qt-issue - see TODO comment in DolphinIconsView::mousePressEvent()). + // Turn off this behavior in Dolphin to stay predictable: + setState(QAbstractItemView::NoState); + return; + } + + if (m_band.show) { + const QPoint mousePos = event->pos(); + const QModelIndex index = indexAt(mousePos); + if (!index.isValid()) { + // the destination of the selection rectangle is above the viewport. In this + // case QTreeView does no selection at all, which is not the wanted behavior + // in Dolphin -> select all items within the elastic band rectangle + updateElasticBandSelection(); + } + + // TODO: enable QTreeView::mouseMoveEvent(event) again, as soon + // as the Qt-issue #199631 has been fixed. + // QTreeView::mouseMoveEvent(event); + QAbstractItemView::mouseMoveEvent(event); + updateElasticBand(); + } else { + // TODO: enable QTreeView::mouseMoveEvent(event) again, as soon + // as the Qt-issue #199631 has been fixed. + // QTreeView::mouseMoveEvent(event); + QAbstractItemView::mouseMoveEvent(event); + } +} + +void DolphinDetailsView::mouseReleaseEvent(QMouseEvent* event) +{ + if (!m_expandingTogglePressed) { + const QModelIndex index = indexAt(event->pos()); + if (index.isValid() && (index.column() == DolphinModel::Name)) { + QTreeView::mouseReleaseEvent(event); + } else { + // don't change the current index if the cursor is released + // above any other column than the name column, as the other + // columns act as viewport + const QModelIndex current = currentIndex(); + QTreeView::mouseReleaseEvent(event); + selectionModel()->setCurrentIndex(current, QItemSelectionModel::Current); + } + } + m_expandingTogglePressed = false; + + if (m_band.show) { + setState(NoState); + updateElasticBand(); + m_band.show = false; + } +} + +void DolphinDetailsView::startDrag(Qt::DropActions supportedActions) +{ + DragAndDropHelper::instance().startDrag(this, supportedActions, m_dolphinViewController); + m_band.show = false; +} + +void DolphinDetailsView::dragEnterEvent(QDragEnterEvent* event) +{ + if (DragAndDropHelper::instance().isMimeDataSupported(event->mimeData())) { + event->acceptProposedAction(); + } + + if (m_band.show) { + updateElasticBand(); + m_band.show = false; + } +} + +void DolphinDetailsView::dragLeaveEvent(QDragLeaveEvent* event) +{ + QTreeView::dragLeaveEvent(event); + setDirtyRegion(m_dropRect); +} + +void DolphinDetailsView::dragMoveEvent(QDragMoveEvent* event) +{ + QTreeView::dragMoveEvent(event); + + // TODO: remove this code when the issue #160611 is solved in Qt 4.4 + setDirtyRegion(m_dropRect); + const QModelIndex index = indexAt(event->pos()); + if (index.isValid() && (index.column() == DolphinModel::Name)) { + const KFileItem item = m_dolphinViewController->itemForIndex(index); + if (!item.isNull() && item.isDir()) { + m_dropRect = visualRect(index); + } else { + m_dropRect.setSize(QSize()); // set as invalid + } + setDirtyRegion(m_dropRect); + } + + if (DragAndDropHelper::instance().isMimeDataSupported(event->mimeData())) { + // accept url drops, independently from the destination item + event->acceptProposedAction(); + } +} + +void DolphinDetailsView::dropEvent(QDropEvent* event) +{ + const QModelIndex index = indexAt(event->pos()); + KFileItem item; + if (index.isValid() && (index.column() == DolphinModel::Name)) { + item = m_dolphinViewController->itemForIndex(index); + } + m_dolphinViewController->indicateDroppedUrls(item, m_viewModeController->url(), event); + QTreeView::dropEvent(event); +} + +void DolphinDetailsView::paintEvent(QPaintEvent* event) +{ + QTreeView::paintEvent(event); + if (m_band.show) { + // The following code has been taken from QListView + // and adapted to DolphinDetailsView. + // (C) 1992-2007 Trolltech ASA + QStyleOptionRubberBand opt; + opt.initFrom(this); + opt.shape = QRubberBand::Rectangle; + opt.opaque = false; + opt.rect = elasticBandRect(); + + QPainter painter(viewport()); + painter.save(); + style()->drawControl(QStyle::CE_RubberBand, &opt, &painter); + painter.restore(); + } +} + +void DolphinDetailsView::keyPressEvent(QKeyEvent* event) +{ + // If the Control modifier is pressed, a multiple selection + // is done and DolphinDetailsView::currentChanged() may not + // not change the selection in a custom way. + m_keyPressed = !(event->modifiers() & Qt::ControlModifier); + + QTreeView::keyPressEvent(event); + m_dolphinViewController->handleKeyPressEvent(event); +} + +void DolphinDetailsView::keyReleaseEvent(QKeyEvent* event) +{ + QTreeView::keyReleaseEvent(event); + m_keyPressed = false; +} + +void DolphinDetailsView::resizeEvent(QResizeEvent* event) +{ + QTreeView::resizeEvent(event); + if (m_autoResize) { + resizeColumns(); + } +} + +void DolphinDetailsView::wheelEvent(QWheelEvent* event) +{ + const int step = m_decorationSize.height(); + verticalScrollBar()->setSingleStep(step); + QTreeView::wheelEvent(event); +} + +void DolphinDetailsView::currentChanged(const QModelIndex& current, const QModelIndex& previous) +{ + QTreeView::currentChanged(current, previous); + m_extensionsFactory->handleCurrentIndexChange(current, previous); + + // Stay consistent with QListView: When changing the current index by key presses, + // also change the selection. + if (m_keyPressed) { + setCurrentIndex(current); + } + + // If folders are expanded, the width which is available for editing may have changed + // because it depends on the level of the current item in the folder hierarchy. + adjustMaximumSizeForEditing(current); +} + +bool DolphinDetailsView::eventFilter(QObject* watched, QEvent* event) +{ + if ((watched == viewport()) && (event->type() == QEvent::Leave)) { + // if the mouse is above an item and moved very fast outside the widget, + // no viewportEntered() signal might be emitted although the mouse has been moved + // above the viewport + m_dolphinViewController->emitViewportEntered(); + } + + return QTreeView::eventFilter(watched, event); +} + +QModelIndex DolphinDetailsView::indexAt(const QPoint& point) const +{ + // the blank portion of the name column counts as empty space + const QModelIndex index = QTreeView::indexAt(point); + const bool isAboveEmptySpace = !m_useDefaultIndexAt && + (index.column() == KDirModel::Name) && !visualRect(index).contains(point); + return isAboveEmptySpace ? QModelIndex() : index; +} + +QRect DolphinDetailsView::visualRect(const QModelIndex& index) const +{ + QRect rect = QTreeView::visualRect(index); + const KFileItem item = m_dolphinViewController->itemForIndex(index); + if (!item.isNull()) { + const int width = DolphinFileItemDelegate::nameColumnWidth(item.text(), viewOptions()); + rect.setWidth(width); + } + + return rect; +} + +void DolphinDetailsView::setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags command) +{ + // We must override setSelection() as Qt calls it internally and when this happens + // we must ensure that the default indexAt() is used. + if (!m_band.show) { + m_useDefaultIndexAt = true; + QTreeView::setSelection(rect, command); + m_useDefaultIndexAt = false; + } else { + // Use our own elastic band selection algorithm + updateElasticBandSelection(); + } +} + +void DolphinDetailsView::scrollTo(const QModelIndex & index, ScrollHint hint) +{ + if (!m_ignoreScrollTo) { + QTreeView::scrollTo(index, hint); + } +} + +void DolphinDetailsView::setSortIndicatorSection(DolphinView::Sorting sorting) +{ + header()->setSortIndicator(sorting, header()->sortIndicatorOrder()); +} + +void DolphinDetailsView::setSortIndicatorOrder(Qt::SortOrder sortOrder) +{ + header()->setSortIndicator(header()->sortIndicatorSection(), sortOrder); +} + +void DolphinDetailsView::synchronizeSortingState(int column) +{ + // The sorting has already been changed in QTreeView if this slot is + // invoked, but Dolphin is not informed about this. + DolphinView::Sorting sorting = DolphinSortFilterProxyModel::sortingForColumn(column); + const Qt::SortOrder sortOrder = header()->sortIndicatorOrder(); + m_dolphinViewController->indicateSortingChange(sorting); + m_dolphinViewController->indicateSortOrderChange(sortOrder); +} + +void DolphinDetailsView::slotEntered(const QModelIndex& index) +{ + if (index.column() == DolphinModel::Name) { + m_dolphinViewController->emitItemEntered(index); + } else { + m_dolphinViewController->emitViewportEntered(); + } +} + +void DolphinDetailsView::updateElasticBand() +{ + if (m_band.show) { + QRect dirtyRegion(elasticBandRect()); + const QPoint scrollPos(horizontalScrollBar()->value(), verticalScrollBar()->value()); + m_band.destination = viewport()->mapFromGlobal(QCursor::pos()) + scrollPos; + // Going above the (logical) top-left of the view causes complications during selection; + // we may as well prevent it. + if (m_band.destination.y() < 0) { + m_band.destination.setY(0); + } + if (m_band.destination.x() < 0) { + m_band.destination.setX(0); + } + dirtyRegion = dirtyRegion.united(elasticBandRect()); + setDirtyRegion(dirtyRegion); + } +} + +QRect DolphinDetailsView::elasticBandRect() const +{ + const QPoint scrollPos(horizontalScrollBar()->value(), verticalScrollBar()->value()); + + const QPoint topLeft = m_band.origin - scrollPos; + const QPoint bottomRight = m_band.destination - scrollPos; + return QRect(topLeft, bottomRight).normalized(); +} + +void DolphinDetailsView::setZoomLevel(int level) +{ + const int size = ZoomLevelInfo::iconSizeForZoomLevel(level); + DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings(); + + const bool showPreview = m_dolphinViewController->view()->showPreview(); + if (showPreview) { + settings->setPreviewSize(size); + } else { + settings->setIconSize(size); + } + + updateDecorationSize(showPreview); +} + +void DolphinDetailsView::slotShowPreviewChanged() +{ + const DolphinView* view = m_dolphinViewController->view(); + updateDecorationSize(view->showPreview()); +} + +void DolphinDetailsView::configureSettings(const QPoint& pos) +{ + KMenu popup(this); + popup.addTitle(i18nc("@title:menu", "Columns")); + + // add checkbox items for each column + QHeaderView* headerView = header(); + const int columns = model()->columnCount(); + for (int i = 0; i < columns; ++i) { + const int logicalIndex = headerView->logicalIndex(i); + const QString text = model()->headerData(logicalIndex, Qt::Horizontal).toString(); + if (!text.isEmpty()) { + QAction* action = popup.addAction(text); + action->setCheckable(true); + action->setChecked(!headerView->isSectionHidden(logicalIndex)); + action->setData(logicalIndex); + action->setEnabled(logicalIndex != DolphinModel::Name); + } + } + popup.addSeparator(); + + QAction* activatedAction = popup.exec(header()->mapToGlobal(pos)); + if (activatedAction != 0) { + const bool show = activatedAction->isChecked(); + const int columnIndex = activatedAction->data().toInt(); + + KFileItemDelegate::InformationList list = m_dolphinViewController->view()->additionalInfo(); + const KFileItemDelegate::Information info = infoForColumn(columnIndex); + if (show) { + Q_ASSERT(!list.contains(info)); + list.append(info); + } else { + Q_ASSERT(list.contains(info)); + const int index = list.indexOf(info); + list.removeAt(index); + } + + m_dolphinViewController->indicateAdditionalInfoChange(list); + setColumnHidden(columnIndex, !show); + resizeColumns(); + } +} + +void DolphinDetailsView::updateColumnVisibility() +{ + QHeaderView* headerView = header(); + disconnect(headerView, SIGNAL(sectionMoved(int, int, int)), + this, SLOT(saveColumnPositions())); + + const DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings(); + const QList<int> columnPositions = settings->columnPositions(); + + const KFileItemDelegate::InformationList list = m_dolphinViewController->view()->additionalInfo(); + for (int i = DolphinModel::Name; i < DolphinModel::ExtraColumnCount; ++i) { + const KFileItemDelegate::Information info = infoForColumn(i); + const bool hide = !list.contains(info) && (i != DolphinModel::Name); + if (isColumnHidden(i) != hide) { + setColumnHidden(i, hide); + } + + // If the list columnPositions has been written by an older Dolphin version, + // its length might be smaller than DolphinModel::ExtraColumnCount. Therefore, + // we have to check if item number i exists before accessing it. + if (i < columnPositions.length()) { + const int position = columnPositions[i]; + + // The position might be outside the correct range if the list columnPositions + // has been written by a newer Dolphin version with more columns. + if (position < DolphinModel::ExtraColumnCount) { + const int from = headerView->visualIndex(i); + headerView->moveSection(from, position); + } + } + } + + resizeColumns(); + + connect(headerView, SIGNAL(sectionMoved(int, int, int)), + this, SLOT(saveColumnPositions())); +} + +void DolphinDetailsView::saveColumnPositions() +{ + QList<int> columnPositions; + for (int i = DolphinModel::Name; i < DolphinModel::ExtraColumnCount; ++i) { + columnPositions.append(header()->visualIndex(i)); + } + + DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings(); + settings->setColumnPositions(columnPositions); +} + +void DolphinDetailsView::slotHeaderSectionResized(int logicalIndex, int oldSize, int newSize) +{ + Q_UNUSED(logicalIndex); + Q_UNUSED(oldSize); + Q_UNUSED(newSize); + // If the user changes the size of the headers, the autoresize feature should be + // turned off. As there is no dedicated interface to find out whether the header + // section has been resized by the user or by a resize event, another approach is used. + // Attention: Take care when changing the if-condition to verify that there is no + // regression in combination with bug 178630 (see fix in comment #8). + if ((QApplication::mouseButtons() & Qt::LeftButton) && header()->underMouse()) { + disableAutoResizing(); + } + + adjustMaximumSizeForEditing(currentIndex()); +} + +void DolphinDetailsView::slotActivationChanged(bool active) +{ + setAlternatingRowColors(active); +} + +void DolphinDetailsView::disableAutoResizing() +{ + m_autoResize = false; +} + +void DolphinDetailsView::requestActivation() +{ + m_dolphinViewController->requestActivation(); +} + +void DolphinDetailsView::slotGlobalSettingsChanged(int category) +{ + Q_UNUSED(category); + + const DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings(); + Q_ASSERT(settings != 0); + if (settings->useSystemFont()) { + m_font = KGlobalSettings::generalFont(); + } + //Disconnect then reconnect, since the settings have been changed, the connection requirements may have also. + disconnect(this, SIGNAL(clicked(QModelIndex)), m_dolphinViewController, SLOT(triggerItem(QModelIndex))); + disconnect(this, SIGNAL(doubleClicked(QModelIndex)), m_dolphinViewController, SLOT(triggerItem(QModelIndex))); + if (KGlobalSettings::singleClick()) { + connect(this, SIGNAL(clicked(QModelIndex)), m_dolphinViewController, SLOT(triggerItem(QModelIndex))); + } else { + connect(this, SIGNAL(doubleClicked(QModelIndex)), m_dolphinViewController, SLOT(triggerItem(QModelIndex))); + } +} + +void DolphinDetailsView::updateElasticBandSelection() +{ + if (!m_band.show) { + return; + } + + // Ensure the elastic band itself is up-to-date, in + // case we are being called due to e.g. a drag event. + updateElasticBand(); + + // Clip horizontally to the name column, as some filenames will be + // longer than the column. We don't clip vertically as origin + // may be above or below the current viewport area. + const int nameColumnX = header()->sectionPosition(DolphinModel::Name); + const int nameColumnWidth = header()->sectionSize(DolphinModel::Name); + QRect selRect = elasticBandRect().normalized(); + QRect nameColumnArea(nameColumnX, selRect.y(), nameColumnWidth, selRect.height()); + selRect = nameColumnArea.intersect(selRect).normalized(); + // Get the last elastic band rectangle, expressed in viewpoint coordinates. + const QPoint scrollPos(horizontalScrollBar()->value(), verticalScrollBar()->value()); + QRect oldSelRect = QRect(m_band.lastSelectionOrigin - scrollPos, m_band.lastSelectionDestination - scrollPos).normalized(); + + if (selRect.isNull()) { + selectionModel()->select(m_band.originalSelection, QItemSelectionModel::ClearAndSelect); + m_band.ignoreOldInfo = true; + return; + } + + if (!m_band.ignoreOldInfo) { + // Do some quick checks to see if we can rule out the need to + // update the selection. + Q_ASSERT(uniformRowHeights()); + QModelIndex dummyIndex = model()->index(0, 0); + if (!dummyIndex.isValid()) { + // No items in the model presumably. + return; + } + + // If the elastic band does not cover the same rows as before, we'll + // need to re-check, and also invalidate the old item distances. + const int rowHeight = QTreeView::rowHeight(dummyIndex); + const bool coveringSameRows = + (selRect.top() / rowHeight == oldSelRect.top() / rowHeight) && + (selRect.bottom() / rowHeight == oldSelRect.bottom() / rowHeight); + if (coveringSameRows) { + // Covering the same rows, but have we moved far enough horizontally + // that we might have (de)selected some other items? + const bool itemSelectionChanged = + ((selRect.left() > oldSelRect.left()) && + (selRect.left() > m_band.insideNearestLeftEdge)) || + ((selRect.left() < oldSelRect.left()) && + (selRect.left() <= m_band.outsideNearestLeftEdge)) || + ((selRect.right() < oldSelRect.right()) && + (selRect.left() >= m_band.insideNearestRightEdge)) || + ((selRect.right() > oldSelRect.right()) && + (selRect.right() >= m_band.outsideNearestRightEdge)); + + if (!itemSelectionChanged) { + return; + } + } + } else { + // This is the only piece of optimization data that needs to be explicitly + // discarded. + m_band.lastSelectionOrigin = QPoint(); + m_band.lastSelectionDestination = QPoint(); + oldSelRect = selRect; + } + + // Do the selection from scratch. Force a update of the horizontal distances info. + m_band.insideNearestLeftEdge = nameColumnX + nameColumnWidth + 1; + m_band.insideNearestRightEdge = nameColumnX - 1; + m_band.outsideNearestLeftEdge = nameColumnX - 1; + m_band.outsideNearestRightEdge = nameColumnX + nameColumnWidth + 1; + + // Include the old selection rect as well, so we can deselect + // items that were inside it but not in the new selRect. + const QRect boundingRect = selRect.united(oldSelRect).normalized(); + if (boundingRect.isNull()) { + return; + } + + // Get the index of the item in this row in the name column. + // TODO - would this still work if the columns could be re-ordered? + QModelIndex startIndex = QTreeView::indexAt(boundingRect.topLeft()); + if (startIndex.parent().isValid()) { + startIndex = startIndex.parent().child(startIndex.row(), KDirModel::Name); + } else { + startIndex = model()->index(startIndex.row(), KDirModel::Name); + } + if (!startIndex.isValid()) { + selectionModel()->select(m_band.originalSelection, QItemSelectionModel::ClearAndSelect); + m_band.ignoreOldInfo = true; + return; + } + + // Go through all indexes between the top and bottom of boundingRect, and + // update the selection. + const int verticalCutoff = boundingRect.bottom(); + QModelIndex currIndex = startIndex; + QModelIndex lastIndex; + bool allItemsInBoundDone = false; + + // Calling selectionModel()->select(...) for each item that needs to be + // toggled is slow as each call emits selectionChanged(...) so store them + // and do the selection toggle in one batch. + QItemSelection itemsToToggle; + // QItemSelection's deal with continuous ranges of indexes better than + // single indexes, so try to portion items that need to be toggled into ranges. + bool formingToggleIndexRange = false; + QModelIndex toggleIndexRangeBegin = QModelIndex(); + + do { + QRect currIndexRect = visualRect(currIndex); + + // Update some optimization info as we go. + const int cr = currIndexRect.right(); + const int cl = currIndexRect.left(); + const int sl = selRect.left(); + const int sr = selRect.right(); + // "The right edge of the name is outside of the rect but nearer than m_outsideNearestLeft", etc + if ((cr < sl && cr > m_band.outsideNearestLeftEdge)) { + m_band.outsideNearestLeftEdge = cr; + } + if ((cl > sr && cl < m_band.outsideNearestRightEdge)) { + m_band.outsideNearestRightEdge = cl; + } + if ((cl >= sl && cl <= sr && cl > m_band.insideNearestRightEdge)) { + m_band.insideNearestRightEdge = cl; + } + if ((cr >= sl && cr <= sr && cr < m_band.insideNearestLeftEdge)) { + m_band.insideNearestLeftEdge = cr; + } + + bool currentlySelected = selectionModel()->isSelected(currIndex); + bool originallySelected = m_band.originalSelection.contains(currIndex); + bool intersectsSelectedRect = currIndexRect.intersects(selRect); + bool shouldBeSelected = (intersectsSelectedRect && !originallySelected) || (!intersectsSelectedRect && originallySelected); + bool needToToggleItem = (currentlySelected && !shouldBeSelected) || (!currentlySelected && shouldBeSelected); + if (needToToggleItem && !formingToggleIndexRange) { + toggleIndexRangeBegin = currIndex; + formingToggleIndexRange = true; + } + + // NOTE: indexBelow actually walks up and down expanded trees for us. + QModelIndex nextIndex = indexBelow(currIndex); + allItemsInBoundDone = !nextIndex.isValid() || currIndexRect.top() > verticalCutoff; + + const bool commitToggleIndexRange = formingToggleIndexRange && + (!needToToggleItem || + allItemsInBoundDone || + currIndex.parent() != toggleIndexRangeBegin.parent()); + if (commitToggleIndexRange) { + formingToggleIndexRange = false; + // If this is the last item in the bounds and it is also the beginning of a range, + // don't toggle lastIndex - it will already have been dealt with. + if (!allItemsInBoundDone || toggleIndexRangeBegin != currIndex) { + itemsToToggle.select(toggleIndexRangeBegin, lastIndex); + } + // Need to start a new range immediately with currIndex? + if (needToToggleItem) { + toggleIndexRangeBegin = currIndex; + formingToggleIndexRange = true; + } + if (allItemsInBoundDone && needToToggleItem) { + // Toggle the very last item in the bounds. + itemsToToggle.select(currIndex, currIndex); + } + } + + // next item + lastIndex = currIndex; + currIndex = nextIndex; + } while (!allItemsInBoundDone); + + + selectionModel()->select(itemsToToggle, QItemSelectionModel::Toggle); + + m_band.lastSelectionOrigin = m_band.origin; + m_band.lastSelectionDestination = m_band.destination; + m_band.ignoreOldInfo = false; +} + +void DolphinDetailsView::setFoldersExpandable(bool expandable) +{ + if (!expandable) { + // collapse all expanded folders, as QTreeView::setItemsExpandable(false) + // does not do this task + const int rowCount = model()->rowCount(); + for (int row = 0; row < rowCount; ++row) { + setExpanded(model()->index(row, 0), false); + } + } + DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings(); + settings->setExpandableFolders(expandable); + setRootIsDecorated(expandable); + setItemsExpandable(expandable); + + // The width of the space which is available for editing has changed + // because of the (dis)appearance of the expanding toggles + adjustMaximumSizeForEditing(currentIndex()); +} + +void DolphinDetailsView::slotExpanded(const QModelIndex& index) +{ + KFileItem item = m_dolphinViewController->itemForIndex(index); + if (!item.isNull()) { + m_expandedUrls.insert(item.url()); + } +} + +void DolphinDetailsView::slotCollapsed(const QModelIndex& index) +{ + KFileItem item = m_dolphinViewController->itemForIndex(index); + if (!item.isNull()) { + m_expandedUrls.remove(item.url()); + } +} + +void DolphinDetailsView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + removeExpandedIndexes(parent, start, end); + QTreeView::rowsAboutToBeRemoved(parent, start, end); +} + +void DolphinDetailsView::removeExpandedIndexes(const QModelIndex& parent, int start, int end) +{ + if (m_expandedUrls.isEmpty()) { + return; + } + + for (int row = start; row <= end; row++) { + const QModelIndex index = model()->index(row, 0, parent); + if (isExpanded(index)) { + slotCollapsed(index); + removeExpandedIndexes(index, 0, model()->rowCount(index) - 1); + } + } +} + +void DolphinDetailsView::updateDecorationSize(bool showPreview) +{ + DetailsModeSettings* settings = DolphinSettings::instance().detailsModeSettings(); + const int iconSize = showPreview ? settings->previewSize() : settings->iconSize(); + setIconSize(QSize(iconSize, iconSize)); + m_decorationSize = QSize(iconSize, iconSize); + + doItemsLayout(); +} + +KFileItemDelegate::Information DolphinDetailsView::infoForColumn(int columnIndex) const +{ + return AdditionalInfoAccessor::instance().keyForColumn(columnIndex); +} + +void DolphinDetailsView::resizeColumns() +{ + // Using the resize mode QHeaderView::ResizeToContents is too slow (it takes + // around 3 seconds for each (!) resize operation when having > 10000 items). + // This gets a problem especially when opening large directories, where several + // resize operations are received for showing the currently available items during + // loading (the application hangs around 20 seconds when loading > 10000 items). + + QHeaderView* headerView = header(); + QFontMetrics fontMetrics(viewport()->font()); + + // Calculate the required with for each column and store it in columnWidth[] + int columnWidth[DolphinModel::ExtraColumnCount]; + const int defaultWidth = fontMetrics.width("xxxxxxxxxx"); + + for (int i = 0; i < DolphinModel::ExtraColumnCount; ++i) { + const int logicalIndex = headerView->logicalIndex(i); + const QString headline = model()->headerData(logicalIndex, Qt::Horizontal).toString(); + const int headlineWidth = fontMetrics.width(headline); + + columnWidth[i] = qMax(defaultWidth, headlineWidth); + } + + const int defaultSizeWidth = fontMetrics.width("00000 Items"); + if (defaultSizeWidth > columnWidth[DolphinModel::Size]) { + columnWidth[DolphinModel::Size] = defaultSizeWidth; + } + + const int defaultTimeWidth = fontMetrics.width("0000-00-00 00:00"); + if (defaultTimeWidth > columnWidth[DolphinModel::ModifiedTime]) { + columnWidth[DolphinModel::ModifiedTime] = defaultTimeWidth; + } + + int requiredWidth = 0; + for (int i = KDirModel::Size; i < DolphinModel::ExtraColumnCount; ++i) { + if (!isColumnHidden(i)) { + columnWidth[i] += 20; // provide a default gap + requiredWidth += columnWidth[i]; + headerView->resizeSection(i, columnWidth[i]); + } + } + + // Resize the name column in a way that the whole available width is used + columnWidth[KDirModel::Name] = viewport()->width() - requiredWidth; + + const int minNameWidth = 300; + if (columnWidth[KDirModel::Name] < minNameWidth) { + columnWidth[KDirModel::Name] = minNameWidth; + + // It might be possible that the name column width can be + // decreased without clipping any text. For performance + // reasons the exact necessary width for full visible names is + // only checked for up to 200 items: + const int rowCount = model()->rowCount(); + if (rowCount > 0 && rowCount < 200) { + const int nameWidth = sizeHintForColumn(DolphinModel::Name); + if (nameWidth + requiredWidth <= viewport()->width()) { + columnWidth[KDirModel::Name] = viewport()->width() - requiredWidth; + } else if (nameWidth < minNameWidth) { + columnWidth[KDirModel::Name] = nameWidth; + } + } + } + + headerView->resizeSection(KDirModel::Name, columnWidth[KDirModel::Name]); +} + +bool DolphinDetailsView::isAboveExpandingToggle(const QPoint& pos) const +{ + // QTreeView offers no public API to get the information whether an index has an + // expanding toggle and what boundaries the toggle has. The following approach + // also assumes a toggle for file items. + if (itemsExpandable()) { + const QModelIndex index = QTreeView::indexAt(pos); + if (index.isValid() && (index.column() == KDirModel::Name)) { + QRect rect = visualRect(index); + const int toggleSize = rect.height(); + if (isRightToLeft()) { + rect.moveRight(rect.right()); + } else { + rect.moveLeft(rect.x() - toggleSize); + } + rect.setWidth(toggleSize); + + QStyleOption opt; + opt.initFrom(this); + opt.rect = rect; + rect = style()->subElementRect(QStyle::SE_TreeViewDisclosureItem, &opt, this); + + return rect.contains(pos); + } + } + return false; +} + +void DolphinDetailsView::adjustMaximumSizeForEditing(const QModelIndex& index) +{ + // Make sure that the full width of the "Name" column is available for "Rename Inline" + m_extensionsFactory->fileItemDelegate()->setMaximumSize(QTreeView::visualRect(index).size()); +} + +DolphinDetailsView::ElasticBand::ElasticBand() : + show(false), + origin(), + destination(), + lastSelectionOrigin(), + lastSelectionDestination(), + ignoreOldInfo(true), + outsideNearestLeftEdge(0), + outsideNearestRightEdge(0), + insideNearestLeftEdge(0), + insideNearestRightEdge(0) +{ +} + +#include "dolphindetailsview.moc" diff --git a/src/views/dolphindetailsview.h b/src/views/dolphindetailsview.h new file mode 100644 index 000000000..3ac08d337 --- /dev/null +++ b/src/views/dolphindetailsview.h @@ -0,0 +1,287 @@ +/*************************************************************************** + * Copyright (C) 2006 by Peter Penz ([email protected]) * + * Copyright (C) 2008 by Simon St. James ([email protected]) * + * * + * 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 DOLPHINDETAILSVIEW_H +#define DOLPHINDETAILSVIEW_H + +#include <QTreeView> +#include <libdolphin_export.h> +#include <views/dolphinview.h> + +class DolphinViewController; +class DolphinSortFilterProxyModel; +class ViewExtensionsFactory; + +/** + * @brief Represents the details view which shows the name, size, + * date, permissions, owner and group of an item. + * + * The width of the columns is automatically adjusted in a way + * that full available width of the view is used by stretching the width + * of the name column. + */ +class LIBDOLPHINPRIVATE_EXPORT DolphinDetailsView : public QTreeView +{ + Q_OBJECT + +public: + /** + * @param parent Parent widget. + * @param dolphinViewController Allows the DolphinDetailsView to control the + * DolphinView in a limited way. + * @param viewModeController Controller that is used by the DolphinView + * to control the DolphinDetailsView. The DolphinDetailsView + * only has read access to the controller. + * @param model Directory that is shown. + */ + explicit DolphinDetailsView(QWidget* parent, + DolphinViewController* dolphinViewController, + const ViewModeController* viewModeController, + DolphinSortFilterProxyModel* model); + virtual ~DolphinDetailsView(); + + /** + * Returns a set containing the URLs of all expanded items. + */ + QSet<KUrl> expandedUrls() const; + + virtual QRegion visualRegionForSelection(const QItemSelection& selection) const; + +protected: + virtual bool event(QEvent* event); + virtual QStyleOptionViewItem viewOptions() const; + virtual void contextMenuEvent(QContextMenuEvent* event); + virtual void mousePressEvent(QMouseEvent* event); + virtual void mouseMoveEvent(QMouseEvent* event); + virtual void mouseReleaseEvent(QMouseEvent* event); + virtual void startDrag(Qt::DropActions supportedActions); + virtual void dragEnterEvent(QDragEnterEvent* event); + virtual void dragLeaveEvent(QDragLeaveEvent* event); + virtual void dragMoveEvent(QDragMoveEvent* event); + virtual void dropEvent(QDropEvent* event); + virtual void paintEvent(QPaintEvent* event); + virtual void keyPressEvent(QKeyEvent* event); + virtual void keyReleaseEvent(QKeyEvent* event); + virtual void resizeEvent(QResizeEvent* event); + virtual void wheelEvent(QWheelEvent* event); + virtual void currentChanged(const QModelIndex& current, const QModelIndex& previous); + virtual bool eventFilter(QObject* watched, QEvent* event); + virtual QModelIndex indexAt (const QPoint& point) const; + virtual QRect visualRect(const QModelIndex& index) const; + virtual void setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags command); + virtual void scrollTo(const QModelIndex& index, ScrollHint hint = EnsureVisible); + +private slots: + /** + * Sets the sort indicator section of the header view + * corresponding to \a sorting. + */ + void setSortIndicatorSection(DolphinView::Sorting sorting); + + /** + * Sets the sort indicator order of the header view + * corresponding to \a sortOrder. + */ + void setSortIndicatorOrder(Qt::SortOrder sortOrder); + + /** + * Synchronizes the sorting state of the Dolphin menu 'View -> Sort' + * with the current state of the details view. + * @param column Index of the current sorting column. + */ + void synchronizeSortingState(int column); + + /** + * Is invoked when the mouse cursor has entered an item. The controller + * gets informed to emit the itemEntered() signal if the mouse cursor + * is above the name column. Otherwise the controller gets informed + * to emit the itemViewportEntered() signal (all other columns should + * behave as viewport area). + */ + void slotEntered(const QModelIndex& index); + + /** + * Updates the destination \a destination from + * the elastic band to the current mouse position and triggers + * an update. + */ + void updateElasticBand(); + + /** + * Returns the rectangle for the elastic band dependent from the + * origin \a origin, the current destination + * \a destination and the viewport position. + */ + QRect elasticBandRect() const; + + void setZoomLevel(int level); + + void slotShowPreviewChanged(); + + /** + * Opens a context menu at the position \a pos and allows to + * configure the visibility of the header columns and whether + * expandable folders should be shown. + */ + void configureSettings(const QPoint& pos); + + /** + * Updates the visibilty state of columns and their order. + */ + void updateColumnVisibility(); + + /** + * Saves order of the columns as global setting. + */ + void saveColumnPositions(); + + /** + * Disables the automatical resizing of columns, if the user has resized the columns + * with the mouse. + */ + void slotHeaderSectionResized(int logicalIndex, int oldSize, int newSize); + + /** + * Changes the alternating row colors setting depending from + * the activation state \a active. + */ + void slotActivationChanged(bool active); + + /** + * Disables the automatical resizing of the columns. Per default all columns + * are resized to use the maximum available width of the view as good as possible. + */ + void disableAutoResizing(); + + void requestActivation(); + + void slotGlobalSettingsChanged(int category); + + /** + * If the elastic band is currently shown, update the elastic band based on + * the current mouse position and ensure that the selection is the set of items + * intersecting it. + */ + void updateElasticBandSelection(); + + /** + * If \a expandable is true, the details view acts as tree view. + * The current expandable state is remembered in the settings. + */ + void setFoldersExpandable(bool expandable); + + /** + * These slots update the list of expanded items. + */ + void slotExpanded(const QModelIndex& index); + void slotCollapsed(const QModelIndex& index); + +protected slots: + + virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + +private: + /** + * Removes the URLs corresponding to the children of \a index in the rows + * between \a start and \a end inclusive from the set of expanded URLs. + */ + void removeExpandedIndexes(const QModelIndex& parent, int start, int end); + + /** + * Updates the size of the decoration dependent on the + * icon size of the DetailsModeSettings. The controller + * will get informed about possible zoom in/zoom out + * operations. + */ + void updateDecorationSize(bool showPreview); + + KFileItemDelegate::Information infoForColumn(int columnIndex) const; + + /** + * Resizes all columns in a way to use the whole available width of the view. + */ + void resizeColumns(); + + /** + * Returns true, if \a pos is within the expanding toggle of a tree. + */ + bool isAboveExpandingToggle(const QPoint& pos) const; + + /** + * Sets the maximum size available for editing in the delegate. + */ + void adjustMaximumSizeForEditing(const QModelIndex& index); + +private: + bool m_autoResize : 1; // if true, the columns are resized automatically to the available width + bool m_expandingTogglePressed : 1; + bool m_keyPressed : 1; // true if a key is pressed currently; info used by currentChanged() + bool m_useDefaultIndexAt : 1; // true, if QTreeView::indexAt() should be used + bool m_ignoreScrollTo : 1; // true if calls to scrollTo(...) should do nothing. + + DolphinViewController* m_dolphinViewController; + const ViewModeController* m_viewModeController; + ViewExtensionsFactory* m_extensionsFactory; + QAction* m_expandableFoldersAction; + + // A set containing the URLs of all currently expanded folders. + // We cannot use a QSet<QModelIndex> because a QModelIndex is not guaranteed to remain valid over time. + // Also a QSet<QPersistentModelIndex> does not work as expected because it is not guaranteed that + // subsequent expand/collapse events of the same file item will yield the same QPersistentModelIndex. + QSet<KUrl> m_expandedUrls; + + QFont m_font; + QSize m_decorationSize; + + QRect m_dropRect; + + struct ElasticBand + { + ElasticBand(); + + // Elastic band origin and destination coordinates are relative to t + // he origin of the view, not the viewport. + bool show; + QPoint origin; + QPoint destination; + + // Optimization mechanisms for use with elastic band selection. + // Basically, allow "incremental" updates to the selection based + // on the last elastic band shape. + QPoint lastSelectionOrigin; + QPoint lastSelectionDestination; + + // If true, compute the set of selected elements from scratch (slower) + bool ignoreOldInfo; + + // Edges of the filenames that are closest to the edges of oldSelectionRect. + // Used to decide whether horizontal changes in the elastic band are likely + // to require us to re-check which items are selected. + int outsideNearestLeftEdge; + int outsideNearestRightEdge; + int insideNearestLeftEdge; + int insideNearestRightEdge; + // The set of items that were selected at the time this band was shown. + // NOTE: Unless CTRL was pressed when the band was created, this is always empty. + QItemSelection originalSelection; + } m_band; +}; + +#endif diff --git a/src/views/dolphindetailsviewexpander.cpp b/src/views/dolphindetailsviewexpander.cpp new file mode 100644 index 000000000..0f3d3fde1 --- /dev/null +++ b/src/views/dolphindetailsviewexpander.cpp @@ -0,0 +1,87 @@ +/*************************************************************************** + * Copyright (C) 2009 by Frank Reininghaus ([email protected]) * + * * + * 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 "dolphindetailsviewexpander.h" + +#include "dolphindetailsview.h" +#include "dolphinmodel.h" +#include "dolphinsortfilterproxymodel.h" + +#include <kdirlister.h> +#include <kdirmodel.h> + +DolphinDetailsViewExpander::DolphinDetailsViewExpander(DolphinDetailsView* parent, + const QSet<KUrl>& urlsToExpand) : + QObject(parent), + m_detailsView(parent), + m_dirLister(0), + m_dolphinModel(0), + m_proxyModel(0) +{ + Q_ASSERT(parent != 0); + + m_proxyModel = qobject_cast<const DolphinSortFilterProxyModel*>(parent->model()); + Q_ASSERT(m_proxyModel != 0); + + m_dolphinModel = qobject_cast<const DolphinModel*>(m_proxyModel->sourceModel()); + Q_ASSERT(m_dolphinModel != 0); + + m_dirLister = m_dolphinModel->dirLister(); + Q_ASSERT(m_dirLister != 0); + + // The URLs must be sorted. E.g. /home/user/ cannot be expanded before /home/ + // because it is not known to the dir model before. + m_urlsToExpand = urlsToExpand.toList(); + qSort(m_urlsToExpand); + + // The dir lister must have completed the folder listing before a subfolder can be expanded. + connect(m_dirLister, SIGNAL(completed()), this, SLOT(slotDirListerCompleted())); +} + +DolphinDetailsViewExpander::~DolphinDetailsViewExpander() +{ +} + +void DolphinDetailsViewExpander::stop() +{ + disconnect(m_dirLister, SIGNAL(completed()), this, SLOT(slotDirListerCompleted())); + deleteLater(); +} + +void DolphinDetailsViewExpander::slotDirListerCompleted() +{ + QModelIndex dirIndex; + + while(!m_urlsToExpand.isEmpty() && !dirIndex.isValid()) { + const KUrl url = m_urlsToExpand.takeFirst(); + dirIndex = m_dolphinModel->indexForUrl(url); + } + + if(dirIndex.isValid()) { + // A valid model index was found. Note that only one item is expanded in each call of this slot + // because expanding any item will trigger KDirLister::openUrl(...) via KDirModel::fetchMore(...), + // and we can only continue when the dir lister is done. + const QModelIndex proxyIndex = m_proxyModel->mapFromSource(dirIndex); + m_detailsView->expand(proxyIndex); + } + else { + emit completed(); + stop(); + } +} diff --git a/src/views/dolphindetailsviewexpander.h b/src/views/dolphindetailsviewexpander.h new file mode 100644 index 000000000..b4dc2fc72 --- /dev/null +++ b/src/views/dolphindetailsviewexpander.h @@ -0,0 +1,77 @@ +/*************************************************************************** + * Copyright (C) 2009 by Frank Reininghaus ([email protected]) * + * * + * 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 DOLPHINDETAILSVIEWEXPANDER_H +#define DOLPHINDETAILSVIEWEXPANDER_H + +#include <QObject> +#include <QSet> +#include <QList> + +class DolphinDetailsView; +class KUrl; +class KDirLister; +class DolphinModel; +class DolphinSortFilterProxyModel; + +/** + * @brief Expands a given set of subfolders in collaboration with the dir lister and the dir model. + * + * Note that only one subfolder can be expanded at a time. Each expansion triggers KDirLister::openUrl(...), + * and further expansions can only be done the next time the dir lister emits its completed() signal. + */ +class DolphinDetailsViewExpander : public QObject +{ + Q_OBJECT + +public: + explicit DolphinDetailsViewExpander(DolphinDetailsView* parent, + const QSet<KUrl>& urlsToExpand); + + virtual ~DolphinDetailsViewExpander(); + + /** + * Stops the expansion and deletes the object via deleteLater(). + */ + void stop(); + +private slots: + /** + * This slot is invoked every time the dir lister has completed a listing. + * It expands the first URL from the list m_urlsToExpand that can be found in the dir model. + * If the list is empty, stop() is called. + */ + void slotDirListerCompleted(); + +signals: + /** + * Is emitted when the expander has finished expanding URLs in the details view. + */ + void completed(); + +private: + QList<KUrl> m_urlsToExpand; + + DolphinDetailsView* m_detailsView; + const KDirLister* m_dirLister; + const DolphinModel* m_dolphinModel; + const DolphinSortFilterProxyModel* m_proxyModel; +}; + +#endif diff --git a/src/views/dolphinfileitemdelegate.cpp b/src/views/dolphinfileitemdelegate.cpp new file mode 100644 index 000000000..17447d8cb --- /dev/null +++ b/src/views/dolphinfileitemdelegate.cpp @@ -0,0 +1,180 @@ +/*************************************************************************** + * Copyright (C) 2008 by Peter Penz <[email protected]> * + * * + * 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 "dolphinfileitemdelegate.h" + +#include <dolphinmodel.h> +#include <kfileitem.h> +#include <kicon.h> +#include <kiconloader.h> + +#include <QAbstractItemModel> +#include <QAbstractProxyModel> +#include <QFontMetrics> +#include <QPalette> +#include <QPainter> +#include <QStyleOptionViewItemV4> + +DolphinFileItemDelegate::DolphinFileItemDelegate(QObject* parent) : + KFileItemDelegate(parent), + m_hasMinimizedNameColumn(false), + m_cachedSize(), + m_cachedEmblems() +{ + setJobTransfersVisible(true); +} + +DolphinFileItemDelegate::~DolphinFileItemDelegate() +{ +} + +void DolphinFileItemDelegate::paint(QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const +{ + const QAbstractProxyModel* proxyModel = static_cast<const QAbstractProxyModel*>(index.model()); + const DolphinModel* dolphinModel = static_cast<const DolphinModel*>(proxyModel->sourceModel()); + const bool isNameColumn = (index.column() == KDirModel::Name); + + QStyleOptionViewItemV4 opt(option); + if (m_hasMinimizedNameColumn && isNameColumn) { + adjustOptionWidth(opt, proxyModel, dolphinModel, index); + } + + if (dolphinModel->hasVersionData() && isNameColumn) { + // The currently shown items are under revision control. Show the current revision + // state by adding an emblem and changing the text tintColor. + const QModelIndex dirIndex = proxyModel->mapToSource(index); + const QModelIndex revisionIndex = dolphinModel->index(dirIndex.row(), DolphinModel::Version, dirIndex.parent()); + const QVariant data = dolphinModel->data(revisionIndex, Qt::DecorationRole); + const KVersionControlPlugin::VersionState state = static_cast<KVersionControlPlugin::VersionState>(data.toInt()); + + adjustOptionTextColor(opt, state); + + KFileItemDelegate::paint(painter, opt, index); + + if (state != KVersionControlPlugin::UnversionedVersion) { + const QRect rect = iconRect(option, index); + const QPixmap emblem = emblemForState(state, rect.size()); + painter->drawPixmap(rect.x(), rect.y() + rect.height() - emblem.height(), emblem); + } + } else { + KFileItemDelegate::paint(painter, opt, index); + } +} + +int DolphinFileItemDelegate::nameColumnWidth(const QString& name, const QStyleOptionViewItem& option) +{ + QFontMetrics fontMetrics(option.font); + int width = option.decorationSize.width() + fontMetrics.width(name) + 16; + + const int defaultWidth = option.rect.width(); + if ((defaultWidth > 0) && (defaultWidth < width)) { + width = defaultWidth; + } + return width; +} + +void DolphinFileItemDelegate::adjustOptionWidth(QStyleOptionViewItemV4& option, + const QAbstractProxyModel* proxyModel, + const DolphinModel* dolphinModel, + const QModelIndex& index) +{ + const QModelIndex dirIndex = proxyModel->mapToSource(index); + const KFileItem item = dolphinModel->itemForIndex(dirIndex); + if (!item.isNull()) { + // symbolic links are displayed in an italic font + if (item.isLink()) { + option.font.setItalic(true); + } + + const int width = nameColumnWidth(item.text(), option); + option.rect.setWidth(width); + } +} + +void DolphinFileItemDelegate::adjustOptionTextColor(QStyleOptionViewItemV4& option, + KVersionControlPlugin::VersionState state) +{ + QColor tintColor; + + // Using hardcoded colors is generally a bad idea. In this case the colors just act + // as tint colors and are mixed with the current set text color. The tint colors + // have been optimized for the base colors of the corresponding Oxygen emblems. + switch (state) { + case KVersionControlPlugin::UpdateRequiredVersion: tintColor = Qt::yellow; break; + case KVersionControlPlugin::LocallyModifiedVersion: tintColor = Qt::green; break; + case KVersionControlPlugin::AddedVersion: tintColor = Qt::darkGreen; break; + case KVersionControlPlugin::RemovedVersion: tintColor = Qt::darkRed; break; + case KVersionControlPlugin::ConflictingVersion: tintColor = Qt::red; break; + case KVersionControlPlugin::UnversionedVersion: + case KVersionControlPlugin::NormalVersion: + default: + // use the default text color + return; + } + + QPalette palette = option.palette; + const QColor textColor = palette.color(QPalette::Text); + tintColor = QColor((tintColor.red() + textColor.red()) / 2, + (tintColor.green() + textColor.green()) / 2, + (tintColor.blue() + textColor.blue()) / 2, + (tintColor.alpha() + textColor.alpha()) / 2); + palette.setColor(QPalette::Text, tintColor); + option.palette = palette; +} + +QPixmap DolphinFileItemDelegate::emblemForState(KVersionControlPlugin::VersionState state, const QSize& size) const +{ + Q_ASSERT(state <= KVersionControlPlugin::ConflictingVersion); + if (m_cachedSize != size) { + m_cachedSize = size; + + const int iconHeight = size.height(); + int emblemHeight = KIconLoader::SizeSmall; + if (iconHeight >= KIconLoader::SizeEnormous) { + emblemHeight = KIconLoader::SizeMedium; + } else if (iconHeight >= KIconLoader::SizeLarge) { + emblemHeight = KIconLoader::SizeSmallMedium; + } else if (iconHeight >= KIconLoader::SizeMedium) { + emblemHeight = KIconLoader::SizeSmall; + } else { + emblemHeight = KIconLoader::SizeSmall / 2; + } + + const QSize emblemSize(emblemHeight, emblemHeight); + for (int i = KVersionControlPlugin::NormalVersion; i <= KVersionControlPlugin::ConflictingVersion; ++i) { + QString iconName; + switch (i) { + case KVersionControlPlugin::NormalVersion: iconName = "vcs-normal"; break; + case KVersionControlPlugin::UpdateRequiredVersion: iconName = "vcs-update-required"; break; + case KVersionControlPlugin::LocallyModifiedVersion: iconName = "vcs-locally-modified"; break; + case KVersionControlPlugin::AddedVersion: iconName = "vcs-added"; break; + case KVersionControlPlugin::RemovedVersion: iconName = "vcs-removed"; break; + case KVersionControlPlugin::ConflictingVersion: iconName = "vcs-conflicting"; break; + case KVersionControlPlugin::UnversionedVersion: + default: Q_ASSERT(false); break; + } + + m_cachedEmblems[i] = KIcon(iconName).pixmap(emblemSize); + } + } + return m_cachedEmblems[state]; +} + diff --git a/src/views/dolphinfileitemdelegate.h b/src/views/dolphinfileitemdelegate.h new file mode 100644 index 000000000..405f24916 --- /dev/null +++ b/src/views/dolphinfileitemdelegate.h @@ -0,0 +1,90 @@ +/*************************************************************************** + * Copyright (C) 2008 by Peter Penz <[email protected]> * + * * + * 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 DOLPHINFILEITEMDELEGATE_H +#define DOLPHINFILEITEMDELEGATE_H + +#include <dolphinmodel.h> +#include <kfileitemdelegate.h> + +class QAbstractProxyModel; + +/** + * Extends KFileItemDelegate by the ability to show the hover effect + * and the selection in a minimized way for the name column of + * the details view. + * + * Note that this is a workaround, as Qt does not support having custom + * shapes within the visual rect of an item view. The visual part of + * workaround is handled inside DolphinFileItemDelegate, the behavior + * changes are handled in DolphinDetailsView. + */ +class DolphinFileItemDelegate : public KFileItemDelegate +{ +public: + explicit DolphinFileItemDelegate(QObject* parent = 0); + virtual ~DolphinFileItemDelegate(); + + /** + * If \a minimized is true, the hover effect and the selection are + * only drawn above the icon and text of an item. Per default + * \a minimized is false, which means that the whole visual rect is + * used like in KFileItemDelegate. + */ + void setMinimizedNameColumn(bool minimized); + bool hasMinimizedNameColumn() const; + + virtual void paint(QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const; + + /** + * Returns the minimized width of the name column for the name \a name. This method + * is also used in DolphinDetailsView to handle the selection of items correctly. + */ + static int nameColumnWidth(const QString& name, const QStyleOptionViewItem& option); + +private: + static void adjustOptionWidth(QStyleOptionViewItemV4& option, + const QAbstractProxyModel* proxyModel, + const DolphinModel* dolphinModel, + const QModelIndex& index); + + static void adjustOptionTextColor(QStyleOptionViewItemV4& option, + KVersionControlPlugin::VersionState state); + + QPixmap emblemForState(KVersionControlPlugin::VersionState state, const QSize& size) const; + +private: + bool m_hasMinimizedNameColumn; + mutable QSize m_cachedSize; + mutable QPixmap m_cachedEmblems[KVersionControlPlugin::ConflictingVersion + 1]; +}; + +inline void DolphinFileItemDelegate::setMinimizedNameColumn(bool minimized) +{ + m_hasMinimizedNameColumn = minimized; +} + +inline bool DolphinFileItemDelegate::hasMinimizedNameColumn() const +{ + return m_hasMinimizedNameColumn; +} + +#endif diff --git a/src/views/dolphiniconsview.cpp b/src/views/dolphiniconsview.cpp new file mode 100644 index 000000000..636bdd66c --- /dev/null +++ b/src/views/dolphiniconsview.cpp @@ -0,0 +1,532 @@ +/*************************************************************************** + * Copyright (C) 2006-2009 by Peter Penz <[email protected]> * + * * + * 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 "dolphiniconsview.h" + +#include "dolphincategorydrawer.h" +#include "dolphinviewcontroller.h" +#include "settings/dolphinsettings.h" +#include "dolphinsortfilterproxymodel.h" +#include "dolphin_iconsmodesettings.h" +#include "dolphin_generalsettings.h" +#include "draganddrophelper.h" +#include "selectionmanager.h" +#include "viewextensionsfactory.h" +#include "viewmodecontroller.h" +#include "zoomlevelinfo.h" + +#include <kcategorizedsortfilterproxymodel.h> +#include <kdialog.h> +#include <kfileitemdelegate.h> + +#include <QAbstractProxyModel> +#include <QApplication> +#include <QScrollBar> + +DolphinIconsView::DolphinIconsView(QWidget* parent, + DolphinViewController* dolphinViewController, + const ViewModeController* viewModeController, + DolphinSortFilterProxyModel* proxyModel) : + KCategorizedView(parent), + m_dolphinViewController(dolphinViewController), + m_viewModeController(viewModeController), + m_categoryDrawer(new DolphinCategoryDrawer(this)), + m_extensionsFactory(0), + m_font(), + m_decorationSize(), + m_decorationPosition(QStyleOptionViewItem::Top), + m_displayAlignment(Qt::AlignHCenter), + m_itemSize(), + m_dropRect() +{ + Q_ASSERT(dolphinViewController != 0); + Q_ASSERT(viewModeController != 0); + + setModel(proxyModel); + setLayoutDirection(Qt::LeftToRight); + setViewMode(QListView::IconMode); + setResizeMode(QListView::Adjust); + setMovement(QListView::Static); + setDragEnabled(true); + setEditTriggers(QAbstractItemView::NoEditTriggers); + viewport()->setAcceptDrops(true); + + setMouseTracking(true); + + connect(this, SIGNAL(clicked(const QModelIndex&)), + dolphinViewController, SLOT(requestTab(const QModelIndex&))); + if (KGlobalSettings::singleClick()) { + connect(this, SIGNAL(clicked(const QModelIndex&)), + dolphinViewController, SLOT(triggerItem(const QModelIndex&))); + } else { + connect(this, SIGNAL(doubleClicked(const QModelIndex&)), + dolphinViewController, SLOT(triggerItem(const QModelIndex&))); + } + + connect(this, SIGNAL(entered(const QModelIndex&)), + dolphinViewController, SLOT(emitItemEntered(const QModelIndex&))); + connect(this, SIGNAL(viewportEntered()), + dolphinViewController, SLOT(emitViewportEntered())); + connect(viewModeController, SIGNAL(zoomLevelChanged(int)), + this, SLOT(setZoomLevel(int))); + + const DolphinView* view = dolphinViewController->view(); + connect(view, SIGNAL(showPreviewChanged()), + this, SLOT(slotShowPreviewChanged())); + connect(view, SIGNAL(additionalInfoChanged()), + this, SLOT(slotAdditionalInfoChanged())); + + // apply the icons mode settings to the widget + const IconsModeSettings* settings = DolphinSettings::instance().iconsModeSettings(); + Q_ASSERT(settings != 0); + + if (settings->useSystemFont()) { + m_font = KGlobalSettings::generalFont(); + } else { + m_font = QFont(settings->fontFamily(), + qRound(settings->fontSize()), + settings->fontWeight(), + settings->italicFont()); + m_font.setPointSizeF(settings->fontSize()); + } + + setWordWrap(settings->numberOfTextlines() > 1); + + if (settings->arrangement() == QListView::TopToBottom) { + setFlow(QListView::LeftToRight); + m_decorationPosition = QStyleOptionViewItem::Top; + m_displayAlignment = Qt::AlignHCenter; + } else { + setFlow(QListView::TopToBottom); + m_decorationPosition = QStyleOptionViewItem::Left; + m_displayAlignment = Qt::AlignLeft | Qt::AlignVCenter; + } + + connect(m_categoryDrawer, SIGNAL(actionRequested(int,QModelIndex)), this, SLOT(categoryDrawerActionRequested(int,QModelIndex))); + setCategoryDrawer(m_categoryDrawer); + + setFocus(); + + connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)), + this, SLOT(slotGlobalSettingsChanged(int))); + + updateGridSize(view->showPreview(), 0); + m_extensionsFactory = new ViewExtensionsFactory(this, dolphinViewController, viewModeController); +} + +DolphinIconsView::~DolphinIconsView() +{ +} + +void DolphinIconsView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + KCategorizedView::dataChanged(topLeft, bottomRight); + + KCategorizedSortFilterProxyModel* proxyModel = dynamic_cast<KCategorizedSortFilterProxyModel*>(model()); + if (!proxyModel->isCategorizedModel()) { + // bypass a QListView issue that items are not layout correctly if the decoration size of + // an index changes + scheduleDelayedItemsLayout(); + } +} + +QStyleOptionViewItem DolphinIconsView::viewOptions() const +{ + QStyleOptionViewItem viewOptions = KCategorizedView::viewOptions(); + viewOptions.font = m_font; + viewOptions.fontMetrics = QFontMetrics(m_font); + viewOptions.decorationPosition = m_decorationPosition; + viewOptions.decorationSize = m_decorationSize; + viewOptions.displayAlignment = m_displayAlignment; + viewOptions.showDecorationSelected = true; + return viewOptions; +} + +void DolphinIconsView::contextMenuEvent(QContextMenuEvent* event) +{ + KCategorizedView::contextMenuEvent(event); + m_dolphinViewController->triggerContextMenuRequest(event->pos()); +} + +void DolphinIconsView::mousePressEvent(QMouseEvent* event) +{ + m_dolphinViewController->requestActivation(); + const QModelIndex index = indexAt(event->pos()); + if (index.isValid() && (event->button() == Qt::LeftButton)) { + // TODO: It should not be necessary to manually set the dragging state, but I could + // not reproduce this issue with a Qt-only example yet to find the root cause. + // Issue description: start Dolphin, split the view and drag an item from the + // inactive view to the active view by a very fast mouse movement. Result: + // the item gets selected instead of being dragged... + setState(QAbstractItemView::DraggingState); + } + + if (!index.isValid() && (QApplication::mouseButtons() & Qt::MidButton)) { + m_dolphinViewController->replaceUrlByClipboard(); + } + + KCategorizedView::mousePressEvent(event); +} + +void DolphinIconsView::startDrag(Qt::DropActions supportedActions) +{ + DragAndDropHelper::instance().startDrag(this, supportedActions, m_dolphinViewController); +} + +void DolphinIconsView::dragEnterEvent(QDragEnterEvent* event) +{ + if (DragAndDropHelper::instance().isMimeDataSupported(event->mimeData())) { + event->acceptProposedAction(); + } +} + +void DolphinIconsView::dragLeaveEvent(QDragLeaveEvent* event) +{ + KCategorizedView::dragLeaveEvent(event); + setDirtyRegion(m_dropRect); +} + +void DolphinIconsView::dragMoveEvent(QDragMoveEvent* event) +{ + KCategorizedView::dragMoveEvent(event); + + // TODO: remove this code when the issue #160611 is solved in Qt 4.4 + const QModelIndex index = indexAt(event->pos()); + setDirtyRegion(m_dropRect); + + m_dropRect.setSize(QSize()); // set as invalid + if (index.isValid()) { + const KFileItem item = m_dolphinViewController->itemForIndex(index); + if (!item.isNull() && item.isDir()) { + m_dropRect = visualRect(index); + } else { + m_dropRect.setSize(QSize()); // set as invalid + } + } + if (DragAndDropHelper::instance().isMimeDataSupported(event->mimeData())) { + // accept url drops, independently from the destination item + event->acceptProposedAction(); + } + + setDirtyRegion(m_dropRect); +} + +void DolphinIconsView::dropEvent(QDropEvent* event) +{ + const QModelIndex index = indexAt(event->pos()); + const KFileItem item = m_dolphinViewController->itemForIndex(index); + m_dolphinViewController->indicateDroppedUrls(item, m_viewModeController->url(), event); + // don't call KCategorizedView::dropEvent(event), as it moves + // the items which is not wanted +} + +QModelIndex DolphinIconsView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) +{ + const QModelIndex oldCurrent = currentIndex(); + + QModelIndex newCurrent = KCategorizedView::moveCursor(cursorAction, modifiers); + if (newCurrent != oldCurrent) { + return newCurrent; + } + + // The cursor has not been moved by the base implementation. Provide a + // wrap behavior, so that the cursor will go to the next item when reaching + // the border. + const IconsModeSettings* settings = DolphinSettings::instance().iconsModeSettings(); + if (settings->arrangement() == QListView::LeftToRight) { + switch (cursorAction) { + case MoveUp: + if (newCurrent.row() == 0) { + return newCurrent; + } + newCurrent = KCategorizedView::moveCursor(MoveLeft, modifiers); + selectionModel()->setCurrentIndex(newCurrent, QItemSelectionModel::NoUpdate); + newCurrent = KCategorizedView::moveCursor(MovePageDown, modifiers); + break; + + case MoveDown: + if (newCurrent.row() == (model()->rowCount() - 1)) { + return newCurrent; + } + newCurrent = KCategorizedView::moveCursor(MovePageUp, modifiers); + selectionModel()->setCurrentIndex(newCurrent, QItemSelectionModel::NoUpdate); + newCurrent = KCategorizedView::moveCursor(MoveRight, modifiers); + break; + + default: + break; + } + } else { + QModelIndex current = oldCurrent; + switch (cursorAction) { + case MoveLeft: + if (newCurrent.row() == 0) { + return newCurrent; + } + newCurrent = KCategorizedView::moveCursor(MoveUp, modifiers); + do { + selectionModel()->setCurrentIndex(newCurrent, QItemSelectionModel::NoUpdate); + current = newCurrent; + newCurrent = KCategorizedView::moveCursor(MoveRight, modifiers); + } while (newCurrent != current); + break; + + case MoveRight: + if (newCurrent.row() == (model()->rowCount() - 1)) { + return newCurrent; + } + do { + selectionModel()->setCurrentIndex(newCurrent, QItemSelectionModel::NoUpdate); + current = newCurrent; + newCurrent = KCategorizedView::moveCursor(MoveLeft, modifiers); + } while (newCurrent != current); + newCurrent = KCategorizedView::moveCursor(MoveDown, modifiers); + break; + + default: + break; + } + } + + // Revert all changes of the current item to make sure that item selection works correctly + selectionModel()->setCurrentIndex(oldCurrent, QItemSelectionModel::NoUpdate); + return newCurrent; +} + +void DolphinIconsView::keyPressEvent(QKeyEvent* event) +{ + KCategorizedView::keyPressEvent(event); + m_dolphinViewController->handleKeyPressEvent(event); +} + +void DolphinIconsView::wheelEvent(QWheelEvent* event) +{ + horizontalScrollBar()->setSingleStep(m_itemSize.width() / 5); + verticalScrollBar()->setSingleStep(m_itemSize.height() / 5); + + KCategorizedView::wheelEvent(event); + // if the icons are aligned left to right, the vertical wheel event should + // be applied to the horizontal scrollbar + const IconsModeSettings* settings = DolphinSettings::instance().iconsModeSettings(); + const bool scrollHorizontal = (event->orientation() == Qt::Vertical) && + (settings->arrangement() == QListView::LeftToRight); + if (scrollHorizontal) { + QWheelEvent horizEvent(event->pos(), + event->delta(), + event->buttons(), + event->modifiers(), + Qt::Horizontal); + QApplication::sendEvent(horizontalScrollBar(), &horizEvent); + } +} + +void DolphinIconsView::showEvent(QShowEvent* event) +{ + KFileItemDelegate* delegate = dynamic_cast<KFileItemDelegate*>(itemDelegate()); + delegate->setMaximumSize(m_itemSize); + + KCategorizedView::showEvent(event); +} + +void DolphinIconsView::leaveEvent(QEvent* event) +{ + KCategorizedView::leaveEvent(event); + // if the mouse is above an item and moved very fast outside the widget, + // no viewportEntered() signal might be emitted although the mouse has been moved + // above the viewport + m_dolphinViewController->emitViewportEntered(); +} + +void DolphinIconsView::currentChanged(const QModelIndex& current, const QModelIndex& previous) +{ + KCategorizedView::currentChanged(current, previous); + m_extensionsFactory->handleCurrentIndexChange(current, previous); +} + +void DolphinIconsView::resizeEvent(QResizeEvent* event) +{ + KCategorizedView::resizeEvent(event); + const DolphinView* view = m_dolphinViewController->view(); + updateGridSize(view->showPreview(), view->additionalInfo().count()); +} + +void DolphinIconsView::slotShowPreviewChanged() +{ + const DolphinView* view = m_dolphinViewController->view(); + updateGridSize(view->showPreview(), additionalInfoCount()); +} + +void DolphinIconsView::slotAdditionalInfoChanged() +{ + const DolphinView* view = m_dolphinViewController->view(); + const bool showPreview = view->showPreview(); + updateGridSize(showPreview, view->additionalInfo().count()); +} + +void DolphinIconsView::setZoomLevel(int level) +{ + IconsModeSettings* settings = DolphinSettings::instance().iconsModeSettings(); + + const int oldIconSize = settings->iconSize(); + int newIconSize = oldIconSize; + + const bool showPreview = m_dolphinViewController->view()->showPreview(); + if (showPreview) { + const int previewSize = ZoomLevelInfo::iconSizeForZoomLevel(level); + settings->setPreviewSize(previewSize); + } else { + newIconSize = ZoomLevelInfo::iconSizeForZoomLevel(level); + settings->setIconSize(newIconSize); + } + + // increase also the grid size + const int diff = newIconSize - oldIconSize; + settings->setItemWidth(settings->itemWidth() + diff); + settings->setItemHeight(settings->itemHeight() + diff); + + updateGridSize(showPreview, additionalInfoCount()); +} + +void DolphinIconsView::requestActivation() +{ + m_dolphinViewController->requestActivation(); +} + +void DolphinIconsView::slotGlobalSettingsChanged(int category) +{ + Q_UNUSED(category); + + const IconsModeSettings* settings = DolphinSettings::instance().iconsModeSettings(); + Q_ASSERT(settings != 0); + if (settings->useSystemFont()) { + m_font = KGlobalSettings::generalFont(); + } + + disconnect(this, SIGNAL(clicked(QModelIndex)), m_dolphinViewController, SLOT(triggerItem(QModelIndex))); + disconnect(this, SIGNAL(doubleClicked(QModelIndex)), m_dolphinViewController, SLOT(triggerItem(QModelIndex))); + if (KGlobalSettings::singleClick()) { + connect(this, SIGNAL(clicked(QModelIndex)), m_dolphinViewController, SLOT(triggerItem(QModelIndex))); + } else { + connect(this, SIGNAL(doubleClicked(QModelIndex)), m_dolphinViewController, SLOT(triggerItem(QModelIndex))); + } +} + +void DolphinIconsView::categoryDrawerActionRequested(int action, const QModelIndex &index) +{ + const QSortFilterProxyModel *model = dynamic_cast<const QSortFilterProxyModel*>(index.model()); + const QModelIndex topLeft = model->index(index.row(), modelColumn()); + QModelIndex bottomRight = topLeft; + const QString category = model->data(index, KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); + QModelIndex current = topLeft; + while (true) { + current = model->index(current.row() + 1, modelColumn()); + const QString curCategory = model->data(model->index(current.row(), index.column()), KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); + if (!current.isValid() || category != curCategory) { + break; + } + bottomRight = current; + } + switch (action) { + case DolphinCategoryDrawer::SelectAll: + selectionModel()->select(QItemSelection(topLeft, bottomRight), QItemSelectionModel::Select); + break; + case DolphinCategoryDrawer::UnselectAll: + selectionModel()->select(QItemSelection(topLeft, bottomRight), QItemSelectionModel::Deselect); + break; + default: + break; + } +} + +void DolphinIconsView::updateGridSize(bool showPreview, int additionalInfoCount) +{ + const IconsModeSettings* settings = DolphinSettings::instance().iconsModeSettings(); + Q_ASSERT(settings != 0); + + int itemWidth = settings->itemWidth(); + int itemHeight = settings->itemHeight(); + int size = settings->iconSize(); + + if (showPreview) { + const int previewSize = settings->previewSize(); + const int diff = previewSize - size; + itemWidth += diff; + itemHeight += diff; + + size = previewSize; + } + + Q_ASSERT(additionalInfoCount >= 0); + itemHeight += additionalInfoCount * QFontMetrics(m_font).height(); + + // Optimize the item size of the grid in a way to prevent large gaps on the + // right border (= row arrangement) or the bottom border (= column arrangement). + // There is no public API in QListView to find out the used width of the viewport + // for the layout. The following calculation of 'contentWidth'/'contentHeight' + // is based on QListViewPrivate::prepareItemsLayout() (Copyright (C) 2009 Nokia Corporation). + int frameAroundContents = 0; + if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) { + frameAroundContents = style()->pixelMetric(QStyle::PM_DefaultFrameWidth) * 2; + } + const int spacing = settings->gridSpacing(); + if (settings->arrangement() == QListView::TopToBottom) { + const int contentWidth = viewport()->width() - 1 + - frameAroundContents + - style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, horizontalScrollBar()); + const int gridWidth = itemWidth + spacing * 2; + const int horizItemCount = contentWidth / gridWidth; + if (horizItemCount > 0) { + itemWidth += (contentWidth - horizItemCount * gridWidth) / horizItemCount; + } + + // The decoration width indirectly defines the maximum + // width for the text wrapping. To use the maximum item width + // for text wrapping, it is used as decoration width. + m_decorationSize = QSize(itemWidth, size); + setIconSize(QSize(itemWidth, size)); + } else { + const int contentHeight = viewport()->height() - 1 + - frameAroundContents + - style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, verticalScrollBar()); + const int gridHeight = itemHeight + spacing; + const int vertItemCount = contentHeight / gridHeight; + if (vertItemCount > 0) { + itemHeight += (contentHeight - vertItemCount * gridHeight) / vertItemCount; + } + + m_decorationSize = QSize(size, size); + setIconSize(QSize(size, size)); + } + + m_itemSize = QSize(itemWidth, itemHeight); + setGridSizeOwn(QSize(itemWidth + spacing * 2, itemHeight + spacing)); + + KFileItemDelegate* delegate = dynamic_cast<KFileItemDelegate*>(itemDelegate()); + if (delegate != 0) { + delegate->setMaximumSize(m_itemSize); + } +} + +int DolphinIconsView::additionalInfoCount() const +{ + const DolphinView* view = m_dolphinViewController->view(); + return view->additionalInfo().count(); +} + +#include "dolphiniconsview.moc" diff --git a/src/views/dolphiniconsview.h b/src/views/dolphiniconsview.h new file mode 100644 index 000000000..21e37ba02 --- /dev/null +++ b/src/views/dolphiniconsview.h @@ -0,0 +1,122 @@ +/*************************************************************************** + * Copyright (C) 2006-2009 by Peter Penz <[email protected]> * + * * + * 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 DOLPHINICONSVIEW_H +#define DOLPHINICONSVIEW_H + +#include <kcategorizedview.h> + +#include <kfileitem.h> +#include <kfileitemdelegate.h> + +#include <QFont> +#include <QSize> +#include <QStyleOption> + +#include <libdolphin_export.h> + +class DolphinViewController; +class DolphinCategoryDrawer; +class DolphinSortFilterProxyModel; +class ViewExtensionsFactory; +class ViewModeController; + +/** + * @brief Represents the view, where each item is shown as an icon. + * + * It is also possible that instead of the icon a preview of the item + * content is shown. + */ +class LIBDOLPHINPRIVATE_EXPORT DolphinIconsView : public KCategorizedView +{ + Q_OBJECT + +public: + /** + * @param parent Parent widget. + * @param dolphinViewController Allows the DolphinIconsView to control the + * DolphinView in a limited way. + * @param viewModeController Controller that is used by the DolphinView + * to control the DolphinIconsView. The DolphinIconsView + * only has read access to the controller. + * @param model Directory that is shown. + */ + explicit DolphinIconsView(QWidget* parent, + DolphinViewController* dolphinViewController, + const ViewModeController* viewModeController, + DolphinSortFilterProxyModel* proxyModel); + virtual ~DolphinIconsView(); + +protected slots: + virtual void dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + +protected: + virtual QStyleOptionViewItem viewOptions() const; + virtual void contextMenuEvent(QContextMenuEvent* event); + virtual void mousePressEvent(QMouseEvent* event); + virtual void startDrag(Qt::DropActions supportedActions); + virtual void dragEnterEvent(QDragEnterEvent* event); + virtual void dragLeaveEvent(QDragLeaveEvent* event); + virtual void dragMoveEvent(QDragMoveEvent* event); + virtual void dropEvent(QDropEvent* event); + virtual QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers); + virtual void keyPressEvent(QKeyEvent* event); + virtual void wheelEvent(QWheelEvent* event); + virtual void showEvent(QShowEvent* event); + virtual void leaveEvent(QEvent* event); + virtual void currentChanged(const QModelIndex& current, const QModelIndex& previous); + virtual void resizeEvent(QResizeEvent* event); + +private slots: + void slotShowPreviewChanged(); + void slotAdditionalInfoChanged(); + void setZoomLevel(int level); + void requestActivation(); + void slotGlobalSettingsChanged(int category); + void categoryDrawerActionRequested(int action, const QModelIndex &index); + +private: + /** + * Updates the size of the grid depending on the state + * of \a showPreview and \a additionalInfoCount. + */ + void updateGridSize(bool showPreview, int additionalInfoCount); + + /** + * Returns the number of additional information lines that should + * be shown below the item name. + */ + int additionalInfoCount() const; + +private: + DolphinViewController* m_dolphinViewController; + const ViewModeController* m_viewModeController; + DolphinCategoryDrawer* m_categoryDrawer; + ViewExtensionsFactory* m_extensionsFactory; + + QFont m_font; + QSize m_decorationSize; + QStyleOptionViewItem::Position m_decorationPosition; + Qt::Alignment m_displayAlignment; + + QSize m_itemSize; + QRect m_dropRect; +}; + +#endif diff --git a/src/views/dolphinview.cpp b/src/views/dolphinview.cpp new file mode 100644 index 000000000..73a0c63c3 --- /dev/null +++ b/src/views/dolphinview.cpp @@ -0,0 +1,1569 @@ +/*************************************************************************** + * Copyright (C) 2006-2009 by Peter Penz <[email protected]> * + * Copyright (C) 2006 by Gregor Kališnik <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "dolphinview.h" + +#include <QAbstractItemView> +#include <QApplication> +#include <QClipboard> +#include <QKeyEvent> +#include <QItemSelection> +#include <QBoxLayout> +#include <QTimer> +#include <QScrollBar> + +#include <kactioncollection.h> +#include <kcolorscheme.h> +#include <kdirlister.h> +#include <kiconeffect.h> +#include <kfileitem.h> +#include <klocale.h> +#include <kio/deletejob.h> +#include <kio/netaccess.h> +#include <kio/previewjob.h> +#include <kjob.h> +#include <kmenu.h> +#include <kmessagebox.h> +#include <kmimetyperesolver.h> +#include <konq_fileitemcapabilities.h> +#include <konq_operations.h> +#include <konqmimedata.h> +#include <kstringhandler.h> +#include <ktoggleaction.h> +#include <kurl.h> + +#include "additionalinfoaccessor.h" +#include "dolphinmodel.h" +#include "dolphincolumnviewcontainer.h" +#include "dolphinviewcontroller.h" +#include "dolphindetailsview.h" +#include "dolphinfileitemdelegate.h" +#include "dolphinnewmenuobserver.h" +#include "dolphinsortfilterproxymodel.h" +#include "dolphin_detailsmodesettings.h" +#include "dolphiniconsview.h" +#include "dolphin_generalsettings.h" +#include "draganddrophelper.h" +#include "renamedialog.h" +#include "settings/dolphinsettings.h" +#include "viewmodecontroller.h" +#include "viewproperties.h" +#include "zoomlevelinfo.h" +#include "dolphindetailsviewexpander.h" + +/** + * Helper function for sorting items with qSort() in + * DolphinView::renameSelectedItems(). + */ +bool lessThan(const KFileItem& item1, const KFileItem& item2) +{ + return KStringHandler::naturalCompare(item1.name(), item2.name()) < 0; +} + +DolphinView::DolphinView(QWidget* parent, + const KUrl& url, + DolphinSortFilterProxyModel* proxyModel) : + QWidget(parent), + m_active(true), + m_showPreview(false), + m_storedCategorizedSorting(false), + m_tabsForFiles(false), + m_isContextMenuOpen(false), + m_ignoreViewProperties(false), + m_assureVisibleCurrentIndex(false), + m_mode(DolphinView::IconsView), + m_topLayout(0), + m_dolphinViewController(0), + m_viewModeController(0), + m_viewAccessor(proxyModel), + m_selectionModel(0), + m_selectionChangedTimer(0), + m_rootUrl(), + m_activeItemUrl(), + m_restoredContentsPosition(), + m_createdItemUrl(), + m_selectedItems(), + m_newFileNames() +{ + m_topLayout = new QVBoxLayout(this); + m_topLayout->setSpacing(0); + m_topLayout->setMargin(0); + + m_dolphinViewController = new DolphinViewController(this); + + m_viewModeController = new ViewModeController(this); + m_viewModeController->setUrl(url); + + connect(m_viewModeController, SIGNAL(urlChanged(const KUrl&)), + this, SIGNAL(urlChanged(const KUrl&))); + + connect(m_dolphinViewController, SIGNAL(requestContextMenu(const QPoint&, const QList<QAction*>&)), + this, SLOT(openContextMenu(const QPoint&, const QList<QAction*>&))); + connect(m_dolphinViewController, SIGNAL(urlsDropped(const KFileItem&, const KUrl&, QDropEvent*)), + this, SLOT(dropUrls(const KFileItem&, const KUrl&, QDropEvent*))); + connect(m_dolphinViewController, SIGNAL(sortingChanged(DolphinView::Sorting)), + this, SLOT(updateSorting(DolphinView::Sorting))); + connect(m_dolphinViewController, SIGNAL(sortOrderChanged(Qt::SortOrder)), + this, SLOT(updateSortOrder(Qt::SortOrder))); + connect(m_dolphinViewController, SIGNAL(sortFoldersFirstChanged(bool)), + this, SLOT(updateSortFoldersFirst(bool))); + connect(m_dolphinViewController, SIGNAL(additionalInfoChanged(const KFileItemDelegate::InformationList&)), + this, SLOT(updateAdditionalInfo(const KFileItemDelegate::InformationList&))); + connect(m_dolphinViewController, SIGNAL(itemTriggered(const KFileItem&)), + this, SLOT(triggerItem(const KFileItem&))); + connect(m_dolphinViewController, SIGNAL(tabRequested(const KUrl&)), + this, SIGNAL(tabRequested(const KUrl&))); + connect(m_dolphinViewController, SIGNAL(activated()), + this, SLOT(activate())); + connect(m_dolphinViewController, SIGNAL(itemEntered(const KFileItem&)), + this, SLOT(showHoverInformation(const KFileItem&))); + connect(m_dolphinViewController, SIGNAL(viewportEntered()), + this, SLOT(clearHoverInformation())); + connect(m_dolphinViewController, SIGNAL(urlChangeRequested(KUrl)), + m_viewModeController, SLOT(setUrl(KUrl))); + + KDirLister* dirLister = m_viewAccessor.dirLister(); + connect(dirLister, SIGNAL(redirection(KUrl,KUrl)), + this, SLOT(slotRedirection(KUrl,KUrl))); + connect(dirLister, SIGNAL(completed()), + this, SLOT(slotDirListerCompleted())); + connect(dirLister, SIGNAL(refreshItems(const QList<QPair<KFileItem,KFileItem>>&)), + this, SLOT(slotRefreshItems())); + + // When a new item has been created by the "Create New..." menu, the item should + // get selected and it must be assured that the item will get visible. As the + // creation is done asynchronously, several signals must be checked: + connect(&DolphinNewMenuObserver::instance(), SIGNAL(itemCreated(const KUrl&)), + this, SLOT(observeCreatedItem(const KUrl&))); + + m_selectionChangedTimer = new QTimer(this); + m_selectionChangedTimer->setSingleShot(true); + m_selectionChangedTimer->setInterval(300); + connect(m_selectionChangedTimer, SIGNAL(timeout()), + this, SLOT(emitSelectionChangedSignal())); + + applyViewProperties(); + m_topLayout->addWidget(m_viewAccessor.layoutTarget()); +} + +DolphinView::~DolphinView() +{ +} + +KUrl DolphinView::url() const +{ + return m_viewModeController->url(); +} + +KUrl DolphinView::rootUrl() const +{ + const KUrl viewUrl = url(); + const KUrl root = m_viewAccessor.rootUrl(); + if (root.isEmpty() || !root.isParentOf(viewUrl)) { + return viewUrl; + } + return root; +} + +void DolphinView::setActive(bool active) +{ + if (active == m_active) { + return; + } + + m_active = active; + + QColor color = KColorScheme(QPalette::Active, KColorScheme::View).background().color(); + if (active) { + emitSelectionChangedSignal(); + } else { + color.setAlpha(150); + } + + QWidget* viewport = m_viewAccessor.itemView()->viewport(); + QPalette palette; + palette.setColor(viewport->backgroundRole(), color); + viewport->setPalette(palette); + + update(); + + if (active) { + m_viewAccessor.itemView()->setFocus(); + emit activated(); + } + + m_viewModeController->indicateActivationChange(active); +} + +bool DolphinView::isActive() const +{ + return m_active; +} + +void DolphinView::setMode(Mode mode) +{ + if (mode == m_mode) { + return; // the wished mode is already set + } + + const int oldZoomLevel = m_viewModeController->zoomLevel(); + m_mode = mode; + + // remember the currently selected items, so that they will + // be restored after reloading the directory + m_selectedItems = selectedItems(); + + deleteView(); + + const KUrl viewPropsUrl = rootUrl(); + ViewProperties props(viewPropsUrl); + props.setViewMode(m_mode); + createView(); + + // the file item delegate has been recreated, apply the current + // additional information manually + const KFileItemDelegate::InformationList infoList = props.additionalInfo(); + m_viewAccessor.itemDelegate()->setShowInformation(infoList); + emit additionalInfoChanged(); + + // Not all view modes support categorized sorting. Adjust the sorting model + // if changing the view mode results in a change of the categorized sorting + // capabilities. + m_storedCategorizedSorting = props.categorizedSorting(); + const bool categorized = m_storedCategorizedSorting && supportsCategorizedSorting(); + if (categorized != m_viewAccessor.proxyModel()->isCategorizedModel()) { + m_viewAccessor.proxyModel()->setCategorizedModel(categorized); + emit categorizedSortingChanged(); + } + + emit modeChanged(); + + updateZoomLevel(oldZoomLevel); + loadDirectory(viewPropsUrl); +} + +DolphinView::Mode DolphinView::mode() const +{ + return m_mode; +} + +bool DolphinView::showPreview() const +{ + return m_showPreview; +} + +bool DolphinView::showHiddenFiles() const +{ + return m_viewAccessor.dirLister()->showingDotFiles(); +} + +bool DolphinView::categorizedSorting() const +{ + // If all view modes would support categorized sorting, returning + // m_viewAccessor.proxyModel()->isCategorizedModel() would be the way to go. As + // currently only the icons view supports caterized sorting, we remember + // the stored view properties state in m_storedCategorizedSorting and + // return this state. The application takes care to disable the corresponding + // checkbox by checking DolphinView::supportsCategorizedSorting() to indicate + // that this setting is not applied to the current view mode. + return m_storedCategorizedSorting; +} + +bool DolphinView::supportsCategorizedSorting() const +{ + return m_viewAccessor.supportsCategorizedSorting(); +} + +bool DolphinView::hasSelection() const +{ + const QAbstractItemView* view = m_viewAccessor.itemView(); + return (view != 0) && view->selectionModel()->hasSelection(); +} + +void DolphinView::markUrlsAsSelected(const QList<KUrl>& urls) +{ + foreach (const KUrl& url, urls) { + KFileItem item(KFileItem::Unknown, KFileItem::Unknown, url); + m_selectedItems.append(item); + } +} + +KFileItemList DolphinView::selectedItems() const +{ + KFileItemList itemList; + const QAbstractItemView* view = m_viewAccessor.itemView(); + if (view == 0) { + return itemList; + } + + const QItemSelection selection = m_viewAccessor.proxyModel()->mapSelectionToSource(view->selectionModel()->selection()); + + const QModelIndexList indexList = selection.indexes(); + foreach (const QModelIndex &index, indexList) { + KFileItem item = m_viewAccessor.dirModel()->itemForIndex(index); + if (!item.isNull()) { + itemList.append(item); + } + } + + return itemList; +} + +KUrl::List DolphinView::selectedUrls() const +{ + KUrl::List urls; + const KFileItemList list = selectedItems(); + foreach (const KFileItem &item, list) { + urls.append(item.url()); + } + return urls; +} + +int DolphinView::selectedItemsCount() const +{ + const QAbstractItemView* view = m_viewAccessor.itemView(); + if (view == 0) { + return 0; + } + + return view->selectionModel()->selectedIndexes().count(); +} + +QItemSelectionModel* DolphinView::selectionModel() const +{ + return m_viewAccessor.itemView()->selectionModel(); +} + +void DolphinView::setZoomLevel(int level) +{ + if (level < ZoomLevelInfo::minimumLevel()) { + level = ZoomLevelInfo::minimumLevel(); + } else if (level > ZoomLevelInfo::maximumLevel()) { + level = ZoomLevelInfo::maximumLevel(); + } + + if (level != zoomLevel()) { + m_viewModeController->setZoomLevel(level); + emit zoomLevelChanged(level); + } +} + +int DolphinView::zoomLevel() const +{ + return m_viewModeController->zoomLevel(); +} + +void DolphinView::setSorting(Sorting sorting) +{ + if (sorting != this->sorting()) { + updateSorting(sorting); + } +} + +DolphinView::Sorting DolphinView::sorting() const +{ + return m_viewAccessor.proxyModel()->sorting(); +} + +void DolphinView::setSortOrder(Qt::SortOrder order) +{ + if (sortOrder() != order) { + updateSortOrder(order); + } +} + +Qt::SortOrder DolphinView::sortOrder() const +{ + return m_viewAccessor.proxyModel()->sortOrder(); +} + +void DolphinView::setSortFoldersFirst(bool foldersFirst) +{ + if (sortFoldersFirst() != foldersFirst) { + updateSortFoldersFirst(foldersFirst); + } +} + +bool DolphinView::sortFoldersFirst() const +{ + return m_viewAccessor.proxyModel()->sortFoldersFirst(); +} + +void DolphinView::setAdditionalInfo(KFileItemDelegate::InformationList info) +{ + const KUrl viewPropsUrl = rootUrl(); + ViewProperties props(viewPropsUrl); + props.setAdditionalInfo(info); + m_viewAccessor.itemDelegate()->setShowInformation(info); + + emit additionalInfoChanged(); + + if (m_viewAccessor.reloadOnAdditionalInfoChange()) { + loadDirectory(viewPropsUrl); + } +} + +KFileItemDelegate::InformationList DolphinView::additionalInfo() const +{ + return m_viewAccessor.itemDelegate()->showInformation(); +} + +void DolphinView::reload() +{ + QByteArray viewState; + QDataStream saveStream(&viewState, QIODevice::WriteOnly); + saveState(saveStream); + m_selectedItems= selectedItems(); + + setUrl(url()); + loadDirectory(url(), true); + + QDataStream restoreStream(viewState); + restoreState(restoreStream); +} + +void DolphinView::refresh() +{ + m_ignoreViewProperties = false; + + const bool oldActivationState = m_active; + const int oldZoomLevel = m_viewModeController->zoomLevel(); + m_active = true; + + createView(); + applyViewProperties(); + reload(); + + setActive(oldActivationState); + updateZoomLevel(oldZoomLevel); +} + +void DolphinView::setNameFilter(const QString& nameFilter) +{ + m_viewModeController->setNameFilter(nameFilter); +} + +void DolphinView::calculateItemCount(int& fileCount, + int& folderCount, + KIO::filesize_t& totalFileSize) const +{ + foreach (const KFileItem& item, m_viewAccessor.dirLister()->items()) { + if (item.isDir()) { + ++folderCount; + } else { + ++fileCount; + totalFileSize += item.size(); + } + } +} + +QString DolphinView::statusBarText() const +{ + QString text; + int folderCount = 0; + int fileCount = 0; + KIO::filesize_t totalFileSize = 0; + + if (hasSelection()) { + // give a summary of the status of the selected files + const KFileItemList list = selectedItems(); + if (list.isEmpty()) { + // when an item is triggered, it is temporary selected but selectedItems() + // will return an empty list + return text; + } + + KFileItemList::const_iterator it = list.begin(); + const KFileItemList::const_iterator end = list.end(); + while (it != end) { + const KFileItem& item = *it; + if (item.isDir()) { + ++folderCount; + } else { + ++fileCount; + totalFileSize += item.size(); + } + ++it; + } + + if (folderCount + fileCount == 1) { + // if only one item is selected, show the filename + const QString name = list.first().text(); + text = (folderCount == 1) ? i18nc("@info:status", "<filename>%1</filename> selected", name) : + i18nc("@info:status", "<filename>%1</filename> selected (%2)", + name, KIO::convertSize(totalFileSize)); + } else { + // at least 2 items are selected + const QString foldersText = i18ncp("@info:status", "1 Folder selected", "%1 Folders selected", folderCount); + const QString filesText = i18ncp("@info:status", "1 File selected", "%1 Files selected", fileCount); + if ((folderCount > 0) && (fileCount > 0)) { + text = i18nc("@info:status folders, files (size)", "%1, %2 (%3)", + foldersText, filesText, KIO::convertSize(totalFileSize)); + } else if (fileCount > 0) { + text = i18nc("@info:status files (size)", "%1 (%2)", filesText, KIO::convertSize(totalFileSize)); + } else { + Q_ASSERT(folderCount > 0); + text = foldersText; + } + } + } else { + calculateItemCount(fileCount, folderCount, totalFileSize); + text = KIO::itemsSummaryString(fileCount + folderCount, + fileCount, folderCount, + totalFileSize, true); + } + + return text; +} + +QList<QAction*> DolphinView::versionControlActions(const KFileItemList& items) const +{ + return m_dolphinViewController->versionControlActions(items); +} + +void DolphinView::setUrl(const KUrl& url) +{ + if (m_viewModeController->url() != url) { + m_newFileNames.clear(); + + m_viewModeController->setUrl(url); // emits urlChanged, which we forward + m_viewAccessor.prepareUrlChange(url); + applyViewProperties(); + loadDirectory(url); + + // When changing the URL there is no need to keep the version + // data of the previous URL. + m_viewAccessor.dirModel()->clearVersionData(); + + emit startedPathLoading(url); + } + + // the selection model might have changed in the case of a column view + QItemSelectionModel* selectionModel = m_viewAccessor.itemView()->selectionModel(); + if (m_selectionModel != selectionModel) { + disconnect(m_selectionModel, SIGNAL(selectionChanged(QItemSelection, QItemSelection)), + this, SLOT(slotSelectionChanged(QItemSelection, QItemSelection))); + m_selectionModel = selectionModel; + connect(m_selectionModel, SIGNAL(selectionChanged(QItemSelection, QItemSelection)), + this, SLOT(slotSelectionChanged(QItemSelection, QItemSelection))); + } +} + +void DolphinView::selectAll() +{ + m_viewAccessor.itemView()->selectAll(); +} + +void DolphinView::invertSelection() +{ + QItemSelectionModel* selectionModel = m_viewAccessor.itemView()->selectionModel(); + const QAbstractItemModel* itemModel = selectionModel->model(); + + const QModelIndex topLeft = itemModel->index(0, 0); + const QModelIndex bottomRight = itemModel->index(itemModel->rowCount() - 1, + itemModel->columnCount() - 1); + + const QItemSelection selection(topLeft, bottomRight); + selectionModel->select(selection, QItemSelectionModel::Toggle); +} + +void DolphinView::clearSelection() +{ + m_viewAccessor.itemView()->clearSelection(); +} + +void DolphinView::renameSelectedItems() +{ + KFileItemList items = selectedItems(); + const int itemCount = items.count(); + if (itemCount < 1) { + return; + } + + if (itemCount > 1) { + // More than one item has been selected for renaming. Open + // a rename dialog and rename all items afterwards. + QPointer<RenameDialog> dialog = new RenameDialog(this, items); + if (dialog->exec() == QDialog::Rejected) { + delete dialog; + return; + } + + const QString newName = dialog->newName(); + if (newName.isEmpty()) { + emit errorMessage(dialog->errorString()); + delete dialog; + return; + } + delete dialog; + + // the selection would be invalid after renaming the items, so just clear + // it before + clearSelection(); + + // TODO: check how this can be integrated into KIO::FileUndoManager/KonqOperations + // as one operation instead of n rename operations like it is done now... + Q_ASSERT(newName.contains('#')); + + // currently the items are sorted by the selection order, resort + // them by the file name + qSort(items.begin(), items.end(), lessThan); + + // iterate through all selected items and rename them... + int index = 1; + foreach (const KFileItem& item, items) { + const KUrl& oldUrl = item.url(); + QString number; + number.setNum(index++); + + QString name = newName; + name.replace('#', number); + + if (oldUrl.fileName() != name) { + KUrl newUrl = oldUrl; + newUrl.setFileName(name); + KonqOperations::rename(this, oldUrl, newUrl); + } + } + } else if (DolphinSettings::instance().generalSettings()->renameInline()) { + Q_ASSERT(itemCount == 1); + const QModelIndex dirIndex = m_viewAccessor.dirModel()->indexForItem(items.first()); + const QModelIndex proxyIndex = m_viewAccessor.proxyModel()->mapFromSource(dirIndex); + m_viewAccessor.itemView()->edit(proxyIndex); + } else { + Q_ASSERT(itemCount == 1); + + QPointer<RenameDialog> dialog = new RenameDialog(this, items); + if (dialog->exec() == QDialog::Rejected) { + delete dialog; + return; + } + + const QString newName = dialog->newName(); + if (newName.isEmpty()) { + emit errorMessage(dialog->errorString()); + delete dialog; + return; + } + delete dialog; + + const KUrl& oldUrl = items.first().url(); + KUrl newUrl = oldUrl; + newUrl.setFileName(newName); + KonqOperations::rename(this, oldUrl, newUrl); + } + + // assure that the current index remains visible when KDirLister + // will notify the view about changed items + m_assureVisibleCurrentIndex = true; +} + +void DolphinView::trashSelectedItems() +{ + const KUrl::List list = simplifiedSelectedUrls(); + KonqOperations::del(this, KonqOperations::TRASH, list); +} + +void DolphinView::deleteSelectedItems() +{ + const KUrl::List list = simplifiedSelectedUrls(); + const bool del = KonqOperations::askDeleteConfirmation(list, + KonqOperations::DEL, + KonqOperations::DEFAULT_CONFIRMATION, + this); + + if (del) { + KIO::Job* job = KIO::del(list); + connect(job, SIGNAL(result(KJob*)), + this, SLOT(slotDeleteFileFinished(KJob*))); + } +} + +void DolphinView::cutSelectedItems() +{ + QMimeData* mimeData = selectionMimeData(); + KonqMimeData::addIsCutSelection(mimeData, true); + QApplication::clipboard()->setMimeData(mimeData); +} + +void DolphinView::copySelectedItems() +{ + QMimeData* mimeData = selectionMimeData(); + QApplication::clipboard()->setMimeData(mimeData); +} + +void DolphinView::paste() +{ + pasteToUrl(url()); +} + +void DolphinView::pasteIntoFolder() +{ + const KFileItemList items = selectedItems(); + if ((items.count() == 1) && items.first().isDir()) { + pasteToUrl(items.first().url()); + } +} + +void DolphinView::setShowPreview(bool show) +{ + if (m_showPreview == show) { + return; + } + + const KUrl viewPropsUrl = rootUrl(); + ViewProperties props(viewPropsUrl); + props.setShowPreview(show); + + m_showPreview = show; + const int oldZoomLevel = m_viewModeController->zoomLevel(); + emit showPreviewChanged(); + + // Enabling or disabling the preview might change the icon size of the view. + // As the view does not emit a signal when the icon size has been changed, + // the used zoom level of the controller must be adjusted manually: + updateZoomLevel(oldZoomLevel); +} + +void DolphinView::setShowHiddenFiles(bool show) +{ + if (m_viewAccessor.dirLister()->showingDotFiles() == show) { + return; + } + + const KUrl viewPropsUrl = rootUrl(); + ViewProperties props(viewPropsUrl); + props.setShowHiddenFiles(show); + + m_viewAccessor.dirLister()->setShowingDotFiles(show); + emit showHiddenFilesChanged(); +} + +void DolphinView::setCategorizedSorting(bool categorized) +{ + if (categorized == categorizedSorting()) { + return; + } + + // setCategorizedSorting(true) may only get invoked + // if the view supports categorized sorting + Q_ASSERT(!categorized || supportsCategorizedSorting()); + + ViewProperties props(rootUrl()); + props.setCategorizedSorting(categorized); + props.save(); + + m_storedCategorizedSorting = categorized; + m_viewAccessor.proxyModel()->setCategorizedModel(categorized); + + emit categorizedSortingChanged(); +} + +void DolphinView::toggleSortOrder() +{ + const Qt::SortOrder order = (sortOrder() == Qt::AscendingOrder) ? + Qt::DescendingOrder : + Qt::AscendingOrder; + setSortOrder(order); +} + +void DolphinView::toggleSortFoldersFirst() +{ + setSortFoldersFirst(!sortFoldersFirst()); +} + +void DolphinView::toggleAdditionalInfo(QAction* action) +{ + const KFileItemDelegate::Information info = + static_cast<KFileItemDelegate::Information>(action->data().toInt()); + + KFileItemDelegate::InformationList list = additionalInfo(); + + const bool show = action->isChecked(); + + const int index = list.indexOf(info); + const bool containsInfo = (index >= 0); + if (show && !containsInfo) { + list.append(info); + setAdditionalInfo(list); + } else if (!show && containsInfo) { + list.removeAt(index); + setAdditionalInfo(list); + Q_ASSERT(list.indexOf(info) < 0); + } +} + +void DolphinView::mouseReleaseEvent(QMouseEvent* event) +{ + QWidget::mouseReleaseEvent(event); + setActive(true); +} + +bool DolphinView::eventFilter(QObject* watched, QEvent* event) +{ + switch (event->type()) { + case QEvent::FocusIn: + if (watched == m_viewAccessor.itemView()) { + m_dolphinViewController->requestActivation(); + } + break; + + case QEvent::DragEnter: + if (watched == m_viewAccessor.itemView()->viewport()) { + setActive(true); + } + break; + + case QEvent::KeyPress: + if (watched == m_viewAccessor.itemView()) { + // clear the selection when Escape has been pressed + QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); + if (keyEvent->key() == Qt::Key_Escape) { + clearSelection(); + } + } + break; + + case QEvent::Wheel: + if (watched == m_viewAccessor.itemView()->viewport()) { + // Ctrl+wheel events should cause icon zooming, but not if the left mouse button is pressed + // (the user is probably trying to scroll during a selection in that case) + QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event); + if (wheelEvent->modifiers() & Qt::ControlModifier && !(wheelEvent->buttons() & Qt::LeftButton)) { + const int delta = wheelEvent->delta(); + const int level = zoomLevel(); + if (delta > 0) { + setZoomLevel(level + 1); + } else if (delta < 0) { + setZoomLevel(level - 1); + } + return true; + } + } + break; + + default: + break; + } + + return QWidget::eventFilter(watched, event); +} + +void DolphinView::activate() +{ + setActive(true); +} + +void DolphinView::triggerItem(const KFileItem& item) +{ + const Qt::KeyboardModifiers modifier = QApplication::keyboardModifiers(); + if ((modifier & Qt::ShiftModifier) || (modifier & Qt::ControlModifier)) { + // items are selected by the user, hence don't trigger the + // item specified by 'index' + return; + } + + // TODO: the m_isContextMenuOpen check is a workaround for Qt-issue 207192 + if (item.isNull() || m_isContextMenuOpen) { + return; + } + + emit itemTriggered(item); // caught by DolphinViewContainer or DolphinPart +} + +void DolphinView::slotSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + const int count = selectedItemsCount(); + const bool selectionStateChanged = ((count > 0) && (selected.count() == count)) || + ((count == 0) && !deselected.isEmpty()); + + // If nothing has been selected before and something got selected (or if something + // was selected before and now nothing is selected) the selectionChangedSignal must + // be emitted asynchronously as fast as possible to update the edit-actions. + m_selectionChangedTimer->setInterval(selectionStateChanged ? 0 : 300); + m_selectionChangedTimer->start(); +} + +void DolphinView::emitSelectionChangedSignal() +{ + emit selectionChanged(DolphinView::selectedItems()); +} + +void DolphinView::openContextMenu(const QPoint& pos, + const QList<QAction*>& customActions) +{ + KFileItem item; + const QModelIndex index = m_viewAccessor.itemView()->indexAt(pos); + if (index.isValid() && (index.column() == DolphinModel::Name)) { + const QModelIndex dolphinModelIndex = m_viewAccessor.proxyModel()->mapToSource(index); + item = m_viewAccessor.dirModel()->itemForIndex(dolphinModelIndex); + } + + m_isContextMenuOpen = true; // TODO: workaround for Qt-issue 207192 + emit requestContextMenu(item, url(), customActions); + m_isContextMenuOpen = false; +} + +void DolphinView::dropUrls(const KFileItem& destItem, + const KUrl& destPath, + QDropEvent* event) +{ + addNewFileNames(event->mimeData()); + DragAndDropHelper::instance().dropUrls(destItem, destPath, event, this); +} + +void DolphinView::updateSorting(DolphinView::Sorting sorting) +{ + ViewProperties props(rootUrl()); + props.setSorting(sorting); + + m_viewAccessor.proxyModel()->setSorting(sorting); + + emit sortingChanged(sorting); +} + +void DolphinView::updateSortOrder(Qt::SortOrder order) +{ + ViewProperties props(rootUrl()); + props.setSortOrder(order); + + m_viewAccessor.proxyModel()->setSortOrder(order); + + emit sortOrderChanged(order); +} + +void DolphinView::updateSortFoldersFirst(bool foldersFirst) +{ + ViewProperties props(rootUrl()); + props.setSortFoldersFirst(foldersFirst); + + m_viewAccessor.proxyModel()->setSortFoldersFirst(foldersFirst); + + emit sortFoldersFirstChanged(foldersFirst); +} + +void DolphinView::updateAdditionalInfo(const KFileItemDelegate::InformationList& info) +{ + ViewProperties props(rootUrl()); + props.setAdditionalInfo(info); + props.save(); + + m_viewAccessor.itemDelegate()->setShowInformation(info); + + emit additionalInfoChanged(); +} + +void DolphinView::updateAdditionalInfoActions(KActionCollection* collection) +{ + const AdditionalInfoAccessor& infoAccessor = AdditionalInfoAccessor::instance(); + + const KFileItemDelegate::InformationList checkedInfos = m_viewAccessor.itemDelegate()->showInformation(); + const KFileItemDelegate::InformationList infos = infoAccessor.keys(); + + const bool enable = (m_mode == DolphinView::DetailsView) || + (m_mode == DolphinView::IconsView); + + foreach (const KFileItemDelegate::Information& info, infos) { + const QString name = infoAccessor.actionCollectionName(info, AdditionalInfoAccessor::AdditionalInfoType); + QAction* action = collection->action(name); + Q_ASSERT(action != 0); + action->setEnabled(enable); + action->setChecked(checkedInfos.contains(info)); + } +} + +QPair<bool, QString> DolphinView::pasteInfo() const +{ + return KonqOperations::pasteInfo(url()); +} + +void DolphinView::setTabsForFilesEnabled(bool tabsForFiles) +{ + m_tabsForFiles = tabsForFiles; +} + +bool DolphinView::isTabsForFilesEnabled() const +{ + return m_tabsForFiles; +} + +bool DolphinView::itemsExpandable() const +{ + return m_viewAccessor.itemsExpandable(); +} + +void DolphinView::restoreState(QDataStream& stream) +{ + // current item + stream >> m_activeItemUrl; + + // view position + stream >> m_restoredContentsPosition; + + // expanded folders (only relevant for the details view - will be ignored by the view in other view modes) + QSet<KUrl> urlsToExpand; + stream >> urlsToExpand; + const DolphinDetailsViewExpander* expander = m_viewAccessor.setExpandedUrls(urlsToExpand); + if (expander != 0) { + m_expanderActive = true; + connect (expander, SIGNAL(completed()), this, SLOT(slotLoadingCompleted())); + } + else { + m_expanderActive = false; + } +} + +void DolphinView::saveState(QDataStream& stream) +{ + // current item + KFileItem currentItem; + const QAbstractItemView* view = m_viewAccessor.itemView(); + + if (view != 0) { + const QModelIndex proxyIndex = view->currentIndex(); + const QModelIndex dirModelIndex = m_viewAccessor.proxyModel()->mapToSource(proxyIndex); + currentItem = m_viewAccessor.dirModel()->itemForIndex(dirModelIndex); + } + + KUrl currentUrl; + if (!currentItem.isNull()) { + currentUrl = currentItem.url(); + } + + stream << currentUrl; + + // view position + const int x = view->horizontalScrollBar()->value(); + const int y = view->verticalScrollBar()->value(); + stream << QPoint(x, y); + + // expanded folders (only relevant for the details view - the set will be empty in other view modes) + stream << m_viewAccessor.expandedUrls(); +} + +void DolphinView::observeCreatedItem(const KUrl& url) +{ + m_createdItemUrl = url; + connect(m_viewAccessor.dirModel(), SIGNAL(rowsInserted(const QModelIndex&, int, int)), + this, SLOT(selectAndScrollToCreatedItem())); +} + +void DolphinView::selectAndScrollToCreatedItem() +{ + const QModelIndex dirIndex = m_viewAccessor.dirModel()->indexForUrl(m_createdItemUrl); + if (dirIndex.isValid()) { + const QModelIndex proxyIndex = m_viewAccessor.proxyModel()->mapFromSource(dirIndex); + m_viewAccessor.itemView()->setCurrentIndex(proxyIndex); + } + + disconnect(m_viewAccessor.dirModel(), SIGNAL(rowsInserted(const QModelIndex&, int, int)), + this, SLOT(selectAndScrollToCreatedItem())); + m_createdItemUrl = KUrl(); +} + +void DolphinView::showHoverInformation(const KFileItem& item) +{ + emit requestItemInfo(item); +} + +void DolphinView::clearHoverInformation() +{ + emit requestItemInfo(KFileItem()); +} + +void DolphinView::slotDeleteFileFinished(KJob* job) +{ + if (job->error() == 0) { + emit operationCompletedMessage(i18nc("@info:status", "Delete operation completed.")); + } else if (job->error() != KIO::ERR_USER_CANCELED) { + emit errorMessage(job->errorString()); + } +} + +void DolphinView::slotDirListerCompleted() +{ + if (!m_expanderActive) { + slotLoadingCompleted(); + } + + if (!m_newFileNames.isEmpty()) { + // select all newly added items created by a paste operation or + // a drag & drop operation, and clear the previous selection + m_viewAccessor.itemView()->clearSelection(); + const int rowCount = m_viewAccessor.proxyModel()->rowCount(); + QItemSelection selection; + for (int row = 0; row < rowCount; ++row) { + const QModelIndex proxyIndex = m_viewAccessor.proxyModel()->index(row, 0); + const QModelIndex dirIndex = m_viewAccessor.proxyModel()->mapToSource(proxyIndex); + const KUrl url = m_viewAccessor.dirModel()->itemForIndex(dirIndex).url(); + if (m_newFileNames.contains(url.fileName())) { + selection.merge(QItemSelection(proxyIndex, proxyIndex), QItemSelectionModel::Select); + } + } + m_viewAccessor.itemView()->selectionModel()->select(selection, QItemSelectionModel::Select); + + m_newFileNames.clear(); + } +} + +void DolphinView::slotLoadingCompleted() +{ + m_expanderActive = false; + + if (!m_activeItemUrl.isEmpty()) { + // assure that the current item remains visible + const QModelIndex dirIndex = m_viewAccessor.dirModel()->indexForUrl(m_activeItemUrl); + if (dirIndex.isValid()) { + const QModelIndex proxyIndex = m_viewAccessor.proxyModel()->mapFromSource(dirIndex); + QAbstractItemView* view = m_viewAccessor.itemView(); + const bool clearSelection = !hasSelection(); + view->setCurrentIndex(proxyIndex); + if (clearSelection) { + view->clearSelection(); + } + m_activeItemUrl.clear(); + } + } + + if (!m_selectedItems.isEmpty()) { + const KUrl& baseUrl = url(); + KUrl url; + QItemSelection newSelection; + foreach(const KFileItem& item, m_selectedItems) { + url = item.url().upUrl(); + if (baseUrl.equals(url, KUrl::CompareWithoutTrailingSlash)) { + QModelIndex index = m_viewAccessor.proxyModel()->mapFromSource(m_viewAccessor.dirModel()->indexForItem(item)); + newSelection.select(index, index); + } + } + m_viewAccessor.itemView()->selectionModel()->select(newSelection, + QItemSelectionModel::ClearAndSelect + | QItemSelectionModel::Current); + m_selectedItems.clear(); + } + + // Restore the contents position. This has to be done using a Qt::QueuedConnection + // because the view might not be in its final state yet. + QMetaObject::invokeMethod(this, "restoreContentsPosition", Qt::QueuedConnection); +} + +void DolphinView::slotRefreshItems() +{ + if (m_assureVisibleCurrentIndex) { + m_assureVisibleCurrentIndex = false; + m_viewAccessor.itemView()->scrollTo(m_viewAccessor.itemView()->currentIndex()); + } +} + +void DolphinView::loadDirectory(const KUrl& url, bool reload) +{ + if (!url.isValid()) { + const QString location(url.pathOrUrl()); + if (location.isEmpty()) { + emit errorMessage(i18nc("@info:status", "The location is empty.")); + } else { + emit errorMessage(i18nc("@info:status", "The location '%1' is invalid.", location)); + } + return; + } + + KDirLister* dirLister = m_viewAccessor.dirLister(); + dirLister->openUrl(url, reload ? KDirLister::Reload : KDirLister::NoFlags); +} + +void DolphinView::applyViewProperties() +{ + if (m_ignoreViewProperties) { + return; + } + + const ViewProperties props(rootUrl()); + + const Mode mode = props.viewMode(); + if (m_mode != mode) { + const int oldZoomLevel = m_viewModeController->zoomLevel(); + + m_mode = mode; + createView(); + emit modeChanged(); + + updateZoomLevel(oldZoomLevel); + } + if (m_viewAccessor.itemView() == 0) { + createView(); + } + Q_ASSERT(m_viewAccessor.itemView() != 0); + Q_ASSERT(m_viewAccessor.itemDelegate() != 0); + + const bool showHiddenFiles = props.showHiddenFiles(); + if (showHiddenFiles != m_viewAccessor.dirLister()->showingDotFiles()) { + m_viewAccessor.dirLister()->setShowingDotFiles(showHiddenFiles); + emit showHiddenFilesChanged(); + } + + m_storedCategorizedSorting = props.categorizedSorting(); + const bool categorized = m_storedCategorizedSorting && supportsCategorizedSorting(); + if (categorized != m_viewAccessor.proxyModel()->isCategorizedModel()) { + m_viewAccessor.proxyModel()->setCategorizedModel(categorized); + emit categorizedSortingChanged(); + } + + const DolphinView::Sorting sorting = props.sorting(); + if (sorting != m_viewAccessor.proxyModel()->sorting()) { + m_viewAccessor.proxyModel()->setSorting(sorting); + emit sortingChanged(sorting); + } + + const Qt::SortOrder sortOrder = props.sortOrder(); + if (sortOrder != m_viewAccessor.proxyModel()->sortOrder()) { + m_viewAccessor.proxyModel()->setSortOrder(sortOrder); + emit sortOrderChanged(sortOrder); + } + + const bool sortFoldersFirst = props.sortFoldersFirst(); + if (sortFoldersFirst != m_viewAccessor.proxyModel()->sortFoldersFirst()) { + m_viewAccessor.proxyModel()->setSortFoldersFirst(sortFoldersFirst); + emit sortFoldersFirstChanged(sortFoldersFirst); + } + + KFileItemDelegate::InformationList info = props.additionalInfo(); + if (info != m_viewAccessor.itemDelegate()->showInformation()) { + m_viewAccessor.itemDelegate()->setShowInformation(info); + emit additionalInfoChanged(); + } + + const bool showPreview = props.showPreview(); + if (showPreview != m_showPreview) { + m_showPreview = showPreview; + const int oldZoomLevel = m_viewModeController->zoomLevel(); + emit showPreviewChanged(); + + // Enabling or disabling the preview might change the icon size of the view. + // As the view does not emit a signal when the icon size has been changed, + // the used zoom level of the controller must be adjusted manually: + updateZoomLevel(oldZoomLevel); + } + + if (DolphinSettings::instance().generalSettings()->globalViewProps()) { + // During the lifetime of a DolphinView instance the global view properties + // should not be changed. This allows e. g. to split a view and use different + // view properties for each view. + m_ignoreViewProperties = true; + } +} + +void DolphinView::createView() +{ + deleteView(); + + Q_ASSERT(m_viewAccessor.itemView() == 0); + m_viewAccessor.createView(this, m_dolphinViewController, m_viewModeController, m_mode); + + QAbstractItemView* view = m_viewAccessor.itemView(); + Q_ASSERT(view != 0); + view->installEventFilter(this); + view->viewport()->installEventFilter(this); + + m_dolphinViewController->setItemView(view); + + // When changing the view mode, the selection is lost due to reinstantiating + // a new item view with a custom selection model. Pass the ownership of the + // selection model to DolphinView, so that it can be shared by all item views. + if (m_selectionModel != 0) { + view->setSelectionModel(m_selectionModel); + } else { + m_selectionModel = view->selectionModel(); + } + m_selectionModel->setParent(this); + connect(m_selectionModel, SIGNAL(selectionChanged(QItemSelection, QItemSelection)), + this, SLOT(slotSelectionChanged(QItemSelection, QItemSelection))); + + setFocusProxy(m_viewAccessor.layoutTarget()); + m_topLayout->insertWidget(1, m_viewAccessor.layoutTarget()); +} + +void DolphinView::deleteView() +{ + QAbstractItemView* view = m_viewAccessor.itemView(); + if (view != 0) { + // It's important to set the keyboard focus to the parent + // before deleting the view: Otherwise when having a split + // view the other view will get the focus and will request + // an activation (see DolphinView::eventFilter()). + setFocusProxy(0); + setFocus(); + + m_topLayout->removeWidget(view); + view->close(); + + // disconnect all signal/slots + disconnect(view); + m_viewModeController->disconnect(view); + view->disconnect(); + + m_viewAccessor.deleteView(); + } +} + +void DolphinView::pasteToUrl(const KUrl& url) +{ + addNewFileNames(QApplication::clipboard()->mimeData()); + KonqOperations::doPaste(this, url); +} + +void DolphinView::updateZoomLevel(int oldZoomLevel) +{ + const int newZoomLevel = ZoomLevelInfo::zoomLevelForIconSize(m_viewAccessor.itemView()->iconSize()); + if (oldZoomLevel != newZoomLevel) { + m_viewModeController->setZoomLevel(newZoomLevel); + emit zoomLevelChanged(newZoomLevel); + } +} + +KUrl::List DolphinView::simplifiedSelectedUrls() const +{ + KUrl::List list = selectedUrls(); + if (itemsExpandable() ) { + list = KDirModel::simplifiedUrlList(list); + } + return list; +} + +QMimeData* DolphinView::selectionMimeData() const +{ + const QAbstractItemView* view = m_viewAccessor.itemView(); + Q_ASSERT((view != 0) && (view->selectionModel() != 0)); + const QItemSelection selection = m_viewAccessor.proxyModel()->mapSelectionToSource(view->selectionModel()->selection()); + return m_viewAccessor.dirModel()->mimeData(selection.indexes()); +} + +void DolphinView::addNewFileNames(const QMimeData* mimeData) +{ + const KUrl::List urls = KUrl::List::fromMimeData(mimeData); + foreach (const KUrl& url, urls) { + m_newFileNames.insert(url.fileName()); + } +} + +DolphinView::ViewAccessor::ViewAccessor(DolphinSortFilterProxyModel* proxyModel) : + m_iconsView(0), + m_detailsView(0), + m_columnsContainer(0), + m_proxyModel(proxyModel), + m_dragSource(0) +{ +} + +DolphinView::ViewAccessor::~ViewAccessor() +{ + delete m_dragSource; + m_dragSource = 0; +} + +void DolphinView::ViewAccessor::createView(QWidget* parent, + DolphinViewController* dolphinViewController, + const ViewModeController* viewModeController, + Mode mode) +{ + Q_ASSERT(itemView() == 0); + + switch (mode) { + case IconsView: + m_iconsView = new DolphinIconsView(parent, + dolphinViewController, + viewModeController, + m_proxyModel); + break; + + case DetailsView: + m_detailsView = new DolphinDetailsView(parent, + dolphinViewController, + viewModeController, + m_proxyModel); + break; + + case ColumnView: + m_columnsContainer = new DolphinColumnViewContainer(parent, + dolphinViewController, + viewModeController); + break; + + default: + Q_ASSERT(false); + } +} + +void DolphinView::ViewAccessor::deleteView() +{ + QAbstractItemView* view = itemView(); + if (view != 0) { + if (DragAndDropHelper::instance().isDragSource(view)) { + // The view is a drag source (the feature "Open folders + // during drag operations" is used). Deleting the view + // during an ongoing drag operation is not allowed, so + // this will postponed. + if (m_dragSource != 0) { + // the old stored view is obviously not the drag source anymore + m_dragSource->deleteLater(); + m_dragSource = 0; + } + view->hide(); + m_dragSource = view; + } else { + view->deleteLater(); + } + } + + m_iconsView = 0; + m_detailsView = 0; + + if (m_columnsContainer != 0) { + m_columnsContainer->deleteLater(); + } + m_columnsContainer = 0; +} + + +void DolphinView::ViewAccessor::prepareUrlChange(const KUrl& url) +{ + if (m_columnsContainer != 0) { + m_columnsContainer->showColumn(url); + } +} + +QAbstractItemView* DolphinView::ViewAccessor::itemView() const +{ + if (m_iconsView != 0) { + return m_iconsView; + } + + if (m_detailsView != 0) { + return m_detailsView; + } + + if (m_columnsContainer != 0) { + return m_columnsContainer->activeColumn(); + } + + return 0; +} + +KFileItemDelegate* DolphinView::ViewAccessor::itemDelegate() const +{ + return static_cast<KFileItemDelegate*>(itemView()->itemDelegate()); +} + +QWidget* DolphinView::ViewAccessor::layoutTarget() const +{ + if (m_columnsContainer != 0) { + return m_columnsContainer; + } + return itemView(); +} + +KUrl DolphinView::ViewAccessor::rootUrl() const +{ + return (m_columnsContainer != 0) ? m_columnsContainer->rootUrl() : KUrl(); +} + +bool DolphinView::ViewAccessor::supportsCategorizedSorting() const +{ + return m_iconsView != 0; +} + +bool DolphinView::ViewAccessor::itemsExpandable() const +{ + return (m_detailsView != 0) && m_detailsView->itemsExpandable(); +} + + +QSet<KUrl> DolphinView::ViewAccessor::expandedUrls() const +{ + if (m_detailsView != 0) { + return m_detailsView->expandedUrls(); + } + + return QSet<KUrl>(); +} + +const DolphinDetailsViewExpander* DolphinView::ViewAccessor::setExpandedUrls(const QSet<KUrl>& urlsToExpand) +{ + if ((m_detailsView != 0) && m_detailsView->itemsExpandable() && !urlsToExpand.isEmpty()) { + // Check if another expander is already active and stop it if necessary. + if(!m_detailsViewExpander.isNull()) { + m_detailsViewExpander->stop(); + } + + m_detailsViewExpander = new DolphinDetailsViewExpander(m_detailsView, urlsToExpand); + return m_detailsViewExpander; + } + else { + return 0; + } +} + +bool DolphinView::ViewAccessor::reloadOnAdditionalInfoChange() const +{ + // the details view requires no reloading of the directory, as it maps + // the file item delegate info to its columns internally + return m_detailsView != 0; +} + +DolphinModel* DolphinView::ViewAccessor::dirModel() const +{ + return static_cast<DolphinModel*>(proxyModel()->sourceModel()); +} + +DolphinSortFilterProxyModel* DolphinView::ViewAccessor::proxyModel() const +{ + if (m_columnsContainer != 0) { + return static_cast<DolphinSortFilterProxyModel*>(m_columnsContainer->activeColumn()->model()); + } + return m_proxyModel; +} + +KDirLister* DolphinView::ViewAccessor::dirLister() const +{ + return dirModel()->dirLister(); +} + +void DolphinView::slotRedirection(const KUrl& oldUrl, const KUrl& newUrl) +{ + if (oldUrl.equals(url(), KUrl::CompareWithoutTrailingSlash)) { + emit redirection(oldUrl, newUrl); + m_viewModeController->redirectToUrl(newUrl); // #186947 + } +} + +void DolphinView::restoreContentsPosition() +{ + if (!m_restoredContentsPosition.isNull()) { + const int x = m_restoredContentsPosition.x(); + const int y = m_restoredContentsPosition.y(); + m_restoredContentsPosition = QPoint(); + + QAbstractItemView* view = m_viewAccessor.itemView(); + Q_ASSERT(view != 0); + view->horizontalScrollBar()->setValue(x); + view->verticalScrollBar()->setValue(y); + } +} + +#include "dolphinview.moc" diff --git a/src/views/dolphinview.h b/src/views/dolphinview.h new file mode 100644 index 000000000..dbef511bf --- /dev/null +++ b/src/views/dolphinview.h @@ -0,0 +1,818 @@ +/*************************************************************************** + * Copyright (C) 2006-2009 by Peter Penz <[email protected]> * + * Copyright (C) 2006 by Gregor Kališnik <[email protected]> * + * * + * 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 DOLPHINVIEW_H +#define DOLPHINVIEW_H + +#include <config-nepomuk.h> + +#include "libdolphin_export.h" + +#include <kparts/part.h> +#include <kfileitem.h> +#include <kfileitemdelegate.h> +#include <kio/fileundomanager.h> +#include <kio/job.h> + +#include <QBoxLayout> +#include <QKeyEvent> +#include <QLinkedList> +#include <QListView> +#include <QSet> +#include <QWidget> + +typedef KIO::FileUndoManager::CommandType CommandType; + +class DolphinColumnViewContainer; +class DolphinDetailsView; +class DolphinIconsView; +class DolphinModel; +class DolphinSortFilterProxyModel; +class DolphinViewController; +class KAction; +class KActionCollection; +class KDirLister; +class KUrl; +class ViewModeController; +class ViewProperties; +class DolphinDetailsViewExpander; + +/** + * @short Represents a view for the directory content. + * + * View modes for icons, details and columns are supported. It's + * possible to adjust: + * - sort order + * - sort type + * - show hidden files + * - show previews + * + * @see DolphinIconsView + * @see DolphinDetailsView + * @see DolphinColumnView + */ +class LIBDOLPHINPRIVATE_EXPORT DolphinView : public QWidget +{ + Q_OBJECT + +public: + /** + * Defines the view mode for a directory. The view mode + * can be defined when constructing a DolphinView. The + * view mode is automatically updated if the directory itself + * defines a view mode (see class ViewProperties for details). + */ + enum Mode + { + /** + * The directory items are shown as icons including an + * icon name. + */ + IconsView = 0, + + /** + * The icon, the name and at least the size of the directory + * items are shown in a table. It is possible to add columns + * for date, group and permissions. + */ + DetailsView = 1, + + /** + * Each folder is shown in a separate column. + */ + ColumnView = 2, + MaxModeEnum = ColumnView + }; + + /** Defines the sort order for the items of a directory. */ + enum Sorting + { + SortByName = 0, + SortBySize, + SortByDate, + SortByPermissions, + SortByOwner, + SortByGroup, + SortByType, + SortByDestination, + SortByPath, + MaxSortingEnum = SortByPath + }; + + /** + * @param parent Parent widget of the view. + * @param url Specifies the content which should be shown. + * @param proxyModel Used proxy model which specifies the sorting. The + * model is not owned by the view and won't get + * deleted. + */ + DolphinView(QWidget* parent, + const KUrl& url, + DolphinSortFilterProxyModel* proxyModel); + + virtual ~DolphinView(); + + /** + * Returns the current active URL, where all actions are applied. + * The URL navigator is synchronized with this URL. + */ + KUrl url() const; + + /** + * Returns the root URL of the view, which is defined as the first + * visible path of DolphinView::url(). Usually the root URL is + * equal to DolphinView::url(), but in the case of the column view + * when 2 columns are shown, the root URL might be: + * /home/peter/Documents + * and DolphinView::url() might return + * /home/peter/Documents/Music/ + */ + KUrl rootUrl() const; + + /** + * If \a active is true, the view will marked as active. The active + * view is defined as view where all actions are applied to. + */ + void setActive(bool active); + bool isActive() const; + + /** + * Changes the view mode for the current directory to \a mode. + * If the view properties should be remembered for each directory + * (GeneralSettings::globalViewProps() returns false), then the + * changed view mode will be stored automatically. + */ + void setMode(Mode mode); + Mode mode() const; + + /** See setShowPreview */ + bool showPreview() const; + + /** See setShowHiddenFiles */ + bool showHiddenFiles() const; + + /** See setCategorizedSorting */ + bool categorizedSorting() const; + + /** + * Returns true, if the categorized sorting is supported by the current + * used mode (see DolphinView::setMode()). Currently only DolphinView::IconsView + * supports categorizations. To check whether the categorized + * sorting is set, use DolphinView::categorizedSorting(). + */ + bool supportsCategorizedSorting() const; + + /** + * Marks the items indicated by \p urls to get selected after the + * directory DolphinView::url() has been loaded. Note that nothing + * gets selected if no loading of a directory has been triggered + * by DolphinView::setUrl() or DolphinView::reload(). + */ + void markUrlsAsSelected(const QList<KUrl>& urls); + + /** + * Returns the selected items. The list is empty if no item has been + * selected. + * @see DolphinView::selectedUrls() + */ + KFileItemList selectedItems() const; + + /** + * Returns a list of URLs for all selected items. An empty list + * is returned, if no item is selected. + * @see DolphinView::selectedItems() + */ + KUrl::List selectedUrls() const; + + /** + * Returns the number of selected items (this is faster than + * invoking selectedItems().count()). + */ + int selectedItemsCount() const; + + QItemSelectionModel* selectionModel() const; + + /** + * Sets the zoom level to \a level. It is assured that the used + * level is adjusted to be inside the range ZoomLevelInfo::minimumLevel() and + * ZoomLevelInfo::maximumLevel(). + */ + void setZoomLevel(int level); + int zoomLevel() const; + + /** + * Returns true, if zooming in is possible. If false is returned, + * the maximum zooming level has been reached. + */ + bool isZoomInPossible() const; + + /** + * Returns true, if zooming out is possible. If false is returned, + * the minimum zooming level has been reached. + */ + bool isZoomOutPossible() const; + + /** Sets the sort order of the items inside a directory (see DolphinView::Sorting). */ + void setSorting(Sorting sorting); + + /** Returns the sort order of the items inside a directory (see DolphinView::Sorting). */ + Sorting sorting() const; + + /** Sets the sort order (Qt::Ascending or Qt::Descending) for the items. */ + void setSortOrder(Qt::SortOrder order); + + /** Returns the current used sort order (Qt::Ascending or Qt::Descending). */ + Qt::SortOrder sortOrder() const; + + /** Sets a separate sorting with folders first (true) or a mixed sorting of files and folders (false). */ + void setSortFoldersFirst(bool foldersFirst); + + /** Returns if files and folders are sorted separately or not. */ + bool sortFoldersFirst() const; + + /** Sets the additional information which should be shown for the items. */ + void setAdditionalInfo(KFileItemDelegate::InformationList info); + + /** Returns the additional information which should be shown for the items. */ + KFileItemDelegate::InformationList additionalInfo() const; + + /** Reloads the current directory. */ + void reload(); + + /** + * Refreshes the view to get synchronized with the (updated) Dolphin settings. + * This method only needs to get invoked if the view settings for the Icons View, + * Details View or Columns View have been changed. + */ + void refresh(); + + /** + * Filters the currently shown items by \a nameFilter. All items + * which contain the given filter string will be shown. + */ + void setNameFilter(const QString& nameFilter); + + /** + * Calculates the number of currently shown files into + * \a fileCount and the number of folders into \a folderCount. + * The size of all files is written into \a totalFileSize. + * It is recommend using this method instead of asking the + * directory lister or the model directly, as it takes + * filtering and hierarchical previews into account. + */ + void calculateItemCount(int& fileCount, int& folderCount, KIO::filesize_t& totalFileSize) const; + + /** + * Returns a textual representation of the state of the current + * folder or selected items, suitable for use in the status bar. + */ + QString statusBarText() const; + + /** + * Returns the version control actions that are provided for the items \p items. + * Usually the actions are presented in the context menu. + */ + QList<QAction*> versionControlActions(const KFileItemList& items) const; + + /** + * Updates the state of the 'Additional Information' actions in \a collection. + */ + void updateAdditionalInfoActions(KActionCollection* collection); + + /** + * Returns the state of the paste action: + * first is whether the action should be enabled + * second is the text for the action + */ + QPair<bool, QString> pasteInfo() const; + + /** + * If \a tabsForFiles is true, the signal tabRequested() will also + * emitted also for files. Per default tabs for files is disabled + * and hence the signal tabRequested() will only be emitted for + * directories. + */ + void setTabsForFilesEnabled(bool tabsForFiles); + bool isTabsForFilesEnabled() const; + + /** + * Returns true if the current view allows folders to be expanded, + * i.e. presents a hierarchical view to the user. + */ + bool itemsExpandable() const; + + /** + * Restores the view state (current item, contents position, details view expansion state) + */ + void restoreState(QDataStream& stream); + + /** + * Saves the view state (current item, contents position, details view expansion state) + */ + void saveState(QDataStream& stream); + +public slots: + /** + * Changes the directory to \a url. If the current directory is equal to + * \a url, nothing will be done (use DolphinView::reload() instead). + */ + void setUrl(const KUrl& url); + + /** + * Selects all items. + * @see DolphinView::selectedItems() + */ + void selectAll(); + + /** + * Inverts the current selection: selected items get unselected, + * unselected items get selected. + * @see DolphinView::selectedItems() + */ + void invertSelection(); + + /** Returns true, if at least one item is selected. */ + bool hasSelection() const; + + void clearSelection(); + + /** + * Triggers the renaming of the currently selected items, where + * the user must input a new name for the items. + */ + void renameSelectedItems(); + + /** + * Moves all selected items to the trash. + */ + void trashSelectedItems(); + + /** + * Deletes all selected items. + */ + void deleteSelectedItems(); + + /** + * Copies all selected items to the clipboard and marks + * the items as cut. + */ + void cutSelectedItems(); + + /** Copies all selected items to the clipboard. */ + void copySelectedItems(); + + /** Pastes the clipboard data to this view. */ + void paste(); + + /** + * Pastes the clipboard data into the currently selected + * folder. If the current selection is not exactly one folder, no + * paste operation is done. + */ + void pasteIntoFolder(); + + /** + * Turns on the file preview for the all files of the current directory, + * if \a show is true. + * If the view properties should be remembered for each directory + * (GeneralSettings::globalViewProps() returns false), then the + * preview setting will be stored automatically. + */ + void setShowPreview(bool show); + + /** + * Shows all hidden files of the current directory, + * if \a show is true. + * If the view properties should be remembered for each directory + * (GeneralSettings::globalViewProps() returns false), then the + * show hidden file setting will be stored automatically. + */ + void setShowHiddenFiles(bool show); + + /** + * Summarizes all sorted items by their category \a categorized + * is true. + * If the view properties should be remembered for each directory + * (GeneralSettings::globalViewProps() returns false), then the + * categorized sorting setting will be stored automatically. + */ + void setCategorizedSorting(bool categorized); + + /** Switches between an ascending and descending sorting order. */ + void toggleSortOrder(); + + /** Switches between a separate sorting (with folders first) and a mixed sorting of files and folders. */ + void toggleSortFoldersFirst(); + + /** + * Switches on or off the displaying of additional information + * as specified by \a action. + */ + void toggleAdditionalInfo(QAction* action); + +signals: + /** + * Is emitted if the view has been activated by e. g. a mouse click. + */ + void activated(); + + /** Is emitted if URL of the view has been changed to \a url. */ + void urlChanged(const KUrl& url); + + /** + * Is emitted when clicking on an item with the left mouse button. + */ + void itemTriggered(const KFileItem& item); + + /** + * Is emitted if a new tab should be opened for the URL \a url. + */ + void tabRequested(const KUrl& url); + + /** + * Is emitted if the view mode (IconsView, DetailsView, + * PreviewsView) has been changed. + */ + void modeChanged(); + + /** Is emitted if the 'show preview' property has been changed. */ + void showPreviewChanged(); + + /** Is emitted if the 'show hidden files' property has been changed. */ + void showHiddenFilesChanged(); + + /** Is emitted if the 'categorized sorting' property has been changed. */ + void categorizedSortingChanged(); + + /** Is emitted if the sorting by name, size or date has been changed. */ + void sortingChanged(DolphinView::Sorting sorting); + + /** Is emitted if the sort order (ascending or descending) has been changed. */ + void sortOrderChanged(Qt::SortOrder order); + + /** Is emitted if the sorting of files and folders (separate with folders first or mixed) has been changed. */ + void sortFoldersFirstChanged(bool foldersFirst); + + /** Is emitted if the additional information shown for this view has been changed. */ + void additionalInfoChanged(); + + /** Is emitted if the zoom level has been changed by zooming in or out. */ + void zoomLevelChanged(int level); + + /** + * Is emitted if information of an item is requested to be shown e. g. in the panel. + * If item is null, no item information request is pending. + */ + void requestItemInfo(const KFileItem& item); + + /** + * Is emitted whenever the selection has been changed. + */ + void selectionChanged(const KFileItemList& selection); + + /** + * Is emitted if a context menu is requested for the item \a item, + * which is part of \a url. If the item is null, the context menu + * for the URL should be shown and the custom actions \a customActions + * will be added. + */ + void requestContextMenu(const KFileItem& item, + const KUrl& url, + const QList<QAction*>& customActions); + + /** + * Is emitted if an information message with the content \a msg + * should be shown. + */ + void infoMessage(const QString& msg); + + /** + * Is emitted if an error message with the content \a msg + * should be shown. + */ + void errorMessage(const QString& msg); + + /** + * Is emitted if an "operation completed" message with the content \a msg + * should be shown. + */ + void operationCompletedMessage(const QString& msg); + + /** + * Is emitted after DolphinView::setUrl() has been invoked and + * the path \a url is currently loaded. If this signal is emitted, + * it is assured that the view contains already the correct root + * URL and property settings. + */ + void startedPathLoading(const KUrl& url); + + /** + * Emitted when KDirLister emits redirection. + * Testcase: fish://localhost + */ + void redirection(const KUrl& oldUrl, const KUrl& newUrl); + +protected: + /** @see QWidget::mouseReleaseEvent */ + virtual void mouseReleaseEvent(QMouseEvent* event); + virtual bool eventFilter(QObject* watched, QEvent* event); + +private slots: + /** + * Marks the view as active (DolphinView:isActive() will return true) + * and emits the 'activated' signal if it is not already active. + */ + void activate(); + + /** + * If the item \a item is a directory, then this + * directory will be loaded. If the item is a file, the corresponding + * application will get started. + */ + void triggerItem(const KFileItem& index); + + /** + * Emits the signal \a selectionChanged() with a small delay. This is + * because getting all file items for the signal can be an expensive + * operation. Fast selection changes are collected in this case and + * the signal is emitted only after no selection change has been done + * within a small delay. + */ + void slotSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + + /** + * Is called by emitDelayedSelectionChangedSignal() and emits the + * signal \a selectionChanged() with all selected file items as parameter. + */ + void emitSelectionChangedSignal(); + + /** + * Opens the context menu on position \a pos. The position + * is used to check whether the context menu is related to an + * item or to the viewport. + */ + void openContextMenu(const QPoint& pos, const QList<QAction*>& customActions); + + /** + * Drops dragged URLs to the destination path \a destPath. If + * the URLs are dropped above an item inside the destination path, + * the item is indicated by \a destItem. + */ + void dropUrls(const KFileItem& destItem, + const KUrl& destPath, + QDropEvent* event); + + /** + * Updates the view properties of the current URL to the + * sorting given by \a sorting. + */ + void updateSorting(DolphinView::Sorting sorting); + + /** + * Updates the view properties of the current URL to the + * sort order given by \a order. + */ + void updateSortOrder(Qt::SortOrder order); + + /** + * Updates the view properties of the current URL to the + * sorting of files and folders (separate with folders first or mixed) given by \a foldersFirst. + */ + void updateSortFoldersFirst(bool foldersFirst); + + /** + * Updates the view properties of the current URL to the + * additional information given by \a info. + */ + void updateAdditionalInfo(const KFileItemDelegate::InformationList& info); + + /** + * Updates the status bar to show hover information for the + * item \a item. If currently other items are selected, + * no hover information is shown. + * @see DolphinView::clearHoverInformation() + */ + void showHoverInformation(const KFileItem& item); + + /** + * Clears the hover information shown in the status bar. + * @see DolphinView::showHoverInformation(). + */ + void clearHoverInformation(); + + /** + * Indicates in the status bar that the delete operation + * of the job \a job has been finished. + */ + void slotDeleteFileFinished(KJob* job); + + /** + * Invoked when the directory lister has completed the loading of + * items. Assures that pasted items and renamed items get seleced. + */ + void slotDirListerCompleted(); + + /** + * Invoked when the loading of the directory is finished. + * Restores the active item and the scroll position if possible. + */ + void slotLoadingCompleted(); + + /** + * Is invoked when the KDirLister indicates refreshed items. + */ + void slotRefreshItems(); + + /** + * Observes the item with the URL \a url. As soon as the directory + * model indicates that the item is available, the item will + * get selected and it is assure that the item stays visible. + * + * @see selectAndScrollToCreatedItem() + */ + void observeCreatedItem(const KUrl& url); + + /** + * Selects and scrolls to the item that got observed + * by observeCreatedItem(). + */ + void selectAndScrollToCreatedItem(); + + /** + * Called when a redirection happens. + * Testcase: fish://localhost + */ + void slotRedirection(const KUrl& oldUrl, const KUrl& newUrl); + + /** + * Restores the contents position, if history information about the old position is available. + */ + void restoreContentsPosition(); + +private: + void loadDirectory(const KUrl& url, bool reload = false); + + /** + * Applies the view properties which are defined by the current URL + * to the DolphinView properties. + */ + void applyViewProperties(); + + /** + * Creates a new view representing the given view mode (DolphinView::mode()). + * The current view will get deleted. + */ + void createView(); + + void deleteView(); + + /** + * Helper method for DolphinView::paste() and DolphinView::pasteIntoFolder(). + * Pastes the clipboard data into the URL \a url. + */ + void pasteToUrl(const KUrl& url); + + /** + * Checks whether the current item view has the same zoom level + * as \a oldZoomLevel. If this is not the case, the zoom level + * of the controller is updated and a zoomLevelChanged() signal + * is emitted. + */ + void updateZoomLevel(int oldZoomLevel); + + /** + * Returns a list of URLs for all selected items. The list is + * simplified, so that when the URLs are part of different tree + * levels, only the parent is returned. + */ + KUrl::List simplifiedSelectedUrls() const; + + /** + * Returns the MIME data for all selected items. + */ + QMimeData* selectionMimeData() const; + + /** + * Is invoked after a paste operation or a drag & drop + * operation and adds the filenames of all URLs from \a mimeData to + * m_newFileNames. This allows to select all newly added + * items in slotDirListerCompleted(). + */ + void addNewFileNames(const QMimeData* mimeData); + +private: + /** + * Abstracts the access to the different view implementations + * for icons-, details- and column-view. + */ + class ViewAccessor + { + public: + ViewAccessor(DolphinSortFilterProxyModel* proxyModel); + ~ViewAccessor(); + + void createView(QWidget* parent, + DolphinViewController* dolphinViewController, + const ViewModeController* viewModeController, + Mode mode); + void deleteView(); + + /** + * Must be invoked before the URL has been changed and allows view implementations + * like the column view to create a new column. + */ + void prepareUrlChange(const KUrl& url); + + QAbstractItemView* itemView() const; + KFileItemDelegate* itemDelegate() const; + + /** + * Returns the widget that should be added to the layout as target. Usually + * the item view itself is returned, but in the case of the column view + * a container widget is returned. + */ + QWidget* layoutTarget() const; + + KUrl rootUrl() const; + + bool supportsCategorizedSorting() const; + bool itemsExpandable() const; + QSet<KUrl> expandedUrls() const; + const DolphinDetailsViewExpander* setExpandedUrls(const QSet<KUrl>& urlsToExpand); + + /** + * Returns true, if a reloading of the items is required + * when the additional information properties have been changed + * by the user. + */ + bool reloadOnAdditionalInfoChange() const; + + DolphinModel* dirModel() const; + DolphinSortFilterProxyModel* proxyModel() const; + KDirLister* dirLister() const; + + private: + DolphinIconsView* m_iconsView; + DolphinDetailsView* m_detailsView; + DolphinColumnViewContainer* m_columnsContainer; + DolphinSortFilterProxyModel* m_proxyModel; + QAbstractItemView* m_dragSource; + QPointer<DolphinDetailsViewExpander> m_detailsViewExpander; + }; + + bool m_active : 1; + bool m_showPreview : 1; + bool m_storedCategorizedSorting : 1; + bool m_tabsForFiles : 1; + bool m_isContextMenuOpen : 1; // TODO: workaround for Qt-issue 207192 + bool m_ignoreViewProperties : 1; + bool m_assureVisibleCurrentIndex : 1; + bool m_expanderActive : 1; + + Mode m_mode; + + QVBoxLayout* m_topLayout; + + DolphinViewController* m_dolphinViewController; + ViewModeController* m_viewModeController; + ViewAccessor m_viewAccessor; + + QItemSelectionModel* m_selectionModel; // allow to switch views without losing the selection + QTimer* m_selectionChangedTimer; + + KUrl m_rootUrl; + KUrl m_activeItemUrl; + QPoint m_restoredContentsPosition; + KUrl m_createdItemUrl; // URL for a new item that got created by the "Create New..." menu + KFileItemList m_selectedItems; // this is used for making the View to remember selections after F5 + + /** + * Remembers the filenames that have been added by a paste operation + * or a drag & drop operation. Allows to select the items in + * slotDirListerCompleted(). + */ + QSet<QString> m_newFileNames; +}; + +/// Allow using DolphinView::Mode in QVariant +Q_DECLARE_METATYPE(DolphinView::Mode) + +#endif // DOLPHINVIEW_H diff --git a/src/views/dolphinviewautoscroller.cpp b/src/views/dolphinviewautoscroller.cpp new file mode 100644 index 000000000..45896a5eb --- /dev/null +++ b/src/views/dolphinviewautoscroller.cpp @@ -0,0 +1,223 @@ +/*************************************************************************** + * Copyright (C) 2008 by Peter Penz <[email protected]> * + * * + * 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 "dolphinviewautoscroller.h" + +#include <QAbstractItemView> +#include <QApplication> +#include <QCursor> +#include <QEvent> +#include <QMouseEvent> +#include <QScrollBar> +#include <QTimer> +#include <math.h> + +DolphinViewAutoScroller::DolphinViewAutoScroller(QAbstractItemView* parent) : + QObject(parent), + m_rubberBandSelection(false), + m_keyPressed(false), + m_initializedTimestamp(false), + m_horizontalScrollInc(0), + m_verticalScrollInc(0), + m_itemView(parent), + m_timer(0), + m_timestamp() +{ + m_itemView->setAutoScroll(false); + m_itemView->viewport()->installEventFilter(this); + m_itemView->installEventFilter(this); + + m_timer = new QTimer(this); + m_timer->setSingleShot(false); + m_timer->setInterval(1000 / 25); // 25 frames per second + connect(m_timer, SIGNAL(timeout()), this, SLOT(scrollViewport())); +} + +DolphinViewAutoScroller::~DolphinViewAutoScroller() +{ +} + +bool DolphinViewAutoScroller::isActive() const +{ + return m_timer->isActive(); +} + +void DolphinViewAutoScroller::handleCurrentIndexChange(const QModelIndex& current, + const QModelIndex& previous) +{ + // When the autoscroller is inactive and a key has been pressed, it must be + // assured that the current item stays visible. The check whether the previous + // item is valid is important because of #197951. The keypress check is done + // because of #199833. + if (current.isValid() && (previous.isValid() || m_keyPressed) && !isActive()) { + m_itemView->scrollTo(current); + } +} + +bool DolphinViewAutoScroller::eventFilter(QObject* watched, QEvent* event) +{ + if (watched == m_itemView->viewport()) { + switch (event->type()) { + case QEvent::MouseButtonPress: + if (static_cast<QMouseEvent*>(event)->button() == Qt::LeftButton) { + m_rubberBandSelection = true; + } + break; + + case QEvent::MouseMove: + if (m_rubberBandSelection) { + triggerAutoScroll(); + } + break; + + case QEvent::MouseButtonRelease: + m_rubberBandSelection = false; + stopAutoScroll(); + break; + + case QEvent::DragEnter: + case QEvent::DragMove: + m_rubberBandSelection = false; + triggerAutoScroll(); + break; + + case QEvent::Drop: + case QEvent::DragLeave: + m_rubberBandSelection = false; + stopAutoScroll(); + break; + + default: + break; + } + } else if (watched == m_itemView) { + switch (event->type()) { + case QEvent::KeyPress: + m_keyPressed = true; + break; + + case QEvent::KeyRelease: + m_keyPressed = false; + break; + + default: + break; + } + } + + return QObject::eventFilter(watched, event); +} + +void DolphinViewAutoScroller::scrollViewport() +{ + if (m_timestamp.elapsed() < QApplication::startDragTime()) { + return; + } + + QScrollBar* verticalScrollBar = m_itemView->verticalScrollBar(); + if (verticalScrollBar != 0) { + const int value = verticalScrollBar->value(); + verticalScrollBar->setValue(value + m_verticalScrollInc); + + } + QScrollBar* horizontalScrollBar = m_itemView->horizontalScrollBar(); + if (horizontalScrollBar != 0) { + const int value = horizontalScrollBar->value(); + horizontalScrollBar->setValue(value + m_horizontalScrollInc); + + } + + if (m_rubberBandSelection) { + // The scrolling does not lead to an update of the rubberband + // selection. Fake a mouse move event to let the QAbstractItemView + // update the rubberband. + QWidget* viewport = m_itemView->viewport(); + const QPoint pos = viewport->mapFromGlobal(QCursor::pos()); + QMouseEvent event(QEvent::MouseMove, pos, Qt::LeftButton, Qt::LeftButton, QApplication::keyboardModifiers()); + QCoreApplication::sendEvent(viewport, &event); + } +} + +void DolphinViewAutoScroller::triggerAutoScroll() +{ + const bool verticalScrolling = (m_itemView->verticalScrollBar() != 0) && + m_itemView->verticalScrollBar()->isVisible(); + const bool horizontalScrolling = (m_itemView->horizontalScrollBar() != 0) && + m_itemView->horizontalScrollBar()->isVisible(); + if (!verticalScrolling && !horizontalScrolling) { + // no scrollbars are shown at all, so no autoscrolling is necessary + stopAutoScroll(); + return; + } + + QWidget* viewport = m_itemView->viewport(); + const QPoint pos = viewport->mapFromGlobal(QCursor::pos()); + if (verticalScrolling) { + m_verticalScrollInc = calculateScrollIncrement(pos.y(), viewport->height()); + } + if (horizontalScrolling) { + m_horizontalScrollInc = calculateScrollIncrement(pos.x(), viewport->width()); + } + + if (m_timer->isActive()) { + if ((m_horizontalScrollInc == 0) && (m_verticalScrollInc == 0)) { + stopAutoScroll(); + } + } else if ((m_horizontalScrollInc != 0) || (m_verticalScrollInc != 0)) { + if (!m_initializedTimestamp) { + m_initializedTimestamp = true; + m_timestamp.start(); + } + m_timer->start(); + } +} + +void DolphinViewAutoScroller::stopAutoScroll() +{ + m_timer->stop(); + m_horizontalScrollInc = 0; + m_verticalScrollInc = 0; + m_initializedTimestamp = false; +} + +int DolphinViewAutoScroller::calculateScrollIncrement(int cursorPos, int rangeSize) const +{ + int inc = 0; + + const int minSpeed = 4; + const int maxSpeed = 768; + const int speedLimiter = 48; + const int autoScrollBorder = 64; + + if (cursorPos < autoScrollBorder) { + inc = -minSpeed + qAbs(cursorPos - autoScrollBorder) * (cursorPos - autoScrollBorder) / speedLimiter; + if (inc < -maxSpeed) { + inc = -maxSpeed; + } + } else if (cursorPos > rangeSize - autoScrollBorder) { + inc = minSpeed + qAbs(cursorPos - rangeSize + autoScrollBorder) * (cursorPos - rangeSize + autoScrollBorder) / speedLimiter; + if (inc > maxSpeed) { + inc = maxSpeed; + } + } + + return inc; +} + +#include "dolphinviewautoscroller.moc" diff --git a/src/views/dolphinviewautoscroller.h b/src/views/dolphinviewautoscroller.h new file mode 100644 index 000000000..9fd35d494 --- /dev/null +++ b/src/views/dolphinviewautoscroller.h @@ -0,0 +1,79 @@ +/*************************************************************************** + * Copyright (C) 2008 by Peter Penz <[email protected]> * + * * + * 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 DOLPHINVIEWAUTOSCROLLER_H +#define DOLPHINVIEWAUTOSCROLLER_H + +#include <QTime> +#include <QObject> + +class QAbstractItemView; +class QModelIndex; +class QTimer; + +/** + * @brief Assures that an autoscrolling is done for item views. + * + * This is a workaround as QAbstractItemView::setAutoScroll() is not usable + * when selecting items (see Qt issue #214542). + */ +class DolphinViewAutoScroller : public QObject +{ + Q_OBJECT + +public: + DolphinViewAutoScroller(QAbstractItemView* parent); + virtual ~DolphinViewAutoScroller(); + bool isActive() const; + + /** + * Must be invoked by the parent item view, when QAbstractItemView::currentChanged() + * has been called. Assures that the current item stays visible when it has been + * changed by the keyboard. + */ + void handleCurrentIndexChange(const QModelIndex& current, const QModelIndex& previous); + +protected: + virtual bool eventFilter(QObject* watched, QEvent* event); + +private slots: + void scrollViewport(); + +private: + void triggerAutoScroll(); + void stopAutoScroll(); + + /** + * Calculates the scroll increment dependent from + * the cursor position \a cursorPos and the range 0 - \a rangeSize - 1. + */ + int calculateScrollIncrement(int cursorPos, int rangeSize) const; + +private: + bool m_rubberBandSelection; + bool m_keyPressed; + bool m_initializedTimestamp; + int m_horizontalScrollInc; + int m_verticalScrollInc; + QAbstractItemView* m_itemView; + QTimer* m_timer; + QTime m_timestamp; +}; + +#endif diff --git a/src/views/dolphinviewcontroller.cpp b/src/views/dolphinviewcontroller.cpp new file mode 100644 index 000000000..6ef32f07f --- /dev/null +++ b/src/views/dolphinviewcontroller.cpp @@ -0,0 +1,249 @@ +/*************************************************************************** + * Copyright (C) 2010 by Peter Penz <[email protected]> * + * * + * 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 "dolphinviewcontroller.h" +#include "zoomlevelinfo.h" + +#include <kdirmodel.h> +#include <kfileitemactions.h> +#include <QAbstractProxyModel> +#include <QApplication> +#include <QClipboard> +#include <QDir> + +Qt::MouseButtons DolphinViewController::m_mouseButtons = Qt::NoButton; + +DolphinViewController::DolphinViewController(DolphinView* dolphinView) : + QObject(dolphinView), + m_dolphinView(dolphinView), + m_itemView(0), + m_versionControlActions() +{ +} + +DolphinViewController::~DolphinViewController() +{ +} + +const DolphinView* DolphinViewController::view() const +{ + return m_dolphinView; +} + +void DolphinViewController::requestUrlChange(const KUrl& url) +{ + emit urlChangeRequested(url); +} + +void DolphinViewController::setItemView(QAbstractItemView* view) +{ + if (m_itemView != 0) { + disconnect(m_itemView, SIGNAL(pressed(const QModelIndex&)), + this, SLOT(updateMouseButtonState())); + } + + m_itemView = view; + + if (m_itemView != 0) { + // TODO: this is a workaround until Qt-issue 176832 has been fixed + connect(m_itemView, SIGNAL(pressed(const QModelIndex&)), + this, SLOT(updateMouseButtonState())); + } +} + +QAbstractItemView* DolphinViewController::itemView() const +{ + return m_itemView; +} + +void DolphinViewController::triggerContextMenuRequest(const QPoint& pos, + const QList<QAction*>& customActions) +{ + emit activated(); + emit requestContextMenu(pos, customActions); +} + +void DolphinViewController::requestActivation() +{ + emit activated(); +} + +void DolphinViewController::indicateDroppedUrls(const KFileItem& destItem, + const KUrl& destPath, + QDropEvent* event) +{ + emit urlsDropped(destItem, destPath, event); +} + + +void DolphinViewController::indicateSortingChange(DolphinView::Sorting sorting) +{ + emit sortingChanged(sorting); +} + +void DolphinViewController::indicateSortOrderChange(Qt::SortOrder order) +{ + emit sortOrderChanged(order); +} + +void DolphinViewController::indicateSortFoldersFirstChange(bool foldersFirst) +{ + emit sortFoldersFirstChanged(foldersFirst); +} + +void DolphinViewController::indicateAdditionalInfoChange(const KFileItemDelegate::InformationList& info) +{ + emit additionalInfoChanged(info); +} + +void DolphinViewController::setVersionControlActions(QList<QAction*> actions) +{ + m_versionControlActions = actions; +} + +QList<QAction*> DolphinViewController::versionControlActions(const KFileItemList& items) +{ + emit requestVersionControlActions(items); + // All view implementations are connected with the signal requestVersionControlActions() + // (see ViewExtensionFactory) and will invoke DolphinViewController::setVersionControlActions(), + // so that the context dependent actions can be returned. + return m_versionControlActions; +} + +void DolphinViewController::handleKeyPressEvent(QKeyEvent* event) +{ + Q_ASSERT(m_itemView != 0); + + const QItemSelectionModel* selModel = m_itemView->selectionModel(); + const QModelIndex currentIndex = selModel->currentIndex(); + const bool trigger = currentIndex.isValid() + && ((event->key() == Qt::Key_Return) || (event->key() == Qt::Key_Enter)) + && !selModel->selectedIndexes().isEmpty(); + if (!trigger) { + return; + } + + // Collect the non-directory files into a list and + // call runPreferredApplications for that list. + // Several selected directories are opened in separate tabs, + // one selected directory will get opened in the view. + QModelIndexList dirQueue; + const QModelIndexList indexList = selModel->selectedIndexes(); + KFileItemList fileOpenList; + foreach (const QModelIndex& index, indexList) { + if (itemForIndex(index).isDir()) { + dirQueue << index; + } else { + fileOpenList << itemForIndex(index); + } + } + + KFileItemActions fileItemActions; + fileItemActions.runPreferredApplications(fileOpenList, "DesktopEntryName != 'dolphin'"); + + if (dirQueue.length() == 1) { + // open directory in the view + emit itemTriggered(itemForIndex(dirQueue[0])); + } else { + // open directories in separate tabs + foreach(const QModelIndex& dir, dirQueue) { + emit tabRequested(itemForIndex(dir).url()); + } + } +} + +void DolphinViewController::replaceUrlByClipboard() +{ + const QClipboard* clipboard = QApplication::clipboard(); + QString text; + if (clipboard->mimeData(QClipboard::Selection)->hasText()) { + text = clipboard->mimeData(QClipboard::Selection)->text(); + } else if (clipboard->mimeData(QClipboard::Clipboard)->hasText()) { + text = clipboard->mimeData(QClipboard::Clipboard)->text(); + } + if (!text.isEmpty() && QDir::isAbsolutePath(text)) { + m_dolphinView->setUrl(KUrl(text)); + } +} + +void DolphinViewController::requestToolTipHiding() +{ + emit hideToolTip(); +} + +void DolphinViewController::emitItemTriggered(const KFileItem& item) +{ + emit itemTriggered(item); +} + +KFileItem DolphinViewController::itemForIndex(const QModelIndex& index) const +{ + Q_ASSERT(m_itemView != 0); + + QAbstractProxyModel* proxyModel = static_cast<QAbstractProxyModel*>(m_itemView->model()); + KDirModel* dirModel = static_cast<KDirModel*>(proxyModel->sourceModel()); + const QModelIndex dirIndex = proxyModel->mapToSource(index); + return dirModel->itemForIndex(dirIndex); +} + +void DolphinViewController::triggerItem(const QModelIndex& index) +{ + if (m_mouseButtons & Qt::LeftButton) { + const KFileItem item = itemForIndex(index); + if (index.isValid() && (index.column() == KDirModel::Name)) { + emit itemTriggered(item); + } else { + m_itemView->clearSelection(); + emit itemEntered(KFileItem()); + } + } +} + +void DolphinViewController::requestTab(const QModelIndex& index) +{ + if (m_mouseButtons & Qt::MidButton) { + const KFileItem item = itemForIndex(index); + const bool validRequest = index.isValid() && + (index.column() == KDirModel::Name) && + (item.isDir() || m_dolphinView->isTabsForFilesEnabled()); + if (validRequest) { + emit tabRequested(item.url()); + } + } +} + +void DolphinViewController::emitItemEntered(const QModelIndex& index) +{ + KFileItem item = itemForIndex(index); + if (!item.isNull()) { + emit itemEntered(item); + } +} + +void DolphinViewController::emitViewportEntered() +{ + emit viewportEntered(); +} + +void DolphinViewController::updateMouseButtonState() +{ + m_mouseButtons = QApplication::mouseButtons(); +} + +#include "dolphinviewcontroller.moc" diff --git a/src/views/dolphinviewcontroller.h b/src/views/dolphinviewcontroller.h new file mode 100644 index 000000000..6eed68e1a --- /dev/null +++ b/src/views/dolphinviewcontroller.h @@ -0,0 +1,314 @@ +/*************************************************************************** + * Copyright (C) 2010 by Peter Penz <[email protected]> * + * * + * 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 DOLPHINVIEWCONTROLLER_H +#define DOLPHINVIEWCONTROLLER_H + +#include <views/dolphinview.h> +#include <kurl.h> +#include <QtCore/QObject> +#include <libdolphin_export.h> + +class QAbstractItemView; +class DolphinView; +class KUrl; +class QPoint; + +/** + * @brief Allows the view mode implementations (DolphinIconsView, DolphinDetailsView, DolphinColumnView) + * to do a limited control of the DolphinView. + * + * The DolphinView connects to the signals of DolphinViewController to react on changes + * that have been triggered by the view mode implementations. The view mode implementations + * have read access to the whole DolphinView by DolphinViewController::view(). Limited control of the + * DolphinView by the view mode implementations are defined by the public DolphinController methods. + */ +class LIBDOLPHINPRIVATE_EXPORT DolphinViewController : public QObject +{ + Q_OBJECT + +public: + explicit DolphinViewController(DolphinView* dolphinView); + virtual ~DolphinViewController(); + + /** + * Allows read access for the view mode implementation + * to the DolphinView. + */ + const DolphinView* view() const; + + /** + * Requests the DolphinView to change the URL to \p url. The signal + * urlChangeRequested will be emitted. + */ + void requestUrlChange(const KUrl& url); + + /** + * Changes the current view mode implementation where the controller is working. + * This is only necessary for views like the tree view, where internally + * several QAbstractItemView instances are used. + */ + void setItemView(QAbstractItemView* view); + QAbstractItemView* itemView() const; + + /** + * Requests a context menu for the position \a pos. DolphinView + * takes care itself to get the selected items depending from + * \a pos. It is possible to define a custom list of actions for + * the context menu by \a customActions. + */ + void triggerContextMenuRequest(const QPoint& pos, + const QList<QAction*>& customActions = QList<QAction*>()); + + /** + * Requests an activation of the DolphinView and emits the signal + * activated(). This method should be invoked by the view mode implementation + * if e. g. a mouse click on the view has been done. + */ + void requestActivation(); + + /** + * Indicates that URLs are dropped above a destination. The DolphinView + * will start the corresponding action (copy, move, link). + * @param destItem Item of the destination (can be null, see KFileItem::isNull()). + * @param destPath Path of the destination. + * @param event Drop event + */ + void indicateDroppedUrls(const KFileItem& destItem, + const KUrl& destPath, + QDropEvent* event); + + /** + * Informs the DolphinView about a sorting change done inside + * the view mode implementation. + */ + void indicateSortingChange(DolphinView::Sorting sorting); + + /** + * Informs the DolphinView about a sort order change done inside + * the view mode implementation. + */ + void indicateSortOrderChange(Qt::SortOrder order); + + /** + * Informs the DolphinView about a change between separate sorting + * (with folders first) and mixed sorting of files and folders done inside + * the view mode implementation. + */ + void indicateSortFoldersFirstChange(bool foldersFirst); + + /** + * Informs the DolphinView about an additional information change + * done inside the view mode implementation. + */ + void indicateAdditionalInfoChange(const KFileItemDelegate::InformationList& info); + + /** + * Sets the available version control actions. Is called by the view + * mode implementation as soon as the DolphinView has requested them by the signal + * requestVersionControlActions(). + */ + void setVersionControlActions(QList<QAction*> actions); + + /** + * Emits the signal requestVersionControlActions(). The view mode implementation + * listens to the signal and will invoke a DolphinViewController::setVersionControlActions() + * and the result will be returned. + */ + QList<QAction*> versionControlActions(const KFileItemList& items); + + /** + * Must be be invoked in each view mode implementation whenever a key has been + * pressed. If the selection model of \a view is not empty and + * the return key has been pressed, the selected items will get triggered. + */ + void handleKeyPressEvent(QKeyEvent* event); + + /** + * Replaces the URL of the DolphinView with the content + * of the clipboard as URL. If the clipboard contains no text, + * nothing will be done. + */ + void replaceUrlByClipboard(); + + /** + * Requests the view mode implementation to hide tooltips. + */ + void requestToolTipHiding(); + + /** + * Emits the signal itemTriggered() for the item \a item. + * The method can be used by the view mode implementations to + * trigger an item directly without mouse interaction. + * If the item triggering is done by the mouse, it is recommended + * to use DolphinViewController::triggerItem(), as this will check + * the used mouse buttons to execute the correct action. + */ + void emitItemTriggered(const KFileItem& item); + + /** + * Returns the file item for the proxy index \a index of the DolphinView. + */ + KFileItem itemForIndex(const QModelIndex& index) const; + +public slots: + /** + * Emits the signal itemTriggered() if the file item for the index \a index + * is not null and the left mouse button has been pressed. If the item is + * null, the signal itemEntered() is emitted. + * The method should be invoked by the view mode implementations whenever the + * user has triggered an item with the mouse (see + * QAbstractItemView::clicked() or QAbstractItemView::doubleClicked()). + */ + void triggerItem(const QModelIndex& index); + + /** + * Emits the signal tabRequested(), if the file item for the index \a index + * represents a directory and when the middle mouse button has been pressed. + */ + void requestTab(const QModelIndex& index); + + /** + * Emits the signal itemEntered() if the file item for the index \a index + * is not null. The method should be invoked by the view mode implementation + * whenever the mouse cursor is above an item. + */ + void emitItemEntered(const QModelIndex& index); + + /** + * Emits the signal viewportEntered(). The method should be invoked by + * the view mode implementation whenever the mouse cursor is above the viewport. + */ + void emitViewportEntered(); + +signals: + void urlChangeRequested(const KUrl& url); + + /** + * Is emitted if a context menu should be opened (see triggerContextMenuRequest()). + * @param pos Position relative to the view widget where the + * context menu should be opened. It is recommended + * to get the corresponding model index from + * this position. + * @param customActions List of actions that is added to the context menu when + * the menu is opened above the viewport. + */ + void requestContextMenu(const QPoint& pos, QList<QAction*> customActions); + + /** + * Is emitted if the view has been activated by e. g. a mouse click. + */ + void activated(); + + /** + * Is emitted if URLs have been dropped to the destination + * path \a destPath. If the URLs have been dropped above an item of + * the destination path, the item is indicated by \a destItem + * (can be null, see KFileItem::isNull()). + */ + void urlsDropped(const KFileItem& destItem, + const KUrl& destPath, + QDropEvent* event); + + /** + * Is emitted if the sorting has been changed to \a sorting by + * the view mode implementation (see indicateSortingChanged(). + * The DolphinView connects to + * this signal to update its menu action. + */ + void sortingChanged(DolphinView::Sorting sorting); + + /** + * Is emitted if the sort order has been changed to \a order + * by the view mode implementation (see indicateSortOrderChanged(). + * The DolphinView connects + * to this signal to update its menu actions. + */ + void sortOrderChanged(Qt::SortOrder order); + + /** + * Is emitted if 'sort folders first' has been changed to \a foldersFirst + * by the view mode implementation (see indicateSortOrderChanged(). + * The DolphinView connects + * to this signal to update its menu actions. + */ + void sortFoldersFirstChanged(bool foldersFirst); + + /** + * Is emitted if the additional info has been changed to \a info + * by the view mode implementation. The DolphinView connects + * to this signal to update its menu actions. + */ + void additionalInfoChanged(const KFileItemDelegate::InformationList& info); + + /** + * Is emitted if the item \a item should be triggered. The abstract + * Dolphin view connects to this signal. If the item represents a directory, + * the directory is opened. On a file the corresponding application is opened. + * The item is null (see KFileItem::isNull()), when clicking on the viewport itself. + */ + void itemTriggered(const KFileItem& item); + + /** + * Is emitted if the mouse cursor has entered the item + * given by \a index (see emitItemEntered()). + */ + void itemEntered(const KFileItem& item); + + /** + * Is emitted if a new tab should be opened for the URL \a url. + */ + void tabRequested(const KUrl& url); + + /** + * Is emitted if the mouse cursor has entered + * the viewport (see emitViewportEntered()). + */ + void viewportEntered(); + + /** + * Is emitted, if DolphinViewController::requestToolTipHiding() is invoked + * and requests to hide all tooltips. + */ + void hideToolTip(); + + /** + * Is emitted if pending previews should be canceled (e. g. because of an URL change). + */ + void cancelPreviews(); + + /** + * Requests the view mode implementation to invoke DolphinViewController::setVersionControlActions(), + * so that they can be returned with DolphinViewController::versionControlActions() for + * the DolphinView. + */ + void requestVersionControlActions(const KFileItemList& items); + +private slots: + void updateMouseButtonState(); + +private: + static Qt::MouseButtons m_mouseButtons; // TODO: this is a workaround until Qt-issue 176832 has been fixed + + DolphinView* m_dolphinView; + QAbstractItemView* m_itemView; + QList<QAction*> m_versionControlActions; +}; + +#endif diff --git a/src/views/selectionmanager.cpp b/src/views/selectionmanager.cpp new file mode 100644 index 000000000..0d3efae09 --- /dev/null +++ b/src/views/selectionmanager.cpp @@ -0,0 +1,186 @@ +/*************************************************************************** + * Copyright (C) 2008 by Peter Penz <[email protected]> * + * * + * 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 "selectionmanager.h" + +#include "dolphinmodel.h" +#include "selectiontoggle.h" +#include <kdirmodel.h> +#include <kiconeffect.h> + +#include <QAbstractButton> +#include <QAbstractItemView> +#include <QAbstractProxyModel> +#include <QApplication> +#include <QModelIndex> +#include <QPainter> +#include <QPaintEvent> +#include <QRect> +#include <QTimeLine> + +SelectionManager::SelectionManager(QAbstractItemView* parent) : + QObject(parent), + m_view(parent), + m_toggle(0), + m_connected(false) +{ + connect(parent, SIGNAL(entered(const QModelIndex&)), + this, SLOT(slotEntered(const QModelIndex&))); + connect(parent, SIGNAL(viewportEntered()), + this, SLOT(slotViewportEntered())); + m_toggle = new SelectionToggle(m_view->viewport()); + m_toggle->setCheckable(true); + m_toggle->hide(); + connect(m_toggle, SIGNAL(clicked(bool)), + this, SLOT(setItemSelected(bool))); +} + +SelectionManager::~SelectionManager() +{ +} + +void SelectionManager::reset() +{ + m_toggle->reset(); +} + +void SelectionManager::slotEntered(const QModelIndex& index) +{ + m_toggle->hide(); + const bool showToggle = index.isValid() && + (index.column() == DolphinModel::Name) && + (QApplication::mouseButtons() == Qt::NoButton); + if (showToggle) { + m_toggle->setUrl(urlForIndex(index)); + + if (!m_connected) { + connect(m_view->model(), SIGNAL(rowsRemoved(const QModelIndex&, int, int)), + this, SLOT(slotRowsRemoved(const QModelIndex&, int, int))); + connect(m_view->selectionModel(), + SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + this, + SLOT(slotSelectionChanged(const QItemSelection&, const QItemSelection&))); + m_connected = true; + } + + // increase the size of the toggle for large items + const int height = m_view->iconSize().height(); + if (height >= KIconLoader::SizeEnormous) { + m_toggle->resize(KIconLoader::SizeMedium, KIconLoader::SizeMedium); + } else if (height >= KIconLoader::SizeLarge) { + m_toggle->resize(KIconLoader::SizeSmallMedium, KIconLoader::SizeSmallMedium); + } else { + m_toggle->resize(KIconLoader::SizeSmall, KIconLoader::SizeSmall); + } + + const QRect rect = m_view->visualRect(index); + int x = rect.left(); + int y = rect.top(); + if (height < KIconLoader::SizeSmallMedium) { + // The height is nearly equal to the smallest toggle height. + // Assure that the toggle is vertically centered instead + // of aligned on the top and gets more horizontal gap. + x += 2; + y += (rect.height() - m_toggle->height()) / 2; + } + m_toggle->move(QPoint(x, y)); + + QItemSelectionModel* selModel = m_view->selectionModel(); + m_toggle->setChecked(selModel->isSelected(index)); + m_toggle->show(); + } else { + m_toggle->setUrl(KUrl()); + disconnect(m_view->model(), SIGNAL(rowsRemoved(const QModelIndex&, int, int)), + this, SLOT(slotRowsRemoved(const QModelIndex&, int, int))); + disconnect(m_view->selectionModel(), + SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + this, + SLOT(slotSelectionChanged(const QItemSelection&, const QItemSelection&))); + m_connected = false; + } +} + +void SelectionManager::slotViewportEntered() +{ + m_toggle->hide(); +} + +void SelectionManager::setItemSelected(bool selected) +{ + emit selectionChanged(); + + if (!m_toggle->url().isEmpty()) { + const QModelIndex index = indexForUrl(m_toggle->url()); + if (index.isValid()) { + QItemSelectionModel* selModel = m_view->selectionModel(); + if (selected) { + selModel->select(index, QItemSelectionModel::Select); + } else { + selModel->select(index, QItemSelectionModel::Deselect); + } + selModel->setCurrentIndex(index, QItemSelectionModel::Current); + } + } +} + +void SelectionManager::slotRowsRemoved(const QModelIndex& parent, int start, int end) +{ + Q_UNUSED(parent); + Q_UNUSED(start); + Q_UNUSED(end); + m_toggle->hide(); +} + +void SelectionManager::slotSelectionChanged(const QItemSelection& selected, + const QItemSelection& deselected) +{ + // The selection has been changed outside the scope of the selection manager + // (e. g. by the rubberband or the "Select All" action). Take care updating + // the state of the toggle button. + if (!m_toggle->url().isEmpty()) { + const QModelIndex index = indexForUrl(m_toggle->url()); + if (index.isValid()) { + if (selected.contains(index)) { + m_toggle->setChecked(true); + } + + if (deselected.contains(index)) { + m_toggle->setChecked(false); + } + } + } +} + +KUrl SelectionManager::urlForIndex(const QModelIndex& index) const +{ + QAbstractProxyModel* proxyModel = static_cast<QAbstractProxyModel*>(m_view->model()); + KDirModel* dirModel = static_cast<KDirModel*>(proxyModel->sourceModel()); + const QModelIndex dirIndex = proxyModel->mapToSource(index); + return dirModel->itemForIndex(dirIndex).url(); +} + +const QModelIndex SelectionManager::indexForUrl(const KUrl& url) const +{ + QAbstractProxyModel* proxyModel = static_cast<QAbstractProxyModel*>(m_view->model()); + KDirModel* dirModel = static_cast<KDirModel*>(proxyModel->sourceModel()); + const QModelIndex dirIndex = dirModel->indexForUrl(url); + return proxyModel->mapFromSource(dirIndex); +} + +#include "selectionmanager.moc" diff --git a/src/views/selectionmanager.h b/src/views/selectionmanager.h new file mode 100644 index 000000000..c2fcc88b4 --- /dev/null +++ b/src/views/selectionmanager.h @@ -0,0 +1,74 @@ +/*************************************************************************** + * Copyright (C) 2008 by Peter Penz <[email protected]> * + * * + * 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 SELECTIONMANAGER_H +#define SELECTIONMANAGER_H + +#include <kfileitem.h> + +#include <QObject> + +class QAbstractItemView; +class QModelIndex; +class QItemSelection; +class SelectionToggle; + +/** + * @brief Allows to select and deselect items for item views. + * + * Whenever an item is hovered by the mouse, a toggle button is shown + * which allows to select/deselect the current item. + */ +class SelectionManager : public QObject +{ + Q_OBJECT + +public: + SelectionManager(QAbstractItemView* parent); + virtual ~SelectionManager(); + +public slots: + /** + * Resets the selection manager so that the toggle button gets + * invisible. + */ + void reset(); + +signals: + /** Is emitted if the selection has been changed by the toggle button. */ + void selectionChanged(); + +private slots: + void slotEntered(const QModelIndex& index); + void slotViewportEntered(); + void setItemSelected(bool selected); + void slotRowsRemoved(const QModelIndex& parent, int start, int end); + void slotSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + +private: + KUrl urlForIndex(const QModelIndex& index) const; + const QModelIndex indexForUrl(const KUrl& url) const; + +private: + QAbstractItemView* m_view; + SelectionToggle* m_toggle; + bool m_connected; +}; + +#endif diff --git a/src/views/selectiontoggle.cpp b/src/views/selectiontoggle.cpp new file mode 100644 index 000000000..6608b5821 --- /dev/null +++ b/src/views/selectiontoggle.cpp @@ -0,0 +1,233 @@ +/*************************************************************************** + * Copyright (C) 2008 by Peter Penz <[email protected]> * + * * + * 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 "selectiontoggle.h" + +#include <kglobalsettings.h> +#include <kicon.h> +#include <kiconloader.h> +#include <kiconeffect.h> +#include <klocale.h> + +#include <QPainter> +#include <QPaintEvent> +#include <QRect> +#include <QTimer> +#include <QTimeLine> + +SelectionToggle::SelectionToggle(QWidget* parent) : + QAbstractButton(parent), + m_isHovered(false), + m_leftMouseButtonPressed(false), + m_fadingValue(0), + m_icon(), + m_fadingTimeLine(0) +{ + setFocusPolicy(Qt::NoFocus); + parent->installEventFilter(this); + resize(sizeHint()); + setIconOverlay(isChecked()); + connect(this, SIGNAL(toggled(bool)), + this, SLOT(setIconOverlay(bool))); + connect(KGlobalSettings::self(), SIGNAL(iconChanged(int)), + this, SLOT(refreshIcon())); +} + +SelectionToggle::~SelectionToggle() +{ +} + +QSize SelectionToggle::sizeHint() const +{ + return QSize(16, 16); +} + +void SelectionToggle::reset() +{ + m_url = KUrl(); + hide(); +} + +void SelectionToggle::setUrl(const KUrl& url) +{ + m_url = url; + if (!url.isEmpty()) { + startFading(); + } +} + +KUrl SelectionToggle::url() const +{ + return m_url; +} + +void SelectionToggle::setVisible(bool visible) +{ + QAbstractButton::setVisible(visible); + + stopFading(); + if (visible) { + startFading(); + } + +} + +bool SelectionToggle::eventFilter(QObject* obj, QEvent* event) +{ + if (obj == parent()) { + switch (event->type()) { + case QEvent::Leave: + hide(); + break; + + case QEvent::MouseMove: + if (m_leftMouseButtonPressed) { + // Don't forward mouse move events to the viewport, + // otherwise a rubberband selection will be shown when + // clicking on the selection toggle and moving the mouse + // above the viewport. + return true; + } + break; + + default: + break; + } + } + + return QAbstractButton::eventFilter(obj, event); +} + +void SelectionToggle::enterEvent(QEvent* event) +{ + QAbstractButton::enterEvent(event); + + // if the mouse cursor is above the selection toggle, display + // it immediately without fading timer + m_isHovered = true; + if (m_fadingTimeLine != 0) { + m_fadingTimeLine->stop(); + } + m_fadingValue = 255; + setToolTip(isChecked() ? i18nc("@info:tooltip", "Deselect Item") : + i18nc("@info:tooltip", "Select Item")); + update(); +} + +void SelectionToggle::leaveEvent(QEvent* event) +{ + QAbstractButton::leaveEvent(event); + m_isHovered = false; + update(); +} + +void SelectionToggle::mousePressEvent(QMouseEvent* event) +{ + QAbstractButton::mousePressEvent(event); + m_leftMouseButtonPressed = (event->buttons() & Qt::LeftButton); +} + +void SelectionToggle::mouseReleaseEvent(QMouseEvent* event) +{ + QAbstractButton::mouseReleaseEvent(event); + m_leftMouseButtonPressed = (event->buttons() & Qt::LeftButton); +} + +void SelectionToggle::resizeEvent(QResizeEvent* event) +{ + QAbstractButton::resizeEvent(event); + setIconOverlay(isChecked()); +} + +void SelectionToggle::paintEvent(QPaintEvent* event) +{ + QPainter painter(this); + painter.setClipRect(event->rect()); + + // draw the icon overlay + if (m_isHovered) { + KIconEffect iconEffect; + QPixmap activeIcon = iconEffect.apply(m_icon, KIconLoader::Desktop, KIconLoader::ActiveState); + painter.drawPixmap(0, 0, activeIcon); + } else { + if (m_fadingValue < 255) { + // apply an alpha mask respecting the fading value to the icon + QPixmap icon = m_icon; + QPixmap alphaMask(icon.width(), icon.height()); + const QColor color(m_fadingValue, m_fadingValue, m_fadingValue); + alphaMask.fill(color); + icon.setAlphaChannel(alphaMask); + painter.drawPixmap(0, 0, icon); + } else { + // no fading is required + painter.drawPixmap(0, 0, m_icon); + } + } +} + +void SelectionToggle::setFadingValue(int value) +{ + m_fadingValue = value; + if (m_fadingValue >= 255) { + Q_ASSERT(m_fadingTimeLine != 0); + m_fadingTimeLine->stop(); + } + update(); +} + +void SelectionToggle::setIconOverlay(bool checked) +{ + const char* icon = checked ? "list-remove" : "list-add"; + m_icon = KIconLoader::global()->loadIcon(icon, + KIconLoader::NoGroup, + qMin(width(), height())); + update(); +} + +void SelectionToggle::refreshIcon() +{ + setIconOverlay(isChecked()); +} + +void SelectionToggle::startFading() +{ + Q_ASSERT(m_fadingTimeLine == 0); + + const bool animate = KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects; + const int duration = animate ? 600 : 1; + + m_fadingTimeLine = new QTimeLine(duration, this); + connect(m_fadingTimeLine, SIGNAL(frameChanged(int)), + this, SLOT(setFadingValue(int))); + m_fadingTimeLine->setFrameRange(0, 255); + m_fadingTimeLine->start(); + m_fadingValue = 0; +} + +void SelectionToggle::stopFading() +{ + if (m_fadingTimeLine != 0) { + m_fadingTimeLine->stop(); + delete m_fadingTimeLine; + m_fadingTimeLine = 0; + } + m_fadingValue = 0; +} + +#include "selectiontoggle.moc" diff --git a/src/views/selectiontoggle.h b/src/views/selectiontoggle.h new file mode 100644 index 000000000..5519272b3 --- /dev/null +++ b/src/views/selectiontoggle.h @@ -0,0 +1,91 @@ +/*************************************************************************** + * Copyright (C) 2008 by Peter Penz <[email protected]> * + * * + * 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 SELECTIONTOGGLE_H +#define SELECTIONTOGGLE_H + +#include <kurl.h> + +#include <QAbstractButton> +#include <QPixmap> + +class QTimeLine; + +/** + * @brief Toggle button for changing the selection of an hovered item. + * + * The toggle button is visually invisible until it is displayed at least + * for one second. + * + * @see SelectionManager + */ +class SelectionToggle : public QAbstractButton +{ + Q_OBJECT + +public: + explicit SelectionToggle(QWidget* parent); + virtual ~SelectionToggle(); + virtual QSize sizeHint() const; + + /** + * Resets the selection toggle so that it is hidden and stays + * visually invisible for at least one second after it is shown again. + */ + void reset(); + + void setUrl(const KUrl& url); + KUrl url() const; + +public slots: + virtual void setVisible(bool visible); + +protected: + virtual bool eventFilter(QObject* obj, QEvent* event); + virtual void enterEvent(QEvent* event); + virtual void leaveEvent(QEvent* event); + virtual void mousePressEvent(QMouseEvent* event); + virtual void mouseReleaseEvent(QMouseEvent* event); + virtual void resizeEvent(QResizeEvent* event); + virtual void paintEvent(QPaintEvent* event); + +private slots: + /** + * Sets the alpha value for the fading animation and is + * connected with m_fadingTimeLine. + */ + void setFadingValue(int value); + + void setIconOverlay(bool checked); + void refreshIcon(); + +private: + void startFading(); + void stopFading(); + +private: + bool m_isHovered; + bool m_leftMouseButtonPressed; + int m_fadingValue; + QPixmap m_icon; + QTimeLine* m_fadingTimeLine; + KUrl m_url; +}; + +#endif diff --git a/src/views/viewextensionsfactory.cpp b/src/views/viewextensionsfactory.cpp new file mode 100644 index 000000000..e5638c03e --- /dev/null +++ b/src/views/viewextensionsfactory.cpp @@ -0,0 +1,244 @@ +/*************************************************************************** + * Copyright (C) 2009 by Peter Penz <[email protected]> * + * * + * 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 "viewextensionsfactory.h" + +#include "dolphinfileitemdelegate.h" +#include "dolphinsortfilterproxymodel.h" +#include "dolphinview.h" +#include "dolphinviewcontroller.h" +#include "dolphinviewautoscroller.h" +#include "folderexpander.h" +#include "selectionmanager.h" +#include "settings/dolphinsettings.h" +#include "tooltips/tooltipmanager.h" +#include "versioncontrol/versioncontrolobserver.h" +#include "viewmodecontroller.h" + +#include "dolphin_generalsettings.h" + +#include <kdirlister.h> +#include <kdirmodel.h> +#include <kfilepreviewgenerator.h> +#include <QAbstractItemView> + +ViewExtensionsFactory::ViewExtensionsFactory(QAbstractItemView* view, + DolphinViewController* dolphinViewController, + const ViewModeController* viewModeController) : + QObject(view), + m_view(view), + m_dolphinViewController(dolphinViewController), + m_toolTipManager(0), + m_previewGenerator(0), + m_selectionManager(0), + m_autoScroller(0), + m_fileItemDelegate(0), + m_versionControlObserver(0) +{ + view->setSelectionMode(QAbstractItemView::ExtendedSelection); + + GeneralSettings* settings = DolphinSettings::instance().generalSettings(); + + // initialize tooltips + if (settings->showToolTips()) { + DolphinSortFilterProxyModel* proxyModel = static_cast<DolphinSortFilterProxyModel*>(view->model()); + m_toolTipManager = new ToolTipManager(view, proxyModel); + + connect(dolphinViewController, SIGNAL(hideToolTip()), + m_toolTipManager, SLOT(hideTip())); + } + + // initialize preview generator + Q_ASSERT(view->iconSize().isValid()); + m_previewGenerator = new KFilePreviewGenerator(view); + m_previewGenerator->setPreviewShown(dolphinViewController->view()->showPreview()); + connect(viewModeController, SIGNAL(zoomLevelChanged(int)), + this, SLOT(slotZoomLevelChanged())); + connect(viewModeController, SIGNAL(cancelPreviews()), + this, SLOT(cancelPreviews())); + connect(dolphinViewController->view(), SIGNAL(showPreviewChanged()), + this, SLOT(slotShowPreviewChanged())); + + // initialize selection manager + if (settings->showSelectionToggle()) { + m_selectionManager = new SelectionManager(view); + connect(m_selectionManager, SIGNAL(selectionChanged()), + this, SLOT(requestActivation())); + connect(viewModeController, SIGNAL(urlChanged(const KUrl&)), + m_selectionManager, SLOT(reset())); + } + + // initialize auto scroller + m_autoScroller = new DolphinViewAutoScroller(view); + + // initialize file item delegate + m_fileItemDelegate = new DolphinFileItemDelegate(view); + m_fileItemDelegate->setShowToolTipWhenElided(false); + view->setItemDelegate(m_fileItemDelegate); + + // initialize version control observer + const DolphinView* dolphinView = dolphinViewController->view(); + m_versionControlObserver = new VersionControlObserver(view); + connect(m_versionControlObserver, SIGNAL(infoMessage(const QString&)), + dolphinView, SIGNAL(infoMessage(const QString&))); + connect(m_versionControlObserver, SIGNAL(errorMessage(const QString&)), + dolphinView, SIGNAL(errorMessage(const QString&))); + connect(m_versionControlObserver, SIGNAL(operationCompletedMessage(const QString&)), + dolphinView, SIGNAL(operationCompletedMessage(const QString&))); + connect(dolphinViewController, SIGNAL(requestVersionControlActions(const KFileItemList&)), + this, SLOT(slotRequestVersionControlActions(const KFileItemList&))); + + // react on view property changes + connect(dolphinView, SIGNAL(showHiddenFilesChanged()), + this, SLOT(slotShowHiddenFilesChanged())); + connect(dolphinView, SIGNAL(sortingChanged(DolphinView::Sorting)), + this, SLOT(slotSortingChanged(DolphinView::Sorting))); + connect(dolphinView, SIGNAL(sortOrderChanged(Qt::SortOrder)), + this, SLOT(slotSortOrderChanged(Qt::SortOrder))); + connect(dolphinView, SIGNAL(sortFoldersFirstChanged(bool)), + this, SLOT(slotSortFoldersFirstChanged(bool))); + + // Give the view the ability to auto-expand its directories on hovering + // (the column view takes care about this itself). If the details view + // uses expandable folders, the auto-expanding should be used always. + m_folderExpander = new FolderExpander(view, proxyModel()); + m_folderExpander->setEnabled(settings->autoExpandFolders()); + connect(m_folderExpander, SIGNAL(enterDir(const QModelIndex&)), + dolphinViewController, SLOT(triggerItem(const QModelIndex&))); + + // react on namefilter changes + connect(viewModeController, SIGNAL(nameFilterChanged(const QString&)), + this, SLOT(slotNameFilterChanged(const QString&))); + + view->viewport()->installEventFilter(this); +} + +ViewExtensionsFactory::~ViewExtensionsFactory() +{ +} + +void ViewExtensionsFactory::handleCurrentIndexChange(const QModelIndex& current, const QModelIndex& previous) +{ + m_autoScroller->handleCurrentIndexChange(current, previous); +} + +DolphinFileItemDelegate* ViewExtensionsFactory::fileItemDelegate() const +{ + return m_fileItemDelegate; +} + +void ViewExtensionsFactory::setAutoFolderExpandingEnabled(bool enabled) +{ + m_folderExpander->setEnabled(enabled); +} + +bool ViewExtensionsFactory::autoFolderExpandingEnabled() const +{ + return m_folderExpander->enabled(); +} + +bool ViewExtensionsFactory::eventFilter(QObject* watched, QEvent* event) +{ + Q_UNUSED(watched); + if ((event->type() == QEvent::Wheel) && (m_selectionManager != 0)) { + m_selectionManager->reset(); + } + return false; +} + +void ViewExtensionsFactory::slotZoomLevelChanged() +{ + m_previewGenerator->updateIcons(); + if (m_selectionManager != 0) { + m_selectionManager->reset(); + } +} + +void ViewExtensionsFactory::cancelPreviews() +{ + m_previewGenerator->cancelPreviews(); +} + +void ViewExtensionsFactory::slotShowPreviewChanged() +{ + const bool show = m_dolphinViewController->view()->showPreview(); + m_previewGenerator->setPreviewShown(show); +} + +void ViewExtensionsFactory::slotShowHiddenFilesChanged() +{ + KDirModel* dirModel = static_cast<KDirModel*>(proxyModel()->sourceModel()); + KDirLister* dirLister = dirModel->dirLister(); + + dirLister->stop(); + + const bool show = m_dolphinViewController->view()->showHiddenFiles(); + dirLister->setShowingDotFiles(show); + + const KUrl url = dirLister->url(); + if (url.isValid()) { + dirLister->openUrl(url, KDirLister::NoFlags); + } +} + +void ViewExtensionsFactory::slotSortingChanged(DolphinView::Sorting sorting) +{ + proxyModel()->setSorting(sorting); +} + +void ViewExtensionsFactory::slotSortOrderChanged(Qt::SortOrder order) +{ + proxyModel()->setSortOrder(order); +} + +void ViewExtensionsFactory::slotSortFoldersFirstChanged(bool foldersFirst) +{ + proxyModel()->setSortFoldersFirst(foldersFirst); +} + +void ViewExtensionsFactory::slotNameFilterChanged(const QString& nameFilter) +{ + proxyModel()->setFilterFixedString(nameFilter); +} + +void ViewExtensionsFactory::slotRequestVersionControlActions(const KFileItemList& items) +{ + QList<QAction*> actions; + if (items.isEmpty()) { + const KDirModel* dirModel = static_cast<const KDirModel*>(proxyModel()->sourceModel()); + const KUrl url = dirModel->dirLister()->url(); + actions = m_versionControlObserver->contextMenuActions(url.path(KUrl::AddTrailingSlash)); + } else { + actions = m_versionControlObserver->contextMenuActions(items); + } + m_dolphinViewController->setVersionControlActions(actions); +} + +void ViewExtensionsFactory::requestActivation() +{ + m_dolphinViewController->requestActivation(); +} + +DolphinSortFilterProxyModel* ViewExtensionsFactory::proxyModel() const +{ + return static_cast<DolphinSortFilterProxyModel*>(m_view->model()); +} + +#include "viewextensionsfactory.moc" + diff --git a/src/views/viewextensionsfactory.h b/src/views/viewextensionsfactory.h new file mode 100644 index 000000000..9324932ac --- /dev/null +++ b/src/views/viewextensionsfactory.h @@ -0,0 +1,105 @@ +/*************************************************************************** + * Copyright (C) 2009 by Peter Penz <[email protected]> * + * * + * 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 VIEWEXTENSIONSFACTORY_H +#define VIEWEXTENSIONSFACTORY_H + +#include <QObject> + +#include "dolphinview.h" + +class DolphinFileItemDelegate; +class DolphinSortFilterProxyModel; +class DolphinViewAutoScroller; +class KFilePreviewGenerator; +class FolderExpander; +class QModelIndex; +class SelectionManager; +class ToolTipManager; +class QAbstractItemView; +class VersionControlObserver; +class ViewModeController; + +/** + * @brief Responsible for creating extensions like tooltips and previews + * that are available in all view implementations. + * + * Each view implementation (iconsview, detailsview, columnview) must + * instantiate an instance of this class to assure having + * a common behavior that is independent from the custom functionality of + * a view implementation. + */ +class ViewExtensionsFactory : public QObject +{ + Q_OBJECT + +public: + explicit ViewExtensionsFactory(QAbstractItemView* view, + DolphinViewController* dolphinViewController, + const ViewModeController* viewModeController); + virtual ~ViewExtensionsFactory(); + + /** + * Must be invoked by the item view, when QAbstractItemView::currentChanged() + * has been called. Assures that the current item stays visible when it has been + * changed by the keyboard. + */ + void handleCurrentIndexChange(const QModelIndex& current, const QModelIndex& previous); + + DolphinFileItemDelegate* fileItemDelegate() const; + + /** + * Enables the automatically expanding of a folder when dragging + * items above the folder. + */ + void setAutoFolderExpandingEnabled(bool enabled); + bool autoFolderExpandingEnabled() const; + +protected: + virtual bool eventFilter(QObject* watched, QEvent* event); + +private slots: + void slotZoomLevelChanged(); + void cancelPreviews(); + void slotShowPreviewChanged(); + void slotShowHiddenFilesChanged(); + void slotSortingChanged(DolphinView::Sorting sorting); + void slotSortOrderChanged(Qt::SortOrder order); + void slotSortFoldersFirstChanged(bool foldersFirst); + void slotNameFilterChanged(const QString& nameFilter); + void slotRequestVersionControlActions(const KFileItemList& items); + void requestActivation(); + +private: + DolphinSortFilterProxyModel* proxyModel() const; + +private: + QAbstractItemView* m_view; + DolphinViewController* m_dolphinViewController; + ToolTipManager* m_toolTipManager; + KFilePreviewGenerator* m_previewGenerator; + SelectionManager* m_selectionManager; + DolphinViewAutoScroller* m_autoScroller; + DolphinFileItemDelegate* m_fileItemDelegate; + VersionControlObserver* m_versionControlObserver; + FolderExpander* m_folderExpander; +}; + +#endif + diff --git a/src/views/viewmodecontroller.cpp b/src/views/viewmodecontroller.cpp new file mode 100644 index 000000000..17d0ba61f --- /dev/null +++ b/src/views/viewmodecontroller.cpp @@ -0,0 +1,88 @@ +/*************************************************************************** + * Copyright (C) 2010 by Peter Penz <[email protected]> * + * * + * 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 "viewmodecontroller.h" + +#include "zoomlevelinfo.h" + +ViewModeController::ViewModeController(QObject* parent) : + QObject(parent), + m_zoomLevel(0), + m_nameFilter(), + m_url() +{ +} + +ViewModeController::~ViewModeController() +{ +} + +KUrl ViewModeController::url() const +{ + return m_url; +} + +void ViewModeController::redirectToUrl(const KUrl& url) +{ + m_url = url; +} + +void ViewModeController::indicateActivationChange(bool active) +{ + emit activationChanged(active); +} + +void ViewModeController::setNameFilter(const QString& nameFilter) +{ + if (nameFilter != m_nameFilter) { + m_nameFilter = nameFilter; + emit nameFilterChanged(nameFilter); + } +} + +QString ViewModeController::nameFilter() const +{ + return m_nameFilter; +} + +void ViewModeController::setZoomLevel(int level) +{ + Q_ASSERT(level >= ZoomLevelInfo::minimumLevel()); + Q_ASSERT(level <= ZoomLevelInfo::maximumLevel()); + if (level != m_zoomLevel) { + m_zoomLevel = level; + emit zoomLevelChanged(m_zoomLevel); + } +} + +int ViewModeController::zoomLevel() const +{ + return m_zoomLevel; +} + +void ViewModeController::setUrl(const KUrl& url) +{ + if (m_url != url) { + m_url = url; + emit cancelPreviews(); + emit urlChanged(url); + } +} + +#include "viewmodecontroller.moc" diff --git a/src/views/viewmodecontroller.h b/src/views/viewmodecontroller.h new file mode 100644 index 000000000..c7378d59a --- /dev/null +++ b/src/views/viewmodecontroller.h @@ -0,0 +1,124 @@ +/*************************************************************************** + * Copyright (C) 2010 by Peter Penz <[email protected]> * + * * + * 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 VIEWMODECONTROLLER_H +#define VIEWMODECONTROLLER_H + +#include <kurl.h> +#include <QObject> +#include <libdolphin_export.h> +#include <views/dolphinview.h> + +/** + * @brief Allows the DolphinView to control the view implementations for the + * different view modes. + * + * The view implementations (DolphinIconsView, DolphinDetailsView, DolphinColumnView) + * connect to signals of the ViewModeController to react on changes. The view + * implementations get only read-access to the ViewModeController. + */ +class LIBDOLPHINPRIVATE_EXPORT ViewModeController : public QObject +{ + Q_OBJECT + +public: + explicit ViewModeController(QObject* parent = 0); + virtual ~ViewModeController(); + + /** + * @return URL that is shown by the view mode implementation. + */ + KUrl url() const; + + /** + * Sets the URL to \a url and does nothing else. Called when + * a redirection happens. See ViewModeController::setUrl() + */ + void redirectToUrl(const KUrl& url); + + /** + * Informs the view mode implementation about a change of the activation + * state by emitting the signal activationChanged(). + */ + void indicateActivationChange(bool active); + + /** + * Sets the zoom level to \a level and emits the signal zoomLevelChanged(). + * It must be assured that the used level is inside the range + * ViewModeController::zoomLevelMinimum() and + * ViewModeController::zoomLevelMaximum(). + */ + void setZoomLevel(int level); + int zoomLevel() const; + + /** + * Sets the name filter to \a and emits the signal nameFilterChanged(). + */ + void setNameFilter(const QString& nameFilter); + QString nameFilter() const; + + /** + * Requests the view mode implementation to hide tooltips. + */ + void requestToolTipHiding(); + +public slots: + /** + * Sets the URL to \a url and emits the signals cancelPreviews() and + * urlChanged() if \a url is different for the current URL. + */ + void setUrl(const KUrl& url); + +signals: + /** + * Is emitted if the URL has been changed by ViewModeController::setUrl(). + */ + void urlChanged(const KUrl& url); + + /** + * Is emitted, if ViewModeController::indicateActivationChange() has been + * invoked. The view mode implementation may update its visual state + * to represent the activation state. + */ + void activationChanged(bool active); + + /** + * Is emitted if the name filter has been changed by + * ViewModeController::setNameFilter(). + */ + void nameFilterChanged(const QString& nameFilter); + + /** + * Is emitted if the zoom level has been changed by + * ViewModeController::setZoomLevel(). + */ + void zoomLevelChanged(int level); + + /** + * Is emitted if pending previews should be canceled (e. g. because of an URL change). + */ + void cancelPreviews(); + +private: + int m_zoomLevel; + QString m_nameFilter; + KUrl m_url; +}; + +#endif diff --git a/src/views/zoomlevelinfo.cpp b/src/views/zoomlevelinfo.cpp new file mode 100644 index 000000000..08e95e3ca --- /dev/null +++ b/src/views/zoomlevelinfo.cpp @@ -0,0 +1,60 @@ +/*************************************************************************** + * Copyright (C) 2008 by Peter Penz <[email protected]> * + * * + * 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 "zoomlevelinfo.h" +#include <kiconloader.h> +#include <QSize> + +int ZoomLevelInfo::minimumLevel() +{ + return 0; +} + +int ZoomLevelInfo::maximumLevel() +{ + return 16; +} + +int ZoomLevelInfo::iconSizeForZoomLevel(int level) +{ + int size = KIconLoader::SizeMedium; + switch (level) { + case 0: size = KIconLoader::SizeSmall; break; + case 1: size = KIconLoader::SizeSmallMedium; break; + case 2: size = KIconLoader::SizeMedium; break; + case 3: size = KIconLoader::SizeLarge; break; + case 4: size = KIconLoader::SizeHuge; break; + default: size = KIconLoader::SizeHuge + ((level - 4) << 4); + } + return size; +} + +int ZoomLevelInfo::zoomLevelForIconSize(const QSize& size) +{ + int level = 0; + switch (size.height()) { + case KIconLoader::SizeSmall: level = 0; break; + case KIconLoader::SizeSmallMedium: level = 1; break; + case KIconLoader::SizeMedium: level = 2; break; + case KIconLoader::SizeLarge: level = 3; break; + case KIconLoader::SizeHuge: level = 4; break; + default: level = 4 + ((size.height() - KIconLoader::SizeHuge) >> 4); + } + return level; +} diff --git a/src/views/zoomlevelinfo.h b/src/views/zoomlevelinfo.h new file mode 100644 index 000000000..a6e92e653 --- /dev/null +++ b/src/views/zoomlevelinfo.h @@ -0,0 +1,51 @@ +/*************************************************************************** + * Copyright (C) 2008 by Peter Penz <[email protected]> * + * * + * 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 ZOOMLEVELINFO_H +#define ZOOMLEVELINFO_H + +class QSize; + +/** + * @short Helper class for getting information about the zooming + * capabilities. + */ +class ZoomLevelInfo { +public: + static int minimumLevel(); + static int maximumLevel(); + + /** + * Helper method for the view implementation to get + * the icon size for the zoom level \a level that + * is between the range ZoomLevelInfo::minimumLevel() and + * ZoomLevelInfo::maximumLevel(). + */ + static int iconSizeForZoomLevel(int level); + + /** + * Helper method for the view implementation to get + * the zoom level for the icon size \a size that + * is between the range ZoomLevelInfo::minimumLevel() and + * ZoomLevelInfo::maximumLevel(). + */ + static int zoomLevelForIconSize(const QSize& size); +}; + +#endif |
