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

Last change on this file since 5 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

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