source: trunk/src/gui/dialogs/qsidebar.cpp

Last change on this file was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 16.0 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qsidebar_p.h"
43#include "qfilesystemmodel.h"
44
45#ifndef QT_NO_FILEDIALOG
46
47#include <qaction.h>
48#include <qurl.h>
49#include <qmenu.h>
50#include <qmimedata.h>
51#include <qevent.h>
52#include <qdebug.h>
53#include <qfileiconprovider.h>
54#include <qfiledialog.h>
55
56QT_BEGIN_NAMESPACE
57
58void QSideBarDelegate::initStyleOption(QStyleOptionViewItem *option,
59 const QModelIndex &index) const
60{
61 QStyledItemDelegate::initStyleOption(option,index);
62 QVariant value = index.data(QUrlModel::EnabledRole);
63 if (value.isValid()) {
64 //If the bookmark/entry is not enabled then we paint it in gray
65 if (!qvariant_cast<bool>(value))
66 option->state &= ~QStyle::State_Enabled;
67 }
68}
69
70QSize QSideBarDelegate::sizeHint (const QStyleOptionViewItem &option,
71 const QModelIndex &index) const
72{
73 // Make the item height in the side bar little bit bigger than in the
74 // file list view (to emphasize it). Note that this is better than
75 // previously used enforcing the icon size on the list with setIconSize()
76 // because it doesn't cause icon scaling (whigh results in blurred images)
77 QSize size = QStyledItemDelegate::sizeHint(option, index);
78 int height = size.height();
79 size.setHeight(height + height / 4);
80 return size;
81}
82
83/*!
84 QUrlModel lets you have indexes from a QFileSystemModel to a list. When QFileSystemModel
85 changes them QUrlModel will automatically update.
86
87 Example usage: File dialog sidebar and combo box
88 */
89QUrlModel::QUrlModel(QObject *parent) : QStandardItemModel(parent), showFullPath(false), fileSystemModel(0)
90{
91}
92
93/*!
94 \reimp
95*/
96QStringList QUrlModel::mimeTypes() const
97{
98 return QStringList(QLatin1String("text/uri-list"));
99}
100
101/*!
102 \reimp
103*/
104Qt::ItemFlags QUrlModel::flags(const QModelIndex &index) const
105{
106 Qt::ItemFlags flags = QStandardItemModel::flags(index);
107 if (index.isValid()) {
108 flags &= ~Qt::ItemIsEditable;
109 // ### some future version could support "moving" urls onto a folder
110 flags &= ~Qt::ItemIsDropEnabled;
111 }
112
113 if (index.data(Qt::DecorationRole).isNull())
114 flags &= ~Qt::ItemIsEnabled;
115
116 return flags;
117}
118
119/*!
120 \reimp
121*/
122QMimeData *QUrlModel::mimeData(const QModelIndexList &indexes) const
123{
124 QList<QUrl> list;
125 for (int i = 0; i < indexes.count(); ++i) {
126 if (indexes.at(i).column() == 0)
127 list.append(indexes.at(i).data(UrlRole).toUrl());
128 }
129 QMimeData *data = new QMimeData();
130 data->setUrls(list);
131 return data;
132}
133
134#ifndef QT_NO_DRAGANDDROP
135
136/*!
137 Decide based upon the data if it should be accepted or not
138
139 We only accept dirs and not files
140*/
141bool QUrlModel::canDrop(QDragEnterEvent *event)
142{
143 if (!event->mimeData()->formats().contains(mimeTypes().first()))
144 return false;
145
146 const QList<QUrl> list = event->mimeData()->urls();
147 for (int i = 0; i < list.count(); ++i) {
148 QModelIndex idx = fileSystemModel->index(list.at(0).toLocalFile());
149 if (!fileSystemModel->isDir(idx))
150 return false;
151 }
152 return true;
153}
154
155/*!
156 \reimp
157*/
158bool QUrlModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
159 int row, int column, const QModelIndex &parent)
160{
161 if (!data->formats().contains(mimeTypes().first()))
162 return false;
163 Q_UNUSED(action);
164 Q_UNUSED(column);
165 Q_UNUSED(parent);
166 addUrls(data->urls(), row);
167 return true;
168}
169
170#endif // QT_NO_DRAGANDDROP
171
172/*!
173 \reimp
174
175 If the role is the UrlRole then handle otherwise just pass to QStandardItemModel
176*/
177bool QUrlModel::setData(const QModelIndex &index, const QVariant &value, int role)
178{
179 if (value.type() == QVariant::Url) {
180 QUrl url = value.toUrl();
181 QModelIndex dirIndex = fileSystemModel->index(url.toLocalFile());
182 //On windows the popup display the "C:\", convert to nativeSeparators
183 if (showFullPath)
184 QStandardItemModel::setData(index, QDir::toNativeSeparators(fileSystemModel->data(dirIndex, QFileSystemModel::FilePathRole).toString()));
185 else {
186 QStandardItemModel::setData(index, QDir::toNativeSeparators(fileSystemModel->data(dirIndex, QFileSystemModel::FilePathRole).toString()), Qt::ToolTipRole);
187 QStandardItemModel::setData(index, fileSystemModel->data(dirIndex).toString());
188 }
189 QStandardItemModel::setData(index, fileSystemModel->data(dirIndex, Qt::DecorationRole),
190 Qt::DecorationRole);
191 QStandardItemModel::setData(index, url, UrlRole);
192 return true;
193 }
194 return QStandardItemModel::setData(index, value, role);
195}
196
197void QUrlModel::setUrl(const QModelIndex &index, const QUrl &url, const QModelIndex &dirIndex)
198{
199 setData(index, url, UrlRole);
200 if (url.path().isEmpty()) {
201 setData(index, fileSystemModel->myComputer());
202 setData(index, fileSystemModel->myComputer(Qt::DecorationRole), Qt::DecorationRole);
203 } else {
204 QString newName;
205 if (showFullPath) {
206 //On windows the popup display the "C:\", convert to nativeSeparators
207 newName = QDir::toNativeSeparators(dirIndex.data(QFileSystemModel::FilePathRole).toString());
208 } else {
209 newName = dirIndex.data().toString();
210 }
211
212 QIcon newIcon = qvariant_cast<QIcon>(dirIndex.data(Qt::DecorationRole));
213 if (!dirIndex.isValid()) {
214 newIcon = fileSystemModel->iconProvider()->icon(QFileIconProvider::Folder);
215 newName = QFileInfo(url.toLocalFile()).fileName();
216 if (!invalidUrls.contains(url))
217 invalidUrls.append(url);
218 //The bookmark is invalid then we set to false the EnabledRole
219 setData(index, false, EnabledRole);
220 } else {
221 //The bookmark is valid then we set to true the EnabledRole
222 setData(index, true, EnabledRole);
223 }
224
225 // Make sure that we have at least 32x32 images
226 const QSize size = newIcon.actualSize(QSize(32,32));
227 if (size.width() < 32) {
228 QPixmap smallPixmap = newIcon.pixmap(QSize(32, 32));
229 newIcon.addPixmap(smallPixmap.scaledToWidth(32, Qt::SmoothTransformation));
230 }
231
232 if (index.data().toString() != newName)
233 setData(index, newName);
234 QIcon oldIcon = qvariant_cast<QIcon>(index.data(Qt::DecorationRole));
235 if (oldIcon.cacheKey() != newIcon.cacheKey())
236 setData(index, newIcon, Qt::DecorationRole);
237 }
238}
239
240void QUrlModel::setUrls(const QList<QUrl> &list)
241{
242 removeRows(0, rowCount());
243 invalidUrls.clear();
244 watching.clear();
245 addUrls(list, 0);
246}
247
248/*!
249 Add urls \a list into the list at \a row. If move then movie
250 existing ones to row.
251
252 \sa dropMimeData()
253*/
254void QUrlModel::addUrls(const QList<QUrl> &list, int row, bool move)
255{
256 if (row == -1)
257 row = rowCount();
258 row = qMin(row, rowCount());
259 for (int i = list.count() - 1; i >= 0; --i) {
260 QUrl url = list.at(i);
261 if (!url.isValid() || url.scheme() != QLatin1String("file"))
262 continue;
263 //this makes sure the url is clean
264 const QString cleanUrl = QDir::cleanPath(url.toLocalFile());
265 url = QUrl::fromLocalFile(cleanUrl);
266
267 for (int j = 0; move && j < rowCount(); ++j) {
268 QString local = index(j, 0).data(UrlRole).toUrl().toLocalFile();
269#if defined(Q_OS_WIN)
270 if (index(j, 0).data(UrlRole).toUrl().toLocalFile().toLower() == cleanUrl.toLower()) {
271#else
272 if (index(j, 0).data(UrlRole).toUrl().toLocalFile() == cleanUrl) {
273#endif
274 removeRow(j);
275 if (j <= row)
276 row--;
277 break;
278 }
279 }
280 row = qMax(row, 0);
281 QModelIndex idx = fileSystemModel->index(cleanUrl);
282 if (!fileSystemModel->isDir(idx))
283 continue;
284 insertRows(row, 1);
285 setUrl(index(row, 0), url, idx);
286 watching.append(qMakePair(idx, cleanUrl));
287 }
288}
289
290/*!
291 Return the complete list of urls in a QList.
292*/
293QList<QUrl> QUrlModel::urls() const
294{
295 QList<QUrl> list;
296 for (int i = 0; i < rowCount(); ++i)
297 list.append(data(index(i, 0), UrlRole).toUrl());
298 return list;
299}
300
301/*!
302 QFileSystemModel to get index's from, clears existing rows
303*/
304void QUrlModel::setFileSystemModel(QFileSystemModel *model)
305{
306 if (model == fileSystemModel)
307 return;
308 if (fileSystemModel != 0) {
309 disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
310 this, SLOT(dataChanged(QModelIndex,QModelIndex)));
311 disconnect(model, SIGNAL(layoutChanged()),
312 this, SLOT(layoutChanged()));
313 disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
314 this, SLOT(layoutChanged()));
315 }
316 fileSystemModel = model;
317 if (fileSystemModel != 0) {
318 connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
319 this, SLOT(dataChanged(QModelIndex,QModelIndex)));
320 connect(model, SIGNAL(layoutChanged()),
321 this, SLOT(layoutChanged()));
322 connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
323 this, SLOT(layoutChanged()));
324 }
325 clear();
326 insertColumns(0, 1);
327}
328
329/*
330 If one of the index's we are watching has changed update our internal data
331*/
332void QUrlModel::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
333{
334 QModelIndex parent = topLeft.parent();
335 for (int i = 0; i < watching.count(); ++i) {
336 QModelIndex index = watching.at(i).first;
337 if (index.model() && topLeft.model()) {
338 Q_ASSERT(index.model() == topLeft.model());
339 }
340 if ( index.row() >= topLeft.row()
341 && index.row() <= bottomRight.row()
342 && index.column() >= topLeft.column()
343 && index.column() <= bottomRight.column()
344 && index.parent() == parent) {
345 changed(watching.at(i).second);
346 }
347 }
348}
349
350/*!
351 Re-get all of our data, anything could have changed!
352 */
353void QUrlModel::layoutChanged()
354{
355 QStringList paths;
356 for (int i = 0; i < watching.count(); ++i)
357 paths.append(watching.at(i).second);
358 watching.clear();
359 for (int i = 0; i < paths.count(); ++i) {
360 QString path = paths.at(i);
361 QModelIndex newIndex = fileSystemModel->index(path);
362 watching.append(QPair<QModelIndex, QString>(newIndex, path));
363 if (newIndex.isValid())
364 changed(path);
365 }
366}
367
368/*!
369 The following path changed data update our copy of that data
370
371 \sa layoutChanged() dataChanged()
372*/
373void QUrlModel::changed(const QString &path)
374{
375 for (int i = 0; i < rowCount(); ++i) {
376 QModelIndex idx = index(i, 0);
377 if (idx.data(UrlRole).toUrl().toLocalFile() == path) {
378 setData(idx, idx.data(UrlRole).toUrl());
379 }
380 }
381}
382
383QSidebar::QSidebar(QWidget *parent) : QListView(parent)
384{
385}
386
387void QSidebar::init(QFileSystemModel *model, const QList<QUrl> &newUrls)
388{
389 setUniformItemSizes(true);
390 urlModel = new QUrlModel(this);
391 urlModel->setFileSystemModel(model);
392 setModel(urlModel);
393 setItemDelegate(new QSideBarDelegate(this));
394
395 connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
396 this, SLOT(clicked(QModelIndex)));
397#ifndef QT_NO_DRAGANDDROP
398 setDragDropMode(QAbstractItemView::DragDrop);
399#endif
400 setContextMenuPolicy(Qt::CustomContextMenu);
401 connect(this, SIGNAL(customContextMenuRequested(QPoint)),
402 this, SLOT(showContextMenu(QPoint)));
403 urlModel->setUrls(newUrls);
404 setCurrentIndex(this->model()->index(0,0));
405}
406
407QSidebar::~QSidebar()
408{
409}
410
411#ifndef QT_NO_DRAGANDDROP
412void QSidebar::dragEnterEvent(QDragEnterEvent *event)
413{
414 if (urlModel->canDrop(event))
415 QListView::dragEnterEvent(event);
416}
417#endif // QT_NO_DRAGANDDROP
418
419QSize QSidebar::sizeHint() const
420{
421 if (model())
422 return QListView::sizeHintForIndex(model()->index(0, 0)) + QSize(2 * frameWidth(), 2 * frameWidth());
423 return QListView::sizeHint();
424}
425
426void QSidebar::selectUrl(const QUrl &url)
427{
428 disconnect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
429 this, SLOT(clicked(QModelIndex)));
430
431 selectionModel()->clear();
432 for (int i = 0; i < model()->rowCount(); ++i) {
433 if (model()->index(i, 0).data(QUrlModel::UrlRole).toUrl() == url) {
434 selectionModel()->select(model()->index(i, 0), QItemSelectionModel::Select);
435 break;
436 }
437 }
438
439 connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
440 this, SLOT(clicked(QModelIndex)));
441}
442
443#ifndef QT_NO_MENU
444/*!
445 \internal
446
447 \sa removeEntry()
448*/
449void QSidebar::showContextMenu(const QPoint &position)
450{
451 QList<QAction *> actions;
452 if (indexAt(position).isValid()) {
453 QAction *action = new QAction(QFileDialog::tr("Remove"), this);
454 if (indexAt(position).data(QUrlModel::UrlRole).toUrl().path().isEmpty())
455 action->setEnabled(false);
456 connect(action, SIGNAL(triggered()), this, SLOT(removeEntry()));
457 actions.append(action);
458 }
459 if (actions.count() > 0)
460 QMenu::exec(actions, mapToGlobal(position));
461}
462#endif // QT_NO_MENU
463
464/*!
465 \internal
466
467 \sa showContextMenu()
468*/
469void QSidebar::removeEntry()
470{
471 QList<QModelIndex> idxs = selectionModel()->selectedIndexes();
472 QList<QPersistentModelIndex> indexes;
473 for (int i = 0; i < idxs.count(); i++)
474 indexes.append(idxs.at(i));
475
476 for (int i = 0; i < indexes.count(); ++i)
477 if (!indexes.at(i).data(QUrlModel::UrlRole).toUrl().path().isEmpty())
478 model()->removeRow(indexes.at(i).row());
479}
480
481/*!
482 \internal
483
484 \sa goToUrl()
485*/
486void QSidebar::clicked(const QModelIndex &index)
487{
488 QUrl url = model()->index(index.row(), 0).data(QUrlModel::UrlRole).toUrl();
489 emit goToUrl(url);
490 selectUrl(url);
491}
492
493/*!
494 \reimp
495 Don't automatically select something
496 */
497void QSidebar::focusInEvent(QFocusEvent *event)
498{
499 QAbstractScrollArea::focusInEvent(event);
500 viewport()->update();
501}
502
503/*!
504 \reimp
505 */
506bool QSidebar::event(QEvent * event)
507{
508 if (event->type() == QEvent::KeyRelease) {
509 QKeyEvent* ke = (QKeyEvent*) event;
510 if (ke->key() == Qt::Key_Delete) {
511 removeEntry();
512 return true;
513 }
514 }
515 return QListView::event(event);
516}
517
518QT_END_NAMESPACE
519
520#endif
Note: See TracBrowser for help on using the repository browser.