src/gui/dialogs/qsidebar.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui/dialogs/qsidebar.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,509 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsidebar_p.h"
+#include "qfilesystemmodel.h"
+
+#ifndef QT_NO_FILEDIALOG
+
+#include <qaction.h>
+#include <qurl.h>
+#include <qmenu.h>
+#include <qmimedata.h>
+#include <qevent.h>
+#include <qdebug.h>
+#include <qfileiconprovider.h>
+#include <qfiledialog.h>
+
+QT_BEGIN_NAMESPACE
+
+void QSideBarDelegate::initStyleOption(QStyleOptionViewItem *option,
+                                         const QModelIndex &index) const
+{
+    QStyledItemDelegate::initStyleOption(option,index);
+    QVariant value = index.data(QUrlModel::EnabledRole);
+    if (value.isValid()) {
+        //If the bookmark/entry is not enabled then we paint it in gray
+        if (!qvariant_cast<bool>(value))
+            option->state &= ~QStyle::State_Enabled;
+    }
+}
+
+/*!
+    QUrlModel lets you have indexes from a QFileSystemModel to a list.  When QFileSystemModel
+    changes them QUrlModel will automatically update.
+
+    Example usage: File dialog sidebar and combo box
+ */
+QUrlModel::QUrlModel(QObject *parent) : QStandardItemModel(parent), showFullPath(false), fileSystemModel(0)
+{
+}
+
+/*!
+    \reimp
+*/
+QStringList QUrlModel::mimeTypes() const
+{
+    return QStringList(QLatin1String("text/uri-list"));
+}
+
+/*!
+    \reimp
+*/
+Qt::ItemFlags QUrlModel::flags(const QModelIndex &index) const
+{
+    Qt::ItemFlags flags = QStandardItemModel::flags(index);
+    if (index.isValid()) {
+        flags &= ~Qt::ItemIsEditable;
+        // ### some future version could support "moving" urls onto a folder
+        flags &= ~Qt::ItemIsDropEnabled;
+    }
+
+    if (index.data(Qt::DecorationRole).isNull())
+        flags &= ~Qt::ItemIsEnabled;
+
+    return flags;
+}
+
+/*!
+    \reimp
+*/
+QMimeData *QUrlModel::mimeData(const QModelIndexList &indexes) const
+{
+    QList<QUrl> list;
+    for (int i = 0; i < indexes.count(); ++i) {
+        if (indexes.at(i).column() == 0)
+           list.append(indexes.at(i).data(UrlRole).toUrl());
+    }
+    QMimeData *data = new QMimeData();
+    data->setUrls(list);
+    return data;
+}
+
+#ifndef QT_NO_DRAGANDDROP
+
+/*!
+    Decide based upon the data if it should be accepted or not
+
+    We only accept dirs and not files
+*/
+bool QUrlModel::canDrop(QDragEnterEvent *event)
+{
+    if (!event->mimeData()->formats().contains(mimeTypes().first()))
+        return false;
+
+    const QList<QUrl> list = event->mimeData()->urls();
+    for (int i = 0; i < list.count(); ++i) {
+        QModelIndex idx = fileSystemModel->index(list.at(0).toLocalFile());
+        if (!fileSystemModel->isDir(idx))
+            return false;
+    }
+    return true;
+}
+
+/*!
+    \reimp
+*/
+bool QUrlModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
+                                 int row, int column, const QModelIndex &parent)
+{
+    if (!data->formats().contains(mimeTypes().first()))
+        return false;
+    Q_UNUSED(action);
+    Q_UNUSED(column);
+    Q_UNUSED(parent);
+    addUrls(data->urls(), row);
+    return true;
+}
+
+#endif // QT_NO_DRAGANDDROP
+
+/*!
+    \reimp
+
+    If the role is the UrlRole then handle otherwise just pass to QStandardItemModel
+*/
+bool QUrlModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+    if (value.type() == QVariant::Url) {
+        QUrl url = value.toUrl();
+        QModelIndex dirIndex = fileSystemModel->index(url.toLocalFile());
+        //On windows the popup display the "C:\", convert to nativeSeparators
+        if (showFullPath)
+            QStandardItemModel::setData(index, QDir::toNativeSeparators(fileSystemModel->data(dirIndex, QFileSystemModel::FilePathRole).toString()));
+        else {
+            QStandardItemModel::setData(index, QDir::toNativeSeparators(fileSystemModel->data(dirIndex, QFileSystemModel::FilePathRole).toString()), Qt::ToolTipRole);
+            QStandardItemModel::setData(index, fileSystemModel->data(dirIndex).toString());
+        }
+        QStandardItemModel::setData(index, fileSystemModel->data(dirIndex, Qt::DecorationRole),
+                                               Qt::DecorationRole);
+        QStandardItemModel::setData(index, url, UrlRole);
+        return true;
+    }
+    return QStandardItemModel::setData(index, value, role);
+}
+
+void QUrlModel::setUrl(const QModelIndex &index, const QUrl &url, const QModelIndex &dirIndex)
+{
+    setData(index, url, UrlRole);
+    if (url.path().isEmpty()) {
+        setData(index, fileSystemModel->myComputer());
+        setData(index, fileSystemModel->myComputer(Qt::DecorationRole), Qt::DecorationRole);
+    } else {
+        QString newName;
+        if (showFullPath) {
+            //On windows the popup display the "C:\", convert to nativeSeparators
+            newName = QDir::toNativeSeparators(dirIndex.data(QFileSystemModel::FilePathRole).toString());
+        } else {
+            newName = dirIndex.data().toString();
+        }
+
+        QIcon newIcon = qvariant_cast<QIcon>(dirIndex.data(Qt::DecorationRole));
+        if (!dirIndex.isValid()) {
+            newIcon = fileSystemModel->iconProvider()->icon(QFileIconProvider::Folder);
+            newName = QFileInfo(url.toLocalFile()).fileName();
+            if (!invalidUrls.contains(url))
+                invalidUrls.append(url);
+            //The bookmark is invalid then we set to false the EnabledRole
+            setData(index, false, EnabledRole);
+        } else {
+            //The bookmark is valid then we set to true the EnabledRole
+            setData(index, true, EnabledRole);
+        }
+
+        // Make sure that we have at least 32x32 images
+        const QSize size = newIcon.actualSize(QSize(32,32));
+        if (size.width() < 32) {
+            QPixmap smallPixmap = newIcon.pixmap(QSize(32, 32));
+            newIcon.addPixmap(smallPixmap.scaledToWidth(32, Qt::SmoothTransformation));
+        }
+
+        if (index.data().toString() != newName)
+            setData(index, newName);
+        QIcon oldIcon = qvariant_cast<QIcon>(index.data(Qt::DecorationRole));
+        if (oldIcon.cacheKey() != newIcon.cacheKey())
+            setData(index, newIcon, Qt::DecorationRole);
+    }
+}
+
+void QUrlModel::setUrls(const QList<QUrl> &list)
+{
+    removeRows(0, rowCount());
+    invalidUrls.clear();
+    watching.clear();
+    addUrls(list, 0);
+}
+
+/*!
+    Add urls \a list into the list at \a row.  If move then movie
+    existing ones to row.
+
+    \sa dropMimeData()
+*/
+void QUrlModel::addUrls(const QList<QUrl> &list, int row, bool move)
+{
+    if (row == -1)
+        row = rowCount();
+    row = qMin(row, rowCount());
+    for (int i = list.count() - 1; i >= 0; --i) {
+        QUrl url = list.at(i);
+        if (!url.isValid() || url.scheme() != QLatin1String("file"))
+            continue;
+        //this makes sure the url is clean
+        const QString cleanUrl = QDir::cleanPath(url.toLocalFile());
+        url = QUrl::fromLocalFile(cleanUrl);
+
+        for (int j = 0; move && j < rowCount(); ++j) {
+            QString local = index(j, 0).data(UrlRole).toUrl().toLocalFile();
+#if defined(Q_OS_WIN)
+            if (index(j, 0).data(UrlRole).toUrl().toLocalFile().toLower() == cleanUrl.toLower()) {
+#else
+            if (index(j, 0).data(UrlRole).toUrl().toLocalFile() == cleanUrl) {
+#endif
+                removeRow(j);
+                if (j <= row)
+                    row--;
+                break;
+            }
+        }
+        row = qMax(row, 0);
+        QModelIndex idx = fileSystemModel->index(cleanUrl);
+        if (!fileSystemModel->isDir(idx))
+            continue;
+        insertRows(row, 1);
+        setUrl(index(row, 0), url, idx);
+        watching.append(qMakePair(idx, cleanUrl));
+    }
+}
+
+/*!
+    Return the complete list of urls in a QList.
+*/
+QList<QUrl> QUrlModel::urls() const
+{
+    QList<QUrl> list;
+    for (int i = 0; i < rowCount(); ++i)
+        list.append(data(index(i, 0), UrlRole).toUrl());
+    return list;
+}
+
+/*!
+    QFileSystemModel to get index's from, clears existing rows
+*/
+void QUrlModel::setFileSystemModel(QFileSystemModel *model)
+{
+    if (model == fileSystemModel)
+        return;
+    if (fileSystemModel != 0) {
+        disconnect(model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
+            this, SLOT(dataChanged(const QModelIndex &, const QModelIndex &)));
+        disconnect(model, SIGNAL(layoutChanged()),
+            this, SLOT(layoutChanged()));
+        disconnect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
+            this, SLOT(layoutChanged()));
+    }
+    fileSystemModel = model;
+    if (fileSystemModel != 0) {
+        connect(model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
+            this, SLOT(dataChanged(const QModelIndex &, const QModelIndex &)));
+        connect(model, SIGNAL(layoutChanged()),
+            this, SLOT(layoutChanged()));
+        connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
+            this, SLOT(layoutChanged()));
+    }
+    clear();
+    insertColumns(0, 1);
+}
+
+/*
+    If one of the index's we are watching has changed update our internal data
+*/
+void QUrlModel::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+    QModelIndex parent = topLeft.parent();
+    for (int i = 0; i < watching.count(); ++i) {
+        QModelIndex index = watching.at(i).first;
+        if (index.model() && topLeft.model()) {
+            Q_ASSERT(index.model() == topLeft.model());
+        }
+        if (   index.row() >= topLeft.row()
+            && index.row() <= bottomRight.row()
+            && index.column() >= topLeft.column()
+            && index.column() <= bottomRight.column()
+            && index.parent() == parent) {
+                changed(watching.at(i).second);
+        }
+    }
+}
+
+/*!
+    Re-get all of our data, anything could have changed!
+ */
+void QUrlModel::layoutChanged()
+{
+    QStringList paths;
+    for (int i = 0; i < watching.count(); ++i)
+        paths.append(watching.at(i).second);
+    watching.clear();
+    for (int i = 0; i < paths.count(); ++i) {
+        QString path = paths.at(i);
+        QModelIndex newIndex = fileSystemModel->index(path);
+        watching.append(QPair<QModelIndex, QString>(newIndex, path));
+        if (newIndex.isValid())
+            changed(path);
+     }
+}
+
+/*!
+    The following path changed data update our copy of that data
+
+    \sa layoutChanged() dataChanged()
+*/
+void QUrlModel::changed(const QString &path)
+{
+    for (int i = 0; i < rowCount(); ++i) {
+        QModelIndex idx = index(i, 0);
+        if (idx.data(UrlRole).toUrl().toLocalFile() == path) {
+            setData(idx, idx.data(UrlRole).toUrl());
+        }
+    }
+}
+
+QSidebar::QSidebar(QWidget *parent) : QListView(parent)
+{
+}
+
+void QSidebar::init(QFileSystemModel *model, const QList<QUrl> &newUrls)
+{
+    // ### TODO make icon size dynamic
+    setIconSize(QSize(24,24));
+    setUniformItemSizes(true);
+    urlModel = new QUrlModel(this);
+    urlModel->setFileSystemModel(model);
+    setModel(urlModel);
+    setItemDelegate(new QSideBarDelegate(this));
+
+    connect(selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
+            this, SLOT(clicked(const QModelIndex &)));
+#ifndef QT_NO_DRAGANDDROP
+    setDragDropMode(QAbstractItemView::DragDrop);
+#endif
+    setContextMenuPolicy(Qt::CustomContextMenu);
+    connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
+            this, SLOT(showContextMenu(const QPoint &)));
+    urlModel->setUrls(newUrls);
+    setCurrentIndex(this->model()->index(0,0));
+}
+
+QSidebar::~QSidebar()
+{
+}
+
+#ifndef QT_NO_DRAGANDDROP
+void QSidebar::dragEnterEvent(QDragEnterEvent *event)
+{
+    if (urlModel->canDrop(event))
+        QListView::dragEnterEvent(event);
+}
+#endif // QT_NO_DRAGANDDROP
+
+QSize QSidebar::sizeHint() const
+{
+    if (model())
+        return QListView::sizeHintForIndex(model()->index(0, 0)) + QSize(2 * frameWidth(), 2 * frameWidth());
+    return QListView::sizeHint();
+}
+
+void QSidebar::selectUrl(const QUrl &url)
+{
+    disconnect(selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
+               this, SLOT(clicked(const QModelIndex &)));
+
+    selectionModel()->clear();
+    for (int i = 0; i < model()->rowCount(); ++i) {
+        if (model()->index(i, 0).data(QUrlModel::UrlRole).toUrl() == url) {
+            selectionModel()->select(model()->index(i, 0), QItemSelectionModel::Select);
+            break;
+        }
+    }
+
+    connect(selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
+            this, SLOT(clicked(const QModelIndex &)));
+}
+
+#ifndef QT_NO_MENU
+/*!
+    \internal
+
+    \sa removeEntry()
+*/
+void QSidebar::showContextMenu(const QPoint &position)
+{
+    QList<QAction *> actions;
+    if (indexAt(position).isValid()) {
+        QAction *action = new QAction(QFileDialog::tr("Remove"), this);
+        if (indexAt(position).data(QUrlModel::UrlRole).toUrl().path().isEmpty())
+            action->setEnabled(false);
+        connect(action, SIGNAL(triggered()), this, SLOT(removeEntry()));
+        actions.append(action);
+    }
+    if (actions.count() > 0)
+        QMenu::exec(actions, mapToGlobal(position));
+}
+#endif // QT_NO_MENU
+
+/*!
+    \internal
+
+    \sa showContextMenu()
+*/
+void QSidebar::removeEntry()
+{
+    QList<QModelIndex> idxs = selectionModel()->selectedIndexes();
+    QList<QPersistentModelIndex> indexes;
+    for (int i = 0; i < idxs.count(); i++)
+        indexes.append(idxs.at(i));
+
+    for (int i = 0; i < indexes.count(); ++i)
+        if (!indexes.at(i).data(QUrlModel::UrlRole).toUrl().path().isEmpty())
+            model()->removeRow(indexes.at(i).row());
+}
+
+/*!
+    \internal
+
+    \sa goToUrl()
+*/
+void QSidebar::clicked(const QModelIndex &index)
+{
+    QUrl url = model()->index(index.row(), 0).data(QUrlModel::UrlRole).toUrl();
+    emit goToUrl(url);
+    selectUrl(url);
+}
+
+/*!
+    \reimp
+    Don't automatically select something
+ */
+void QSidebar::focusInEvent(QFocusEvent *event)
+{
+    QAbstractScrollArea::focusInEvent(event);
+    viewport()->update();
+}
+
+/*!
+    \reimp
+ */
+bool QSidebar::event(QEvent * event)
+{
+    if (event->type() == QEvent::KeyRelease) {
+        QKeyEvent* ke = (QKeyEvent*) event;
+        if (ke->key() == Qt::Key_Delete) {
+            removeEntry();
+            return true;
+        }
+    }
+    return QListView::event(event);
+}
+
+QT_END_NAMESPACE
+
+#endif