b79ac1949ade7754872eac52e807f1fbd9660e8b
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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
101 if (_icon_id
> ICID_NONE
)
102 g_Globals
._icon_cache
.free_icon(_icon_id
);
104 if (_display_name
!= _data
.cFileName
)
118 // read directory tree and expand to the given location
119 Entry
* Entry::read_tree(const void* path
, SORT_ORDER sortOrder
, int scan_flags
)
121 CONTEXT("Entry::read_tree()");
127 for(const void*p
=path
; p
&& entry
; ) {
128 entry
->smart_scan(sortOrder
, scan_flags
);
131 entry
->_expanded
= true;
133 Entry
* found
= entry
->find_entry(p
);
134 p
= entry
->get_next_path_component(p
);
143 void Entry::read_directory_base(SORT_ORDER sortOrder
, int scan_flags
)
145 CONTEXT("Entry::read_directory_base()");
147 // call into subclass
148 read_directory(scan_flags
);
151 if (g_Globals
._prescan_nodes
) { ///@todo _prescan_nodes should not be used for reading the start menu.
152 for(Entry
*entry
=_down
; entry
; entry
=entry
->_next
)
153 if (entry
->_data
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
154 entry
->read_directory(scan_flags
);
155 entry
->sort_directory(sortOrder
);
160 sort_directory(sortOrder
);
166 memset(this, 0, sizeof(Root
));
172 _entry
->free_subentries();
178 // sort order for different directory/file types
188 // distinguish between ".", ".." and any other directory names
189 static TYPE_ORDER
TypeOrderFromDirname(LPCTSTR name
)
191 if (name
[0] == '.') {
193 return TO_DOT
; // "."
195 if (name
[1]=='.' && name
[2]=='\0')
196 return TO_DOTDOT
; // ".."
199 return TO_OTHER_DIR
; // any other directory
202 // directories first...
203 static int compareType(const Entry
* entry1
, const Entry
* entry2
)
205 const WIN32_FIND_DATA
* fd1
= &entry1
->_data
;
206 const WIN32_FIND_DATA
* fd2
= &entry2
->_data
;
208 TYPE_ORDER order1
= fd1
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
? TO_DIR
: TO_FILE
;
209 TYPE_ORDER order2
= fd2
->dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
? TO_DIR
: TO_FILE
;
211 // Handle "." and ".." as special case and move them at the very first beginning.
212 if (order1
==TO_DIR
&& order2
==TO_DIR
) {
213 order1
= TypeOrderFromDirname(fd1
->cFileName
);
214 order2
= TypeOrderFromDirname(fd2
->cFileName
);
216 // Move virtual folders after physical folders
217 if (!(entry1
->_shell_attribs
& SFGAO_FILESYSTEM
))
218 order1
= TO_VIRTUAL_FOLDER
;
220 if (!(entry2
->_shell_attribs
& SFGAO_FILESYSTEM
))
221 order2
= TO_VIRTUAL_FOLDER
;
224 return order2
==order1
? 0: order1
<order2
? -1: 1;
228 static int compareNothing(const void* arg1
, const void* arg2
)
233 static int compareName(const void* arg1
, const void* arg2
)
235 const Entry
* entry1
= *(const Entry
**)arg1
;
236 const Entry
* entry2
= *(const Entry
**)arg2
;
238 int cmp
= compareType(entry1
, entry2
);
242 return lstrcmpi(entry1
->_data
.cFileName
, entry2
->_data
.cFileName
);
245 static int compareExt(const void* arg1
, const void* arg2
)
247 const Entry
* entry1
= *(const Entry
**)arg1
;
248 const Entry
* entry2
= *(const Entry
**)arg2
;
249 const TCHAR
*name1
, *name2
, *ext1
, *ext2
;
251 int cmp
= compareType(entry1
, entry2
);
255 name1
= entry1
->_data
.cFileName
;
256 name2
= entry2
->_data
.cFileName
;
258 ext1
= _tcsrchr(name1
, TEXT('.'));
259 ext2
= _tcsrchr(name2
, TEXT('.'));
271 cmp
= lstrcmpi(ext1
, ext2
);
275 return lstrcmpi(name1
, name2
);
278 static int compareSize(const void* arg1
, const void* arg2
)
280 const Entry
* entry1
= *(const Entry
**)arg1
;
281 const Entry
* entry2
= *(const Entry
**)arg2
;
283 int cmp
= compareType(entry1
, entry2
);
287 cmp
= entry2
->_data
.nFileSizeHigh
- entry1
->_data
.nFileSizeHigh
;
294 cmp
= entry2
->_data
.nFileSizeLow
- entry1
->_data
.nFileSizeLow
;
296 return cmp
<0? -1: cmp
>0? 1: 0;
299 static int compareDate(const void* arg1
, const void* arg2
)
301 const Entry
* entry1
= *(const Entry
**)arg1
;
302 const Entry
* entry2
= *(const Entry
**)arg2
;
304 int cmp
= compareType(entry1
, entry2
);
308 return CompareFileTime(&entry2
->_data
.ftLastWriteTime
, &entry1
->_data
.ftLastWriteTime
);
312 static int (*sortFunctions
[])(const void* arg1
, const void* arg2
) = {
313 compareNothing
, // SORT_NONE
314 compareName
, // SORT_NAME
315 compareExt
, // SORT_EXT
316 compareSize
, // SORT_SIZE
317 compareDate
// SORT_DATE
321 void Entry::sort_directory(SORT_ORDER sortOrder
)
323 if (sortOrder
!= SORT_NONE
) {
324 Entry
* entry
= _down
;
329 for(entry
=_down
; entry
; entry
=entry
->_next
)
333 array
= (Entry
**) alloca(len
*sizeof(Entry
*));
336 for(entry
=_down
; entry
; entry
=entry
->_next
)
339 // call qsort with the appropriate compare function
340 qsort(array
, len
, sizeof(array
[0]), sortFunctions
[sortOrder
]);
344 for(p
=array
; --len
; p
++)
353 void Entry::smart_scan(SORT_ORDER sortOrder
, int scan_flags
)
355 CONTEXT("Entry::smart_scan()");
359 read_directory_base(sortOrder
, scan_flags
); ///@todo We could use IShellFolder2::GetDefaultColumn to determine sort order.
365 int Entry::extract_icon(ICONCACHE_FLAGS flags
)
367 TCHAR path
[MAX_PATH
];
369 ICON_ID icon_id
= ICID_NONE
;
371 if (_etype
!=ET_SHELL
&& get_path(path
, COUNTOF(path
))) // not for ET_SHELL to display the correct desktop icon
372 if (!(flags
& ICF_MIDDLE
)) // not for ICF_MIDDLE to extract 24x24 icons because SHGetFileInfo() doesn't support this icon size
373 icon_id
= g_Globals
._icon_cache
.extract(path
, flags
);
375 if (icon_id
== ICID_NONE
) {
376 if (!(flags
& ICF_OVERLAYS
)) {
377 IExtractIcon
* pExtract
;
378 if (SUCCEEDED(GetUIObjectOf(0, IID_IExtractIcon
, (LPVOID
*)&pExtract
))) {
379 unsigned gil_flags
= 0;
382 if (flags
& ICF_OPEN
)
383 gil_flags
|= GIL_OPENICON
;
385 if (SUCCEEDED(pExtract
->GetIconLocation(GIL_FORSHELL
, path
, COUNTOF(path
), &idx
, &gil_flags
))) {
386 if (gil_flags
& GIL_NOTFILENAME
)
387 icon_id
= g_Globals
._icon_cache
.extract(pExtract
, path
, idx
, flags
);
390 idx
= 0; // special case for some control panel applications ("System")
392 icon_id
= g_Globals
._icon_cache
.extract(path
, idx
, flags
);
395 /* using create_absolute_pidl() [see below] results in more correct icons for some control panel applets (NVidia display driver).
396 if (icon_id == ICID_NONE) {
399 if (SHGetFileInfo(path, 0, &sfi, sizeof(sfi), SHGFI_ICON|SHGFI_SMALLICON))
400 icon_id = g_Globals._icon_cache.add(sfi.hIcon)._id;
403 if (icon_id == ICID_NONE) {
404 LPBYTE b = (LPBYTE) alloca(0x10000);
407 FILE* file = fopen(path, "rb");
409 int l = fread(b, 1, 0x10000, file);
413 icon_id = g_Globals._icon_cache.add(CreateIconFromResourceEx(b, l, TRUE, 0x00030000, 16, 16, LR_DEFAULTCOLOR));
420 if (icon_id
== ICID_NONE
) {
421 const ShellPath
& pidl_abs
= create_absolute_pidl();
422 LPCITEMIDLIST pidl
= pidl_abs
;
424 icon_id
= g_Globals
._icon_cache
.extract(pidl
, flags
);
431 int Entry::safe_extract_icon(ICONCACHE_FLAGS flags
)
434 return extract_icon(flags
);
435 } catch(COMException
&) {
436 // ignore unexpected exceptions while extracting icons
443 BOOL
Entry::launch_entry(HWND hwnd
, UINT nCmdShow
)
447 if (!get_path(cmd
, COUNTOF(cmd
)))
450 // add path to the recent file list
451 SHAddToRecentDocs(SHARD_PATH
, cmd
);
453 // start program, open document...
454 return launch_file(hwnd
, cmd
, nCmdShow
);
458 // local replacement implementation for SHBindToParent()
459 // (derived from http://www.geocities.com/SiliconValley/2060/articles/shell-helpers.html)
460 static HRESULT
my_SHBindToParent(LPCITEMIDLIST pidl
, REFIID riid
, VOID
** ppv
, LPCITEMIDLIST
* ppidlLast
)
467 // There must be at least one item ID.
468 if (!pidl
|| !pidl
->mkid
.cb
)
471 // Get the desktop folder as root.
473 /* IShellFolderPtr desktop;
474 hr = SHGetDesktopFolder(&desktop);
478 // Walk to the penultimate item ID.
479 LPCITEMIDLIST marker
= pidl
;
482 LPCITEMIDLIST next
= reinterpret_cast<LPCITEMIDLIST
>(
483 marker
->mkid
.abID
- sizeof(marker
->mkid
.cb
) + marker
->mkid
.cb
);
491 // There was only a single item ID, so bind to the root folder.
492 hr
= desktop
->QueryInterface(riid
, ppv
);
496 // Copy the ID list, truncating the last item.
497 int length
= marker
->mkid
.abID
- pidl
->mkid
.abID
;
498 if (LPITEMIDLIST parent_id
= reinterpret_cast<LPITEMIDLIST
>(
499 malloc(length
+ sizeof(pidl
->mkid
.cb
))))
501 LPBYTE raw_data
= reinterpret_cast<LPBYTE
>(parent_id
);
502 memcpy(raw_data
, pidl
, length
);
503 memset(raw_data
+ length
, 0, sizeof(pidl
->mkid
.cb
));
504 hr
= desktop
->BindToObject(parent_id
, 0, riid
, ppv
);
508 return E_OUTOFMEMORY
;
511 // Return a pointer to the last item ID.
517 #define USE_MY_SHBINDTOPARENT
519 HRESULT
Entry::do_context_menu(HWND hwnd
, const POINT
& pos
, CtxMenuInterfaces
& cm_ifs
)
521 ShellPath shell_path
= create_absolute_pidl();
522 LPCITEMIDLIST pidl_abs
= shell_path
;
525 return S_FALSE
; // no action for registry entries, etc.
527 #ifdef USE_MY_SHBINDTOPARENT
528 IShellFolder
* parentFolder
;
529 LPCITEMIDLIST pidlLast
;
531 // get and use the parent folder to display correct context menu in all cases -> correct "Properties" dialog for directories, ...
532 HRESULT hr
= my_SHBindToParent(pidl_abs
, IID_IShellFolder
, (LPVOID
*)&parentFolder
, &pidlLast
);
535 hr
= ShellFolderContextMenu(parentFolder
, hwnd
, 1, &pidlLast
, pos
.x
, pos
.y
, cm_ifs
);
537 parentFolder
->Release();
542 static DynamicFct
<HRESULT(WINAPI
*)(LPCITEMIDLIST
, REFIID
, LPVOID
*, LPCITEMIDLIST
*)> SHBindToParent(TEXT("SHELL32"), "SHBindToParent");
544 if (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
= (*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 /**@todo use parent folder instead of desktop folder
562 ShellPath parent_path;
565 parent_path = dir->create_absolute_pidl();
567 parent_path = DesktopFolderPath();
569 ShellPath shell_path = create_relative_pidl(parent_path);
570 LPCITEMIDLIST pidl = shell_path;
572 ShellFolder parent_folder = parent_path;
573 return ShellFolderContextMenu(parent_folder, hwnd, 1, &pidl, pos.x, pos.y);
575 return ShellFolderContextMenu(GetDesktopFolder(), hwnd
, 1, &pidl_abs
, pos
.x
, pos
.y
, cm_ifs
);
581 HRESULT
Entry::GetUIObjectOf(HWND hWnd
, REFIID riid
, LPVOID
* ppvOut
)
583 TCHAR path
[MAX_PATH
];
585 if (!get_path(path, COUNTOF(path)))
588 ShellPath shell_path(path);
590 IShellFolder* pFolder;
591 LPCITEMIDLIST pidl_last = NULL;
593 static DynamicFct<HRESULT(WINAPI*)(LPCITEMIDLIST, REFIID, LPVOID*, LPCITEMIDLIST*)> SHBindToParent(TEXT("SHELL32"), "SHBindToParent");
598 HRESULT hr = (*SHBindToParent)(shell_path, IID_IShellFolder, (LPVOID*)&pFolder, &pidl_last);
602 ShellFolder shell_folder(pFolder);
604 shell_folder->Release();
606 return shell_folder->GetUIObjectOf(hWnd, 1, &pidl_last, riid, NULL, ppvOut);
611 if (!_up
->get_path(path
, COUNTOF(path
)))
614 ShellPath
shell_path(path
);
615 ShellFolder
shell_folder(shell_path
);
618 LPWSTR wname
= _data
.cFileName
;
620 WCHAR wname
[MAX_PATH
];
621 MultiByteToWideChar(CP_ACP
, 0, _data
.cFileName
, -1, wname
, COUNTOF(wname
));
624 LPITEMIDLIST pidl_last
= NULL
;
625 HRESULT hr
= shell_folder
->ParseDisplayName(hWnd
, NULL
, wname
, NULL
, &pidl_last
, NULL
);
630 hr
= shell_folder
->GetUIObjectOf(hWnd
, 1, (LPCITEMIDLIST
*)&pidl_last
, riid
, NULL
, ppvOut
);
632 ShellMalloc()->Free((void*)pidl_last
);
638 // get full path of specified directory entry
639 bool Entry::get_path_base ( PTSTR path
, size_t path_count
, ENTRY_TYPE etype
) const
645 TCHAR buffer
[MAX_PATH
];
647 if (!path
|| path_count
==0)
651 if ( path_count
> 1 )
653 for(entry
=this; entry
; level
++) {
656 if (entry
->_etype
== etype
) {
657 name
= entry
->_data
.cFileName
;
659 for(LPCTSTR s
=name
; *s
&& *s
!=TEXT('/') && *s
!=TEXT('\\'); s
++)
665 if (entry
->get_path(buffer
, COUNTOF(buffer
))) {
669 /* special handling of drive names */
670 if (l
>0 && buffer
[l
-1]=='\\' && path
[0]=='\\')
673 if ( len
+l
>= path_count
)
675 if ( l
+ 1 > path_count
)
678 len
= path_count
- l
- 1;
680 memmove(path
+l
, path
, len
*sizeof(TCHAR
));
681 if ( l
+1 >= path_count
)
683 memcpy(path
, name
, l
*sizeof(TCHAR
));
692 if ( len
+l
+1 >= path_count
)
694 /* compare to 2 here because of terminator plus the '\\' we prepend */
695 if ( l
+ 2 > path_count
)
698 len
= path_count
- l
- 2;
700 memmove(path
+l
+1, path
, len
*sizeof(TCHAR
));
701 /* compare to 2 here because of terminator plus the '\\' we prepend */
702 if ( l
+2 >= path_count
)
704 memcpy(path
+1, name
, l
*sizeof(TCHAR
));
708 if (etype
== ET_WINDOWS
&& entry
->_up
&& !(entry
->_up
->_data
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) // a NTFS stream?
712 path
[0] = TEXT('\\');
719 if ( len
+l
>= path_count
)
721 if ( l
+ 1 > path_count
)
724 len
= path_count
- l
- 1;
726 memmove(path
+l
, path
, len
*sizeof(TCHAR
));
727 if ( l
+1 >= path_count
)
729 memcpy(path
, name
, l
*sizeof(TCHAR
));
733 if ( !level
&& (len
+1 < path_count
) )
734 path
[len
++] = TEXT('\\');
737 path
[len
] = TEXT('\0');
742 // recursively free all child entries
743 void Entry::free_subentries()
745 Entry
*entry
, *next
=_down
;
754 entry
->free_subentries();
761 Entry
* Root::read_tree(LPCTSTR path
, int scan_flags
)
766 entry
= _entry
->read_tree(path
, _sort_order
);
768 entry
= _entry
->read_tree(NULL
, _sort_order
);
770 _entry
->smart_scan();
773 _entry
->_expanded
= true;
780 Entry
* Root::read_tree(LPCITEMIDLIST pidl
, int scan_flags
)
782 return _entry
->read_tree(pidl
, _sort_order
);