2 * Copyright 2003, 2004, 2005 Martin Fuchs
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 // Martin Fuchs, 23.07.2003
31 //#include "entries.h"
34 // allocate and initialise a directory entry
35 Entry::Entry(ENTRY_TYPE etype
)
45 _icon_id
= ICID_UNKNOWN
;
46 _display_name
= _data
.cFileName
;
51 Entry::Entry(Entry
* parent
, ENTRY_TYPE etype
)
61 _icon_id
= ICID_UNKNOWN
;
63 _display_name
= _data
.cFileName
;
68 Entry::Entry(const Entry
& other
)
78 _expanded
= other
._expanded
;
79 _scanned
= other
._scanned
;
80 _level
= other
._level
;
84 _shell_attribs
= other
._shell_attribs
;
85 _display_name
= other
._display_name
==other
._data
.cFileName
? _data
.cFileName
: _tcsdup(other
._display_name
);
86 _type_name
= other
._type_name
? _tcsdup(other
._type_name
): NULL
;
87 _content
= other
._content
? _tcsdup(other
._content
): NULL
;
89 _etype
= other
._etype
;
90 _icon_id
= other
._icon_id
;
93 _bhfi_valid
= other
._bhfi_valid
;
96 // free a directory entry
99 if (_icon_id
> ICID_NONE
)
100 g_Globals
._icon_cache
.free_icon(_icon_id
);
102 if (_display_name
!= _data
.cFileName
)
113 // read directory tree and expand to the given location
114 Entry
* Entry::read_tree(const void* path
, SORT_ORDER sortOrder
, int scan_flags
)
116 CONTEXT("Entry::read_tree()");
122 for(const void*p
=path
; p
&& entry
; ) {
123 entry
->smart_scan(sortOrder
, scan_flags
);
126 entry
->_expanded
= true;
128 Entry
* found
= entry
->find_entry(p
);
129 p
= entry
->get_next_path_component(p
);
138 void Entry::read_directory_base(SORT_ORDER sortOrder
, int scan_flags
)
140 CONTEXT("Entry::read_directory_base()");
142 // call into subclass
143 read_directory(scan_flags
);
146 if (g_Globals
._prescan_nodes
) { //@todo _prescan_nodes should not be used for reading the start menu.
147 for(Entry
*entry
=_down
; entry
; entry
=entry
->_next
)
148 if (entry
->_data
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
149 entry
->read_directory(scan_flags
);
150 entry
->sort_directory(sortOrder
);
155 sort_directory(sortOrder
);
161 memset(this, 0, sizeof(Root
));
167 _entry
->free_subentries();
173 // sort order for different directory/file types
183 // distinguish between ".", ".." and any other directory names
184 static TYPE_ORDER
TypeOrderFromDirname(LPCTSTR name
)
186 if (name
[0] == '.') {
188 return TO_DOT
; // "."
190 if (name
[1]=='.' && name
[2]=='\0')
191 return TO_DOTDOT
; // ".."
194 return TO_OTHER_DIR
; // any other directory
197 // directories first...
198 static int compareType(const Entry
* entry1
, const Entry
* entry2
)
200 const WIN32_FIND_DATA
* fd1
= &entry1
->_data
;
201 const WIN32_FIND_DATA
* fd2
= &entry2
->_data
;
203 TYPE_ORDER order1
= fd1
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
? TO_DIR
: TO_FILE
;
204 TYPE_ORDER order2
= fd2
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
? TO_DIR
: TO_FILE
;
206 // Handle "." and ".." as special case and move them at the very first beginning.
207 if (order1
==TO_DIR
&& order2
==TO_DIR
) {
208 order1
= TypeOrderFromDirname(fd1
->cFileName
);
209 order2
= TypeOrderFromDirname(fd2
->cFileName
);
211 // Move virtual folders after physical folders
212 if (!(entry1
->_shell_attribs
& SFGAO_FILESYSTEM
))
213 order1
= TO_VIRTUAL_FOLDER
;
215 if (!(entry2
->_shell_attribs
& SFGAO_FILESYSTEM
))
216 order2
= TO_VIRTUAL_FOLDER
;
219 return order2
==order1
? 0: order1
<order2
? -1: 1;
223 static int compareNothing(const void* arg1
, const void* arg2
)
228 static int compareName(const void* arg1
, const void* arg2
)
230 const Entry
* entry1
= *(const Entry
**)arg1
;
231 const Entry
* entry2
= *(const Entry
**)arg2
;
233 int cmp
= compareType(entry1
, entry2
);
237 return lstrcmpi(entry1
->_data
.cFileName
, entry2
->_data
.cFileName
);
240 static int compareExt(const void* arg1
, const void* arg2
)
242 const Entry
* entry1
= *(const Entry
**)arg1
;
243 const Entry
* entry2
= *(const Entry
**)arg2
;
244 const TCHAR
*name1
, *name2
, *ext1
, *ext2
;
246 int cmp
= compareType(entry1
, entry2
);
250 name1
= entry1
->_data
.cFileName
;
251 name2
= entry2
->_data
.cFileName
;
253 ext1
= _tcsrchr(name1
, TEXT('.'));
254 ext2
= _tcsrchr(name2
, TEXT('.'));
266 cmp
= lstrcmpi(ext1
, ext2
);
270 return lstrcmpi(name1
, name2
);
273 static int compareSize(const void* arg1
, const void* arg2
)
275 const Entry
* entry1
= *(const Entry
**)arg1
;
276 const Entry
* entry2
= *(const Entry
**)arg2
;
278 int cmp
= compareType(entry1
, entry2
);
282 cmp
= entry2
->_data
.nFileSizeHigh
- entry1
->_data
.nFileSizeHigh
;
289 cmp
= entry2
->_data
.nFileSizeLow
- entry1
->_data
.nFileSizeLow
;
291 return cmp
<0? -1: cmp
>0? 1: 0;
294 static int compareDate(const void* arg1
, const void* arg2
)
296 const Entry
* entry1
= *(const Entry
**)arg1
;
297 const Entry
* entry2
= *(const Entry
**)arg2
;
299 int cmp
= compareType(entry1
, entry2
);
303 return CompareFileTime(&entry2
->_data
.ftLastWriteTime
, &entry1
->_data
.ftLastWriteTime
);
307 static int (*sortFunctions
[])(const void* arg1
, const void* arg2
) = {
308 compareNothing
, // SORT_NONE
309 compareName
, // SORT_NAME
310 compareExt
, // SORT_EXT
311 compareSize
, // SORT_SIZE
312 compareDate
// SORT_DATE
316 void Entry::sort_directory(SORT_ORDER sortOrder
)
318 if (sortOrder
!= SORT_NONE
) {
319 Entry
* entry
= _down
;
324 for(entry
=_down
; entry
; entry
=entry
->_next
)
328 array
= (Entry
**) alloca(len
*sizeof(Entry
*));
331 for(entry
=_down
; entry
; entry
=entry
->_next
)
334 // call qsort with the appropriate compare function
335 qsort(array
, len
, sizeof(array
[0]), sortFunctions
[sortOrder
]);
339 for(p
=array
; --len
; p
++)
348 void Entry::smart_scan(SORT_ORDER sortOrder
, int scan_flags
)
350 CONTEXT("Entry::smart_scan()");
354 read_directory_base(sortOrder
, scan_flags
); ///@todo We could use IShellFolder2::GetDefaultColumn to determine sort order.
360 int Entry::extract_icon(ICONCACHE_FLAGS flags
)
362 TCHAR path
[MAX_PATH
];
364 ICON_ID icon_id
= ICID_NONE
;
366 if (_etype
!=ET_SHELL
&& get_path(path
, COUNTOF(path
))) // not for ET_SHELL to display the correct desktop icon
367 if (!(flags
& ICF_MIDDLE
)) // not for ICF_MIDDLE to extract 24x24 icons because SHGetFileInfo() doesn't support this icon size
368 icon_id
= g_Globals
._icon_cache
.extract(path
, flags
);
370 if (icon_id
== ICID_NONE
) {
371 if (!(flags
& ICF_OVERLAYS
)) {
372 IExtractIcon
* pExtract
;
373 if (SUCCEEDED(GetUIObjectOf(0, IID_IExtractIcon
, (LPVOID
*)&pExtract
))) {
377 if (flags
& ICF_OPEN
)
378 gil_flags
|= GIL_OPENICON
;
380 if (SUCCEEDED(pExtract
->GetIconLocation(GIL_FORSHELL
, path
, COUNTOF(path
), &idx
, &gil_flags
))) {
381 if (gil_flags
& GIL_NOTFILENAME
)
382 icon_id
= g_Globals
._icon_cache
.extract(pExtract
, path
, idx
, flags
);
385 idx
= 0; // special case for some control panel applications ("System")
387 icon_id
= g_Globals
._icon_cache
.extract(path
, idx
, flags
);
390 /* using create_absolute_pidl() [see below] results in more correct icons for some control panel applets (NVidia display driver).
391 if (icon_id == ICID_NONE) {
394 if (SHGetFileInfo(path, 0, &sfi, sizeof(sfi), SHGFI_ICON|SHGFI_SMALLICON))
395 icon_id = g_Globals._icon_cache.add(sfi.hIcon)._id;
398 if (icon_id == ICID_NONE) {
399 LPBYTE b = (LPBYTE) alloca(0x10000);
402 FILE* file = fopen(path, "rb");
404 int l = fread(b, 1, 0x10000, file);
408 icon_id = g_Globals._icon_cache.add(CreateIconFromResourceEx(b, l, TRUE, 0x00030000, 16, 16, LR_DEFAULTCOLOR));
415 if (icon_id
== ICID_NONE
) {
418 const ShellPath
& pidl_abs
= create_absolute_pidl();
419 LPCITEMIDLIST pidl
= pidl_abs
;
421 int shgfi_flags
= SHGFI_PIDL
;
423 if (!(flags
& (ICF_LARGE
|ICF_MIDDLE
)))
424 shgfi_flags
|= SHGFI_SMALLICON
;
426 if (flags
& ICF_OPEN
)
427 shgfi_flags
|= SHGFI_OPENICON
;
429 if (flags
& ICF_SYSCACHE
) {
430 assert(!(flags
&ICF_OVERLAYS
));
432 HIMAGELIST himlSys
= (HIMAGELIST
) SHGetFileInfo((LPCTSTR
)pidl
, 0, &sfi
, sizeof(sfi
), SHGFI_SYSICONINDEX
|shgfi_flags
);
434 icon_id
= g_Globals
._icon_cache
.add(sfi
.iIcon
);
436 if (flags
& ICF_OVERLAYS
)
437 shgfi_flags
|= SHGFI_ADDOVERLAYS
;
439 if (SHGetFileInfo((LPCTSTR
)pidl
, 0, &sfi
, sizeof(sfi
), SHGFI_ICON
|shgfi_flags
))
440 icon_id
= g_Globals
._icon_cache
.add(sfi
.hIcon
);
448 int Entry::safe_extract_icon(ICONCACHE_FLAGS flags
)
451 return extract_icon(flags
);
452 } catch(COMException
&) {
453 // ignore unexpected exceptions while extracting icons
460 BOOL
Entry::launch_entry(HWND hwnd
, UINT nCmdShow
)
464 if (!get_path(cmd
, COUNTOF(cmd
)))
467 // add path to the recent file list
468 SHAddToRecentDocs(SHARD_PATH
, cmd
);
470 // start program, open document...
471 return launch_file(hwnd
, cmd
, nCmdShow
);
475 // local replacement implementation for SHBindToParent()
476 // (derived from http://www.geocities.com/SiliconValley/2060/articles/shell-helpers.html)
477 static HRESULT
my_SHBindToParent(LPCITEMIDLIST pidl
, REFIID riid
, VOID
** ppv
, LPCITEMIDLIST
* ppidlLast
)
484 // There must be at least one item ID.
485 if (!pidl
|| !pidl
->mkid
.cb
)
488 // Get the desktop folder as root.
490 /* IShellFolderPtr desktop;
491 hr = SHGetDesktopFolder(&desktop);
495 // Walk to the penultimate item ID.
496 LPCITEMIDLIST marker
= pidl
;
499 LPCITEMIDLIST next
= reinterpret_cast<LPCITEMIDLIST
>(
500 marker
->mkid
.abID
- sizeof(marker
->mkid
.cb
) + marker
->mkid
.cb
);
508 // There was only a single item ID, so bind to the root folder.
509 hr
= desktop
->QueryInterface(riid
, ppv
);
513 // Copy the ID list, truncating the last item.
514 int length
= marker
->mkid
.abID
- pidl
->mkid
.abID
;
515 if (LPITEMIDLIST parent_id
= reinterpret_cast<LPITEMIDLIST
>(
516 malloc(length
+ sizeof(pidl
->mkid
.cb
))))
518 LPBYTE raw_data
= reinterpret_cast<LPBYTE
>(parent_id
);
519 memcpy(raw_data
, pidl
, length
);
520 memset(raw_data
+ length
, 0, sizeof(pidl
->mkid
.cb
));
521 hr
= desktop
->BindToObject(parent_id
, 0, riid
, ppv
);
525 return E_OUTOFMEMORY
;
528 // Return a pointer to the last item ID.
534 #define USE_MY_SHBINDTOPARENT
536 HRESULT
Entry::do_context_menu(HWND hwnd
, const POINT
& pos
, CtxMenuInterfaces
& cm_ifs
)
538 ShellPath shell_path
= create_absolute_pidl();
539 LPCITEMIDLIST pidl_abs
= shell_path
;
542 return S_FALSE
; // no action for registry entries, etc.
544 #ifdef USE_MY_SHBINDTOPARENT
545 IShellFolder
* parentFolder
;
546 LPCITEMIDLIST pidlLast
;
548 // get and use the parent folder to display correct context menu in all cases -> correct "Properties" dialog for directories, ...
549 HRESULT hr
= my_SHBindToParent(pidl_abs
, IID_IShellFolder
, (LPVOID
*)&parentFolder
, &pidlLast
);
552 hr
= ShellFolderContextMenu(parentFolder
, hwnd
, 1, &pidlLast
, pos
.x
, pos
.y
, cm_ifs
);
554 parentFolder
->Release();
559 static DynamicFct
<HRESULT(WINAPI
*)(LPCITEMIDLIST
, REFIID
, LPVOID
*, LPCITEMIDLIST
*)> SHBindToParent(TEXT("SHELL32"), "SHBindToParent");
561 if (SHBindToParent
) {
562 IShellFolder
* parentFolder
;
563 LPCITEMIDLIST pidlLast
;
565 // get and use the parent folder to display correct context menu in all cases -> correct "Properties" dialog for directories, ...
566 HRESULT hr
= (*SHBindToParent
)(pidl_abs
, IID_IShellFolder
, (LPVOID
*)&parentFolder
, &pidlLast
);
569 hr
= ShellFolderContextMenu(parentFolder
, hwnd
, 1, &pidlLast
, pos
.x
, pos
.y
, cm_ifs
);
571 parentFolder
->Release();
576 /**@todo use parent folder instead of desktop folder
579 ShellPath parent_path;
582 parent_path = dir->create_absolute_pidl();
584 parent_path = DesktopFolderPath();
586 ShellPath shell_path = create_relative_pidl(parent_path);
587 LPCITEMIDLIST pidl = shell_path;
589 ShellFolder parent_folder = parent_path;
590 return ShellFolderContextMenu(parent_folder, hwnd, 1, &pidl, pos.x, pos.y);
592 return ShellFolderContextMenu(GetDesktopFolder(), hwnd
, 1, &pidl_abs
, pos
.x
, pos
.y
, cm_ifs
);
598 HRESULT
Entry::GetUIObjectOf(HWND hWnd
, REFIID riid
, LPVOID
* ppvOut
)
600 TCHAR path
[MAX_PATH
];
602 if (!get_path(path, COUNTOF(path)))
605 ShellPath shell_path(path);
607 IShellFolder* pFolder;
608 LPCITEMIDLIST pidl_last = NULL;
610 static DynamicFct<HRESULT(WINAPI*)(LPCITEMIDLIST, REFIID, LPVOID*, LPCITEMIDLIST*)> SHBindToParent(TEXT("SHELL32"), "SHBindToParent");
615 HRESULT hr = (*SHBindToParent)(shell_path, IID_IShellFolder, (LPVOID*)&pFolder, &pidl_last);
619 ShellFolder shell_folder(pFolder);
621 shell_folder->Release();
623 return shell_folder->GetUIObjectOf(hWnd, 1, &pidl_last, riid, NULL, ppvOut);
628 if (!_up
->get_path(path
, COUNTOF(path
)))
631 ShellPath
shell_path(path
);
632 ShellFolder
shell_folder(shell_path
);
635 LPWSTR wname
= _data
.cFileName
;
637 WCHAR wname
[MAX_PATH
];
638 MultiByteToWideChar(CP_ACP
, 0, _data
.cFileName
, -1, wname
, COUNTOF(wname
));
641 LPITEMIDLIST pidl_last
= NULL
;
642 HRESULT hr
= shell_folder
->ParseDisplayName(hWnd
, NULL
, wname
, NULL
, &pidl_last
, NULL
);
647 hr
= shell_folder
->GetUIObjectOf(hWnd
, 1, (LPCITEMIDLIST
*)&pidl_last
, riid
, NULL
, ppvOut
);
649 ShellMalloc()->Free((void*)pidl_last
);
655 // get full path of specified directory entry
656 bool Entry::get_path_base ( PTSTR path
, size_t path_count
, ENTRY_TYPE etype
) const
662 TCHAR buffer
[MAX_PATH
];
664 if (!path
|| path_count
==0)
668 if ( path_count
> 1 )
670 for(entry
=this; entry
; level
++) {
673 if (entry
->_etype
== etype
) {
674 name
= entry
->_data
.cFileName
;
676 for(LPCTSTR s
=name
; *s
&& *s
!=TEXT('/') && *s
!=TEXT('\\'); s
++)
682 if (entry
->get_path(buffer
, COUNTOF(buffer
))) {
686 /* special handling of drive names */
687 if (l
>0 && buffer
[l
-1]=='\\' && path
[0]=='\\')
690 if ( len
+l
>= path_count
)
692 if ( l
+ 1 > path_count
)
695 len
= path_count
- l
- 1;
697 memmove(path
+l
, path
, len
*sizeof(TCHAR
));
698 if ( l
+1 >= path_count
)
700 memcpy(path
, name
, l
*sizeof(TCHAR
));
709 if ( len
+l
+1 >= path_count
)
711 /* compare to 2 here because of terminator plus the '\\' we prepend */
712 if ( l
+ 2 > path_count
)
715 len
= path_count
- l
- 2;
717 memmove(path
+l
+1, path
, len
*sizeof(TCHAR
));
718 /* compare to 2 here because of terminator plus the '\\' we prepend */
719 if ( l
+2 >= path_count
)
721 memcpy(path
+1, name
, l
*sizeof(TCHAR
));
725 if (etype
== ET_WINDOWS
&& entry
->_up
&& !(entry
->_up
->_data
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) // a NTFS stream?
729 path
[0] = TEXT('\\');
736 if ( len
+l
>= path_count
)
738 if ( l
+ 1 > path_count
)
741 len
= path_count
- l
- 1;
743 memmove(path
+l
, path
, len
*sizeof(TCHAR
));
744 if ( l
+1 >= path_count
)
746 memcpy(path
, name
, l
*sizeof(TCHAR
));
750 if ( !level
&& (len
+1 < path_count
) )
751 path
[len
++] = TEXT('\\');
754 path
[len
] = TEXT('\0');
759 // recursively free all child entries
760 void Entry::free_subentries()
762 Entry
*entry
, *next
=_down
;
771 entry
->free_subentries();
778 Entry
* Root::read_tree(LPCTSTR path
, int scan_flags
)
783 entry
= _entry
->read_tree(path
, _sort_order
);
785 entry
= _entry
->read_tree(NULL
, _sort_order
);
787 _entry
->smart_scan();
790 _entry
->_expanded
= true;
797 Entry
* Root::read_tree(LPCITEMIDLIST pidl
, int scan_flags
)
799 return _entry
->read_tree(pidl
, _sort_order
);