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

Last change on this file since 651 was 651, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.2 sources.

File size: 15.5 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2010 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
70/*!
71 QUrlModel lets you have indexes from a QFileSystemModel to a list. When QFileSystemModel
72 changes them QUrlModel will automatically update.
73
74 Example usage: File dialog sidebar and combo box
75 */
76QUrlModel::QUrlModel(QObject *parent) : QStandardItemModel(parent), showFullPath(false), fileSystemModel(0)
77{
78}
79
80/*!
81 \reimp
82*/
83QStringList QUrlModel::mimeTypes() const
84{
85 return QStringList(QLatin1String("text/uri-list"));
86}
87
88/*!
89 \reimp
90*/
91Qt::ItemFlags QUrlModel::flags(const QModelIndex &index) const
92{
93 Qt::ItemFlags flags = QStandardItemModel::flags(index);
94 if (index.isValid()) {
95 flags &= ~Qt::ItemIsEditable;
96 // ### some future version could support "moving" urls onto a folder
97 flags &= ~Qt::ItemIsDropEnabled;
98 }
99
100 if (index.data(Qt::DecorationRole).isNull())
101 flags &= ~Qt::ItemIsEnabled;
102
103 return flags;
104}
105
106/*!
107 \reimp
108*/
109QMimeData *QUrlModel::mimeData(const QModelIndexList &indexes) const
110{
111 QList<QUrl> list;
112 for (int i = 0; i < indexes.count(); ++i) {
113 if (indexes.at(i).column() == 0)
114 list.append(indexes.at(i).data(UrlRole).toUrl());
115 }
116 QMimeData *data = new QMimeData();
117 data->setUrls(list);
118 return data;
119}
120
121#ifndef QT_NO_DRAGANDDROP
122
123/*!
124 Decide based upon the data if it should be accepted or not
125
126 We only accept dirs and not files
127*/
128bool QUrlModel::canDrop(QDragEnterEvent *event)
129{
130 if (!event->mimeData()->formats().contains(mimeTypes().first()))
131 return false;
132
133 const QList<QUrl> list = event->mimeData()->urls();
134 for (int i = 0; i < list.count(); ++i) {
135 QModelIndex idx = fileSystemModel->index(list.at(0).toLocalFile());
136 if (!fileSystemModel->isDir(idx))
137 return false;
138 }
139 return true;
140}
141
142/*!
143 \reimp
144*/
145bool QUrlModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
146 int row, int column, const QModelIndex &parent)
147{
148 if (!data->formats().contains(mimeTypes().first()))
149 return false;
150 Q_UNUSED(action);
151 Q_UNUSED(column);
152 Q_UNUSED(parent);
153 addUrls(data->urls(), row);
154 return true;
155}
156
157#endif // QT_NO_DRAGANDDROP
158
159/*!
160 \reimp
161
162 If the role is the UrlRole then handle otherwise just pass to QStandardItemModel
163*/
164bool QUrlModel::setData(const QModelIndex &index, const QVariant &value, int role)
165{
166 if (value.type() == QVariant::Url) {
167 QUrl url = value.toUrl();
168 QModelIndex dirIndex = fileSystemModel->index(url.toLocalFile());
169 //On windows the popup display the "C:\", convert to nativeSeparators
170 if (showFullPath)
171 QStandardItemModel::setData(index, QDir::toNativeSeparators(fileSystemModel->data(dirIndex, QFileSystemModel::FilePathRole).toString()));
172 else {
173 QStandardItemModel::setData(index, QDir::toNativeSeparators(fileSystemModel->data(dirIndex, QFileSystemModel::FilePathRole).toString()), Qt::ToolTipRole);
174 QStandardItemModel::setData(index, fileSystemModel->data(dirIndex).toString());
175 }
176 QStandardItemModel::setData(index, fileSystemModel->data(dirIndex, Qt::DecorationRole),
177 Qt::DecorationRole);
178 QStandardItemModel::setData(index, url, UrlRole);
179 return true;
180 }
181 return QStandardItemModel::setData(index, value, role);
182}
183
184void QUrlModel::setUrl(const QModelIndex &index, const QUrl &url, const QModelIndex &dirIndex)
185{
186 setData(index, url, UrlRole);
187 if (url.path().isEmpty()) {
188 setData(index, fileSystemModel->myComputer());
189 setData(index, fileSystemModel->myComputer(Qt::DecorationRole), Qt::DecorationRole);
190 } else {
191 QString newName;
192 if (showFullPath) {
193 //On windows the popup display the "C:\", convert to nativeSeparators
194 newName = QDir::toNativeSeparators(dirIndex.data(QFileSystemModel::FilePathRole).toString());
195 } else {
196 newName = dirIndex.data().toString();
197 }
198
199 QIcon newIcon = qvariant_cast<QIcon>(dirIndex.data(Qt::DecorationRole));
200 if (!dirIndex.isValid()) {
201 newIcon = fileSystemModel->iconProvider()->icon(QFileIconProvider::Folder);
202 newName = QFileInfo(url.toLocalFile()).fileName();
203 if (!invalidUrls.contains(url))
204 invalidUrls.append(url);
205 //The bookmark is invalid then we set to false the EnabledRole
206 setData(index, false, EnabledRole);
207 } else {
208 //The bookmark is valid then we set to true the EnabledRole
209 setData(index, true, EnabledRole);
210 }
211
212 // Make sure that we have at least 32x32 images
213 const QSize size = newIcon.actualSize(QSize(32,32));
214 if (size.width() < 32) {
215 QPixmap smallPixmap = newIcon.pixmap(QSize(32, 32));
216 newIcon.addPixmap(smallPixmap.scaledToWidth(32, Qt::SmoothTransformation));
217 }
218
219 if (index.data().toString() != newName)
220 setData(index, newName);
221 QIcon oldIcon = qvariant_cast<QIcon>(index.data(Qt::DecorationRole));
222 if (oldIcon.cacheKey() != newIcon.cacheKey())
223 setData(index, newIcon, Qt::DecorationRole);
224 }
225}
226
227void QUrlModel::setUrls(const QList<QUrl> &list)
228{
229 removeRows(0, rowCount());
230 invalidUrls.clear();
231 watching.clear();
232 addUrls(list, 0);
233}
234
235/*!
236 Add urls \a list into the list at \a row. If move then movie
237 existing ones to row.
238
239 \sa dropMimeData()
240*/
241void QUrlModel::addUrls(const QList<QUrl> &list, int row, bool move)
242{
243 if (row == -1)
244 row = rowCount();
245 row = qMin(row, rowCount());
246 for (int i = list.count() - 1; i >= 0; --i) {
247 QUrl url = list.at(i);
248 if (!url.isValid() || url.scheme() != QLatin1String("file"))
249 continue;
250 //this makes sure the url is clean
251 const QString cleanUrl = QDir::cleanPath(url.toLocalFile());
252 url = QUrl::fromLocalFile(cleanUrl);
253
254 for (int j = 0; move && j < rowCount(); ++j) {
255 QString local = index(j, 0).data(UrlRole).toUrl().toLocalFile();
256#if defined(Q_OS_WIN)
257 if (index(j, 0).data(UrlRole).toUrl().toLocalFile().toLower() == cleanUrl.toLower()) {
258#else
259 if (index(j, 0).data(UrlRole).toUrl().toLocalFile() == cleanUrl) {
260#endif
261 removeRow(j);
262 if (j <= row)
263 row--;
264 break;
265 }
266 }
267 row = qMax(row, 0);
268 QModelIndex idx = fileSystemModel->index(cleanUrl);
269 if (!fileSystemModel->isDir(idx))
270 continue;
271 insertRows(row, 1);
272 setUrl(index(row, 0), url, idx);
273 watching.append(qMakePair(idx, cleanUrl));
274 }
275}
276
277/*!
278 Return the complete list of urls in a QList.
279*/
280QList<QUrl> QUrlModel::urls() const
281{
282 QList<QUrl> list;
283 for (int i = 0; i < rowCount(); ++i)
284 list.append(data(index(i, 0), UrlRole).toUrl());
285 return list;
286}
287
288/*!
289 QFileSystemModel to get index's from, clears existing rows
290*/
291void QUrlModel::setFileSystemModel(QFileSystemModel *model)
292{
293 if (model == fileSystemModel)
294 return;
295 if (fileSystemModel != 0) {
296 disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
297 this, SLOT(dataChanged(QModelIndex,QModelIndex)));
298 disconnect(model, SIGNAL(layoutChanged()),
299 this, SLOT(layoutChanged()));
300 disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
301 this, SLOT(layoutChanged()));
302 }
303 fileSystemModel = model;
304 if (fileSystemModel != 0) {
305 connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
306 this, SLOT(dataChanged(QModelIndex,QModelIndex)));
307 connect(model, SIGNAL(layoutChanged()),
308 this, SLOT(layoutChanged()));
309 connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
310 this, SLOT(layoutChanged()));
311 }
312 clear();
313 insertColumns(0, 1);
314}
315
316/*
317 If one of the index's we are watching has changed update our internal data
318*/
319void QUrlModel::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
320{
321 QModelIndex parent = topLeft.parent();
322 for (int i = 0; i < watching.count(); ++i) {
323 QModelIndex index = watching.at(i).first;
324 if (index.model() && topLeft.model()) {
325 Q_ASSERT(index.model() == topLeft.model());
326 }
327 if ( index.row() >= topLeft.row()
328 && index.row() <= bottomRight.row()
329 && index.column() >= topLeft.column()
330 && index.column() <= bottomRight.column()
331 && index.parent() == parent) {
332 changed(watching.at(i).second);
333 }
334 }
335}
336
337/*!
338 Re-get all of our data, anything could have changed!
339 */
340void QUrlModel::layoutChanged()
341{
342 QStringList paths;
343 for (int i = 0; i < watching.count(); ++i)
344 paths.append(watching.at(i).second);
345 watching.clear();
346 for (int i = 0; i < paths.count(); ++i) {
347 QString path = paths.at(i);
348 QModelIndex newIndex = fileSystemModel->index(path);
349 watching.append(QPair<QModelIndex, QString>(newIndex, path));
350 if (newIndex.isValid())
351 changed(path);
352 }
353}
354
355/*!
356 The following path changed data update our copy of that data
357
358 \sa layoutChanged() dataChanged()
359*/
360void QUrlModel::changed(const QString &path)
361{
362 for (int i = 0; i < rowCount(); ++i) {
363 QModelIndex idx = index(i, 0);
364 if (idx.data(UrlRole).toUrl().toLocalFile() == path) {
365 setData(idx, idx.data(UrlRole).toUrl());
366 }
367 }
368}
369
370QSidebar::QSidebar(QWidget *parent) : QListView(parent)
371{
372}
373
374void QSidebar::init(QFileSystemModel *model, const QList<QUrl> &newUrls)
375{
376 // ### TODO make icon size dynamic
377 setIconSize(QSize(24,24));
378 setUniformItemSizes(true);
379 urlModel = new QUrlModel(this);
380 urlModel->setFileSystemModel(model);
381 setModel(urlModel);
382 setItemDelegate(new QSideBarDelegate(this));
383
384 connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
385 this, SLOT(clicked(QModelIndex)));
386#ifndef QT_NO_DRAGANDDROP
387 setDragDropMode(QAbstractItemView::DragDrop);
388#endif
389 setContextMenuPolicy(Qt::CustomContextMenu);
390 connect(this, SIGNAL(customContextMenuRequested(QPoint)),
391 this, SLOT(showContextMenu(QPoint)));
392 urlModel->setUrls(newUrls);
393 setCurrentIndex(this->model()->index(0,0));
394}
395
396QSidebar::~QSidebar()
397{
398}
399
400#ifndef QT_NO_DRAGANDDROP
401void QSidebar::dragEnterEvent(QDragEnterEvent *event)
402{
403 if (urlModel->canDrop(event))
404 QListView::dragEnterEvent(event);
405}
406#endif // QT_NO_DRAGANDDROP
407
408QSize QSidebar::sizeHint() const
409{
410 if (model())
411 return QListView::sizeHintForIndex(model()->index(0, 0)) + QSize(2 * frameWidth(), 2 * frameWidth());
412 return QListView::sizeHint();
413}
414
415void QSidebar::selectUrl(const QUrl &url)
416{
417 disconnect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
418 this, SLOT(clicked(QModelIndex)));
419
420 selectionModel()->clear();
421 for (int i = 0; i < model()->rowCount(); ++i) {
422 if (model()->index(i, 0).data(QUrlModel::UrlRole).toUrl() == url) {
423 selectionModel()->select(model()->index(i, 0), QItemSelectionModel::Select);
424 break;
425 }
426 }
427
428 connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
429 this, SLOT(clicked(QModelIndex)));
430}
431
432#ifndef QT_NO_MENU
433/*!
434 \internal
435
436 \sa removeEntry()
437*/
438void QSidebar::showContextMenu(const QPoint &position)
439{
440 QList<QAction *> actions;
441 if (indexAt(position).isValid()) {
442 QAction *action = new QAction(QFileDialog::tr("Remove"), this);
443 if (indexAt(position).data(QUrlModel::UrlRole).toUrl().path().isEmpty())
444 action->setEnabled(false);
445 connect(action, SIGNAL(triggered()), this, SLOT(removeEntry()));
446 actions.append(action);
447 }
448 if (actions.count() > 0)
449 QMenu::exec(actions, mapToGlobal(position));
450}
451#endif // QT_NO_MENU
452
453/*!
454 \internal
455
456 \sa showContextMenu()
457*/
458void QSidebar::removeEntry()
459{
460 QList<QModelIndex> idxs = selectionModel()->selectedIndexes();
461 QList<QPersistentModelIndex> indexes;
462 for (int i = 0; i < idxs.count(); i++)
463 indexes.append(idxs.at(i));
464
465 for (int i = 0; i < indexes.count(); ++i)
466 if (!indexes.at(i).data(QUrlModel::UrlRole).toUrl().path().isEmpty())
467 model()->removeRow(indexes.at(i).row());
468}
469
470/*!
471 \internal
472
473 \sa goToUrl()
474*/
475void QSidebar::clicked(const QModelIndex &index)
476{
477 QUrl url = model()->index(index.row(), 0).data(QUrlModel::UrlRole).toUrl();
478 emit goToUrl(url);
479 selectUrl(url);
480}
481
482/*!
483 \reimp
484 Don't automatically select something
485 */
486void QSidebar::focusInEvent(QFocusEvent *event)
487{
488 QAbstractScrollArea::focusInEvent(event);
489 viewport()->update();
490}
491
492/*!
493 \reimp
494 */
495bool QSidebar::event(QEvent * event)
496{
497 if (event->type() == QEvent::KeyRelease) {
498 QKeyEvent* ke = (QKeyEvent*) event;
499 if (ke->key() == Qt::Key_Delete) {
500 removeEntry();
501 return true;
502 }
503 }
504 return QListView::event(event);
505}
506
507QT_END_NAMESPACE
508
509#endif
Note: See TracBrowser for help on using the repository browser.