source: trunk/src/gui/itemviews/qfileiconprovider.cpp@ 829

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

gui: Return a correct system icon (folder or file) for a non-existent path based on some heuristics, instead of a default Qt icon which doesn't match the style.

File size: 19.8 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
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 qt-info@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qfileiconprovider.h"
43
44#ifndef QT_NO_FILEICONPROVIDER
45#include <qstyle.h>
46#include <qapplication.h>
47#include <qdir.h>
48#include <qpixmapcache.h>
49#if defined(Q_WS_WIN)
50# define _WIN32_IE 0x0500
51# include <qt_windows.h>
52# include <commctrl.h>
53# include <objbase.h>
54#elif defined(Q_WS_PM)
55# define INCL_DOSDEVIOCTL
56# include <qt_os2.h>
57#elif defined(Q_WS_MAC)
58# include <private/qt_cocoa_helpers_mac_p.h>
59#endif
60
61#include <private/qfunctions_p.h>
62#include <private/qguiplatformplugin_p.h>
63
64#if defined(Q_WS_X11) && !defined(Q_NO_STYLE_GTK)
65# include <private/qgtkstyle_p.h>
66# include <private/qt_x11_p.h>
67#endif
68
69#ifndef SHGFI_ADDOVERLAYS
70# define SHGFI_ADDOVERLAYS 0x000000020
71# define SHGFI_OVERLAYINDEX 0x000000040
72#endif
73
74QT_BEGIN_NAMESPACE
75
76/*!
77 \class QFileIconProvider
78
79 \brief The QFileIconProvider class provides file icons for the QDirModel class.
80*/
81
82/*!
83 \enum QFileIconProvider::IconType
84 \value Computer
85 \value Desktop
86 \value Trashcan
87 \value Network
88 \value Drive
89 \value Folder
90 \value File
91*/
92
93class QFileIconProviderPrivate
94{
95 Q_DECLARE_PUBLIC(QFileIconProvider)
96
97public:
98 QFileIconProviderPrivate();
99 QIcon getIcon(QStyle::StandardPixmap name) const;
100#ifdef Q_WS_WIN
101 QIcon getWinIcon(const QFileInfo &fi) const;
102#elif defined(Q_WS_PM)
103 QIcon getPmIcon(const QFileInfo &fi) const;
104#elif defined(Q_WS_MAC)
105 QIcon getMacIcon(const QFileInfo &fi) const;
106#endif
107 QFileIconProvider *q_ptr;
108 QString homePath;
109
110private:
111 QIcon file;
112 QIcon fileLink;
113 QIcon directory;
114 QIcon directoryLink;
115 QIcon harddisk;
116 QIcon floppy;
117 QIcon cdrom;
118 QIcon ram;
119 QIcon network;
120 QIcon computer;
121 QIcon desktop;
122 QIcon trashcan;
123 QIcon generic;
124 QIcon home;
125};
126
127QFileIconProviderPrivate::QFileIconProviderPrivate()
128{
129 QStyle *style = QApplication::style();
130 file = style->standardIcon(QStyle::SP_FileIcon);
131 directory = style->standardIcon(QStyle::SP_DirIcon);
132 fileLink = style->standardIcon(QStyle::SP_FileLinkIcon);
133 directoryLink = style->standardIcon(QStyle::SP_DirLinkIcon);
134 harddisk = style->standardIcon(QStyle::SP_DriveHDIcon);
135 floppy = style->standardIcon(QStyle::SP_DriveFDIcon);
136 cdrom = style->standardIcon(QStyle::SP_DriveCDIcon);
137 network = style->standardIcon(QStyle::SP_DriveNetIcon);
138 computer = style->standardIcon(QStyle::SP_ComputerIcon);
139 desktop = style->standardIcon(QStyle::SP_DesktopIcon);
140 trashcan = style->standardIcon(QStyle::SP_TrashIcon);
141 home = style->standardIcon(QStyle::SP_DirHomeIcon);
142 homePath = QDir::home().absolutePath();
143}
144
145QIcon QFileIconProviderPrivate::getIcon(QStyle::StandardPixmap name) const
146{
147 switch (name) {
148 case QStyle::SP_FileIcon:
149 return file;
150 case QStyle::SP_FileLinkIcon:
151 return fileLink;
152 case QStyle::SP_DirIcon:
153 return directory;
154 case QStyle::SP_DirLinkIcon:
155 return directoryLink;
156 case QStyle::SP_DriveHDIcon:
157 return harddisk;
158 case QStyle::SP_DriveFDIcon:
159 return floppy;
160 case QStyle::SP_DriveCDIcon:
161 return cdrom;
162 case QStyle::SP_DriveNetIcon:
163 return network;
164 case QStyle::SP_ComputerIcon:
165 return computer;
166 case QStyle::SP_DesktopIcon:
167 return desktop;
168 case QStyle::SP_TrashIcon:
169 return trashcan;
170 case QStyle::SP_DirHomeIcon:
171 return home;
172 default:
173 return QIcon();
174 }
175 return QIcon();
176}
177
178/*!
179 Constructs a file icon provider.
180*/
181
182QFileIconProvider::QFileIconProvider()
183 : d_ptr(new QFileIconProviderPrivate)
184{
185}
186
187/*!
188 Destroys the file icon provider.
189
190*/
191
192QFileIconProvider::~QFileIconProvider()
193{
194}
195
196/*!
197 Returns an icon set for the given \a type.
198*/
199
200QIcon QFileIconProvider::icon(IconType type) const
201{
202 Q_D(const QFileIconProvider);
203 switch (type) {
204 case Computer:
205 return d->getIcon(QStyle::SP_ComputerIcon);
206 case Desktop:
207 return d->getIcon(QStyle::SP_DesktopIcon);
208 case Trashcan:
209 return d->getIcon(QStyle::SP_TrashIcon);
210 case Network:
211 return d->getIcon(QStyle::SP_DriveNetIcon);
212 case Drive:
213 return d->getIcon(QStyle::SP_DriveHDIcon);
214 case Folder:
215 return d->getIcon(QStyle::SP_DirIcon);
216 case File:
217 return d->getIcon(QStyle::SP_FileIcon);
218 default:
219 break;
220 };
221 return QIcon();
222}
223
224#ifdef Q_WS_WIN
225QIcon QFileIconProviderPrivate::getWinIcon(const QFileInfo &fileInfo) const
226{
227 QIcon retIcon;
228 const QString fileExtension = QLatin1Char('.') + fileInfo.suffix().toUpper();
229
230 QString key;
231 if (fileInfo.isFile() && !fileInfo.isExecutable() && !fileInfo.isSymLink())
232 key = QLatin1String("qt_") + fileExtension;
233
234 QPixmap pixmap;
235 if (!key.isEmpty()) {
236 QPixmapCache::find(key, pixmap);
237 }
238
239 if (!pixmap.isNull()) {
240 retIcon.addPixmap(pixmap);
241 if (QPixmapCache::find(key + QLatin1Char('l'), pixmap))
242 retIcon.addPixmap(pixmap);
243 return retIcon;
244 }
245
246 /* We don't use the variable, but by storing it statically, we
247 * ensure CoInitialize is only called once. */
248 static HRESULT comInit = CoInitialize(NULL);
249 Q_UNUSED(comInit);
250
251 SHFILEINFO info;
252 unsigned long val = 0;
253
254 //Get the small icon
255#ifndef Q_OS_WINCE
256 val = SHGetFileInfo((const wchar_t *)QDir::toNativeSeparators(fileInfo.filePath()).utf16(), 0, &info,
257 sizeof(SHFILEINFO), SHGFI_ICON|SHGFI_SMALLICON|SHGFI_SYSICONINDEX|SHGFI_ADDOVERLAYS|SHGFI_OVERLAYINDEX);
258#else
259 val = SHGetFileInfo((const wchar_t *)QDir::toNativeSeparators(fileInfo.filePath()).utf16(), 0, &info,
260 sizeof(SHFILEINFO), SHGFI_SMALLICON|SHGFI_SYSICONINDEX);
261#endif
262
263 // Even if GetFileInfo returns a valid result, hIcon can be empty in some cases
264 if (val && info.hIcon) {
265 if (fileInfo.isDir() && !fileInfo.isRoot()) {
266 //using the unique icon index provided by windows save us from duplicate keys
267 key = QString::fromLatin1("qt_dir_%1").arg(info.iIcon);
268 QPixmapCache::find(key, pixmap);
269 if (!pixmap.isNull()) {
270 retIcon.addPixmap(pixmap);
271 if (QPixmapCache::find(key + QLatin1Char('l'), pixmap))
272 retIcon.addPixmap(pixmap);
273 DestroyIcon(info.hIcon);
274 return retIcon;
275 }
276 }
277 if (pixmap.isNull()) {
278#ifndef Q_OS_WINCE
279 pixmap = QPixmap::fromWinHICON(info.hIcon);
280#else
281 pixmap = QPixmap::fromWinHICON(ImageList_GetIcon((HIMAGELIST) val, info.iIcon, ILD_NORMAL));
282#endif
283 if (!pixmap.isNull()) {
284 retIcon.addPixmap(pixmap);
285 if (!key.isEmpty())
286 QPixmapCache::insert(key, pixmap);
287 }
288 else {
289 qWarning("QFileIconProviderPrivate::getWinIcon() no small icon found");
290 }
291 }
292 DestroyIcon(info.hIcon);
293 }
294
295 //Get the big icon
296#ifndef Q_OS_WINCE
297 val = SHGetFileInfo((const wchar_t *)QDir::toNativeSeparators(fileInfo.filePath()).utf16(), 0, &info,
298 sizeof(SHFILEINFO), SHGFI_ICON|SHGFI_LARGEICON|SHGFI_SYSICONINDEX|SHGFI_ADDOVERLAYS|SHGFI_OVERLAYINDEX);
299#else
300 val = SHGetFileInfo((const wchar_t *)QDir::toNativeSeparators(fileInfo.filePath()).utf16(), 0, &info,
301 sizeof(SHFILEINFO), SHGFI_LARGEICON|SHGFI_SYSICONINDEX);
302#endif
303 if (val && info.hIcon) {
304 if (fileInfo.isDir() && !fileInfo.isRoot()) {
305 //using the unique icon index provided by windows save us from duplicate keys
306 key = QString::fromLatin1("qt_dir_%1").arg(info.iIcon);
307 }
308#ifndef Q_OS_WINCE
309 pixmap = QPixmap::fromWinHICON(info.hIcon);
310#else
311 pixmap = QPixmap::fromWinHICON(ImageList_GetIcon((HIMAGELIST) val, info.iIcon, ILD_NORMAL));
312#endif
313 if (!pixmap.isNull()) {
314 retIcon.addPixmap(pixmap);
315 if (!key.isEmpty())
316 QPixmapCache::insert(key + QLatin1Char('l'), pixmap);
317 }
318 else {
319 qWarning("QFileIconProviderPrivate::getWinIcon() no large icon found");
320 }
321 DestroyIcon(info.hIcon);
322 }
323 return retIcon;
324}
325
326#elif defined(Q_WS_PM)
327QIcon QFileIconProviderPrivate::getPmIcon(const QFileInfo &fileInfo) const
328{
329 QIcon retIcon;
330
331 if (fileInfo.isRoot()) {
332 // Unfortunately, WinLoadFileIcon() returns a regular folder icon for
333 // paths like "C:\" (and nothing for "C:") instead of a drive icon.
334 // Getting the latter involves calling WPS object methods so we leave it
335 // out for now and let the stock Qt drive-specific icons be used instead.
336 return retIcon;
337 }
338
339 QByteArray path = QDir::toNativeSeparators(
340 QDir::cleanPath(fileInfo.absoluteFilePath())).toLocal8Bit();
341 HPOINTER hicon = WinLoadFileIcon(path, FALSE);
342 if (hicon == NULLHANDLE) {
343 // WinLoadFileIcon() fails on non-existing paths but we still want a
344 // system icon for it to be "in tune" with other icons. One known case
345 // is a directory on an NDFS SMB multi-resource share that links to a
346 // remote CD-ROM drive with no disk in it. This actually represents a
347 // more common problem when an icon for a non-existent path is
348 // requested. We will solve it by providing an icon of a known-to-exist
349 // path element instead. In order to differentiate between a
350 // non-existent folder and file we use a return code from
351 // DosQueryPathInfo() which appears to be ERROR_PATH_NOT_FOUND for the
352 // SMB multi-resource case and ERROR_FILE_NOT_FOUND otherwise. Note that
353 // a calling app may actually already know if it's a file or a directory
354 // from the listing of a parent directory (or we could do such a listing
355 // here) but it looks like too much hassle to use this approach here.
356 FILESTATUS3 info;
357 APIRET arc = DosQueryPathInfo(path, FIL_STANDARD, &info, sizeof(info));
358 if (arc == ERROR_PATH_NOT_FOUND) {
359 // a known-to-exist directory
360 hicon = WinLoadFileIcon("\\", FALSE);
361 } else {
362 // a known-to-exist file
363 static char kernelFilePath[] = "?:\\OS2KRNL";
364 if (kernelFilePath[0] == '?') {
365 ULONG bootDrive = 0;
366 DosQuerySysInfo(QSV_BOOT_DRIVE, QSV_BOOT_DRIVE,
367 (PVOID)&bootDrive, sizeof(bootDrive));
368 kernelFilePath[0] = bootDrive + 'A' - 1;
369 }
370 hicon = WinLoadFileIcon(kernelFilePath, FALSE);
371 }
372 }
373 if (hicon != NULLHANDLE) {
374 // we're requesting the system (shared) icon handle which should be
375 // always the same for the given file until the icon is changed, so use
376 // the bitmap handles as a key in the pixmap cache
377 QString key = QString(QLatin1String("qt_hicon_%1")).arg(hicon);
378 QString keyMini = key + QLatin1String("_m");
379 QPixmap pixmap;
380 QPixmapCache::find(key, pixmap);
381 if (!pixmap.isNull()) {
382 retIcon.addPixmap(pixmap);
383 QPixmapCache::find(keyMini, pixmap);
384 if (!pixmap.isNull())
385 retIcon.addPixmap(pixmap);
386 } else {
387 QPixmap mini;
388 retIcon = QPixmap::fromPmHPOINTER(hicon, &pixmap, &mini);
389 if (!retIcon.isNull()) {
390 // store pixmaps in the cache
391 Q_ASSERT(!pixmap.isNull() || !mini.isNull());
392 if (!pixmap.isNull())
393 QPixmapCache::insert(key, pixmap);
394 if (!mini.isNull())
395 QPixmapCache::insert(keyMini, mini);
396 }
397 }
398 }
399
400 return retIcon;
401}
402
403#elif defined(Q_WS_MAC)
404QIcon QFileIconProviderPrivate::getMacIcon(const QFileInfo &fi) const
405{
406 QIcon retIcon;
407 QString fileExtension = fi.suffix().toUpper();
408 fileExtension.prepend(QLatin1String("."));
409
410 const QString keyBase = QLatin1String("qt_") + fileExtension;
411
412 QPixmap pixmap;
413 if (fi.isFile() && !fi.isExecutable() && !fi.isSymLink()) {
414 QPixmapCache::find(keyBase + QLatin1String("16"), pixmap);
415 }
416
417 if (!pixmap.isNull()) {
418 retIcon.addPixmap(pixmap);
419 if (QPixmapCache::find(keyBase + QLatin1String("32"), pixmap)) {
420 retIcon.addPixmap(pixmap);
421 if (QPixmapCache::find(keyBase + QLatin1String("64"), pixmap)) {
422 retIcon.addPixmap(pixmap);
423 if (QPixmapCache::find(keyBase + QLatin1String("128"), pixmap)) {
424 retIcon.addPixmap(pixmap);
425 return retIcon;
426 }
427 }
428 }
429 }
430
431
432 FSRef macRef;
433 OSStatus status = FSPathMakeRef(reinterpret_cast<const UInt8*>(fi.canonicalFilePath().toUtf8().constData()),
434 &macRef, 0);
435 if (status != noErr)
436 return retIcon;
437 FSCatalogInfo info;
438 HFSUniStr255 macName;
439 status = FSGetCatalogInfo(&macRef, kIconServicesCatalogInfoMask, &info, &macName, 0, 0);
440 if (status != noErr)
441 return retIcon;
442 IconRef iconRef;
443 SInt16 iconLabel;
444 status = GetIconRefFromFileInfo(&macRef, macName.length, macName.unicode,
445 kIconServicesCatalogInfoMask, &info, kIconServicesNormalUsageFlag,
446 &iconRef, &iconLabel);
447 if (status != noErr)
448 return retIcon;
449 qt_mac_constructQIconFromIconRef(iconRef, 0, &retIcon);
450 ReleaseIconRef(iconRef);
451
452 pixmap = retIcon.pixmap(16);
453 QPixmapCache::insert(keyBase + QLatin1String("16"), pixmap);
454 pixmap = retIcon.pixmap(32);
455 QPixmapCache::insert(keyBase + QLatin1String("32"), pixmap);
456 pixmap = retIcon.pixmap(64);
457 QPixmapCache::insert(keyBase + QLatin1String("64"), pixmap);
458 pixmap = retIcon.pixmap(128);
459 QPixmapCache::insert(keyBase + QLatin1String("128"), pixmap);
460
461 return retIcon;
462}
463#endif
464
465
466/*!
467 Returns an icon for the file described by \a info.
468*/
469
470QIcon QFileIconProvider::icon(const QFileInfo &info) const
471{
472 Q_D(const QFileIconProvider);
473
474 QIcon platformIcon = qt_guiPlatformPlugin()->fileSystemIcon(info);
475 if (!platformIcon.isNull())
476 return platformIcon;
477
478#if defined(Q_WS_X11) && !defined(QT_NO_STYLE_GTK)
479 if (X11->desktopEnvironment == DE_GNOME) {
480 QIcon gtkIcon = QGtkStylePrivate::getFilesystemIcon(info);
481 if (!gtkIcon.isNull())
482 return gtkIcon;
483 }
484#endif
485
486#ifdef Q_WS_MAC
487 QIcon retIcon = d->getMacIcon(info);
488 if (!retIcon.isNull())
489 return retIcon;
490#elif defined Q_WS_WIN
491 QIcon icon = d->getWinIcon(info);
492 if (!icon.isNull())
493 return icon;
494#elif defined Q_WS_PM
495 if (QApplication::desktopSettingsAware()) {
496 QIcon icon= d->getPmIcon(info);
497 if (!icon.isNull())
498 return icon;
499 }
500#endif
501 if (info.isRoot())
502#if defined (Q_WS_WIN) && !defined(Q_WS_WINCE)
503 {
504 UINT type = GetDriveType((wchar_t *)info.absoluteFilePath().utf16());
505
506 switch (type) {
507 case DRIVE_REMOVABLE:
508 return d->getIcon(QStyle::SP_DriveFDIcon);
509 case DRIVE_FIXED:
510 return d->getIcon(QStyle::SP_DriveHDIcon);
511 case DRIVE_REMOTE:
512 return d->getIcon(QStyle::SP_DriveNetIcon);
513 case DRIVE_CDROM:
514 return d->getIcon(QStyle::SP_DriveCDIcon);
515 case DRIVE_RAMDISK:
516 case DRIVE_UNKNOWN:
517 case DRIVE_NO_ROOT_DIR:
518 default:
519 return d->getIcon(QStyle::SP_DriveHDIcon);
520 }
521 }
522#elif defined(Q_WS_PM)
523 {
524 UCHAR ioc_parm[2];
525 BIOSPARAMETERBLOCK bpb;
526 ioc_parm[0] = 0;
527 ioc_parm[1] = info.absoluteFilePath().at(0).toUpper().cell() - 'A';
528 APIRET arc = DosDevIOCtl((HFILE) - 1, IOCTL_DISK, DSK_GETDEVICEPARAMS,
529 ioc_parm, sizeof(ioc_parm), NULL,
530 &bpb, sizeof(bpb), NULL);
531
532 if (arc == ERROR_NOT_SUPPORTED)
533 return d->getIcon(QStyle::SP_DriveNetIcon);
534
535 if (arc == NO_ERROR && bpb.bDeviceType != DEVTYPE_FIXED) {
536 if (bpb.fsDeviceAttr & 0x10) // floppy format
537 return d->getIcon(QStyle::SP_DriveFDIcon);
538 if (!(bpb.fsDeviceAttr & 0x08)) // partitionable removable
539 return d->getIcon(QStyle::SP_DriveCDIcon);
540 }
541
542 return d->getIcon(QStyle::SP_DriveHDIcon);
543 }
544#else
545 return d->getIcon(QStyle::SP_DriveHDIcon);
546#endif
547 if (info.isFile()) {
548 if (info.isSymLink())
549 return d->getIcon(QStyle::SP_FileLinkIcon);
550 else
551 return d->getIcon(QStyle::SP_FileIcon);
552 }
553 if (info.isDir()) {
554 if (info.isSymLink()) {
555 return d->getIcon(QStyle::SP_DirLinkIcon);
556 } else {
557 if (info.absoluteFilePath() == d->homePath) {
558 return d->getIcon(QStyle::SP_DirHomeIcon);
559 } else {
560 return d->getIcon(QStyle::SP_DirIcon);
561 }
562 }
563 }
564 return QIcon();
565}
566
567/*!
568 Returns the type of the file described by \a info.
569*/
570
571QString QFileIconProvider::type(const QFileInfo &info) const
572{
573 if (info.isRoot())
574 return QApplication::translate("QFileDialog", "Drive");
575 if (info.isFile()) {
576 if (!info.suffix().isEmpty())
577 return info.suffix() + QLatin1Char(' ') + QApplication::translate("QFileDialog", "File");
578 return QApplication::translate("QFileDialog", "File");
579 }
580
581 if (info.isDir())
582#ifdef Q_WS_WIN
583 return QApplication::translate("QFileDialog", "File Folder", "Match Windows Explorer");
584#else
585 return QApplication::translate("QFileDialog", "Folder", "All other platforms");
586#endif
587 // Windows - "File Folder"
588 // OS X - "Folder"
589 // Konqueror - "Folder"
590 // Nautilus - "folder"
591
592 if (info.isSymLink())
593#ifdef Q_OS_MAC
594 return QApplication::translate("QFileDialog", "Alias", "Mac OS X Finder");
595#else
596 return QApplication::translate("QFileDialog", "Shortcut", "All other platforms");
597#endif
598 // OS X - "Alias"
599 // Windows - "Shortcut"
600 // Konqueror - "Folder" or "TXT File" i.e. what it is pointing to
601 // Nautilus - "link to folder" or "link to object file", same as Konqueror
602
603 return QApplication::translate("QFileDialog", "Unknown");
604}
605
606QT_END_NAMESPACE
607
608#endif
Note: See TracBrowser for help on using the repository browser.