icon caching
[reactos.git] / reactos / subsys / system / explorer / shell / entries.cpp
1 /*
2 * Copyright 2003 Martin Fuchs
3 *
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.
8 *
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.
13 *
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
17 */
18
19
20 //
21 // Explorer clone
22 //
23 // entries.cpp
24 //
25 // Martin Fuchs, 23.07.2003
26 //
27
28
29 #include "../utility/utility.h"
30 #include "../utility/shellclasses.h"
31 #include "../globals.h" // for _prescan_nodes
32
33 #include "entries.h"
34
35
36 // allocate and initialise a directory entry
37 Entry::Entry(ENTRY_TYPE etype)
38 : _etype(etype)
39 {
40 _up = NULL;
41 _next = NULL;
42 _down = NULL;
43 _expanded = false;
44 _scanned = false;
45 _bhfi_valid = false;
46 _level = 0;
47 _icon_id = ICID_UNKNOWN;
48 _display_name = _data.cFileName;
49 }
50
51 Entry::Entry(Entry* parent, ENTRY_TYPE etype)
52 : _up(parent),
53 _etype(etype)
54 {
55 _next = NULL;
56 _down = NULL;
57 _expanded = false;
58 _scanned = false;
59 _bhfi_valid = false;
60 _level = 0;
61 _icon_id = ICID_UNKNOWN;
62 _display_name = _data.cFileName;
63 }
64
65 Entry::Entry(const Entry& other)
66 {
67 _next = NULL;
68 _down = NULL;
69 _up = NULL;
70
71 assert(!other._next);
72 assert(!other._down);
73 assert(!other._up);
74
75 _expanded = other._expanded;
76 _scanned = other._scanned;
77 _level = other._level;
78
79 _data = other._data;
80
81 _shell_attribs = other._shell_attribs;
82 _display_name = other._display_name==other._data.cFileName? _data.cFileName: _tcsdup(other._display_name);
83
84 _etype = other._etype;
85 _icon_id = other._icon_id;
86
87 _bhfi = other._bhfi;
88 _bhfi_valid = other._bhfi_valid;
89 }
90
91 // free a directory entry
92 Entry::~Entry()
93 {
94 if (_icon_id > ICID_NONE)
95 g_Globals._icon_cache.free_icon(_icon_id);
96
97 if (_display_name != _data.cFileName)
98 free(_display_name);
99 }
100
101
102 // read directory tree and expand to the given location
103 Entry* Entry::read_tree(const void* path, SORT_ORDER sortOrder)
104 {
105 CONTEXT("Entry::read_tree()");
106
107 HCURSOR old_cursor = SetCursor(LoadCursor(0, IDC_WAIT));
108
109 Entry* entry = this;
110 Entry* next_entry = entry;
111
112 for(const void*p=path; p&&next_entry; p=entry->get_next_path_component(p)) {
113 entry = next_entry;
114
115 entry->read_directory(sortOrder);
116
117 if (entry->_down)
118 entry->_expanded = true;
119
120 next_entry = entry->find_entry(p);
121 }
122
123 SetCursor(old_cursor);
124
125 return entry;
126 }
127
128
129 void Entry::read_directory(SORT_ORDER sortOrder, int scan_flags)
130 {
131 CONTEXT("Entry::read_directory(SORT_ORDER)");
132
133 // call into subclass
134 read_directory(scan_flags);
135
136 if (g_Globals._prescan_nodes) { //@todo _prescan_nodes should not be used for reading the start menu.
137 for(Entry*entry=_down; entry; entry=entry->_next)
138 if (entry->_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
139 entry->read_directory(scan_flags);
140 entry->sort_directory(sortOrder);
141 }
142 }
143
144 sort_directory(sortOrder);
145 }
146
147
148 Root::Root()
149 {
150 memset(this, 0, sizeof(Root));
151 }
152
153 Root::~Root()
154 {
155 if (_entry)
156 _entry->free_subentries();
157 }
158
159
160 // directories first...
161 static int compareType(const WIN32_FIND_DATA* fd1, const WIN32_FIND_DATA* fd2)
162 {
163 int dir1 = fd1->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
164 int dir2 = fd2->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
165
166 return dir2==dir1? 0: dir2<dir1? -1: 1;
167 }
168
169
170 static int compareName(const void* arg1, const void* arg2)
171 {
172 const WIN32_FIND_DATA* fd1 = &(*(Entry**)arg1)->_data;
173 const WIN32_FIND_DATA* fd2 = &(*(Entry**)arg2)->_data;
174
175 int cmp = compareType(fd1, fd2);
176 if (cmp)
177 return cmp;
178
179 return lstrcmpi(fd1->cFileName, fd2->cFileName);
180 }
181
182 static int compareExt(const void* arg1, const void* arg2)
183 {
184 const WIN32_FIND_DATA* fd1 = &(*(Entry**)arg1)->_data;
185 const WIN32_FIND_DATA* fd2 = &(*(Entry**)arg2)->_data;
186 const TCHAR *name1, *name2, *ext1, *ext2;
187
188 int cmp = compareType(fd1, fd2);
189 if (cmp)
190 return cmp;
191
192 name1 = fd1->cFileName;
193 name2 = fd2->cFileName;
194
195 ext1 = _tcsrchr(name1, TEXT('.'));
196 ext2 = _tcsrchr(name2, TEXT('.'));
197
198 if (ext1)
199 ++ext1;
200 else
201 ext1 = TEXT("");
202
203 if (ext2)
204 ++ext2;
205 else
206 ext2 = TEXT("");
207
208 cmp = lstrcmpi(ext1, ext2);
209 if (cmp)
210 return cmp;
211
212 return lstrcmpi(name1, name2);
213 }
214
215 static int compareSize(const void* arg1, const void* arg2)
216 {
217 WIN32_FIND_DATA* fd1 = &(*(Entry**)arg1)->_data;
218 WIN32_FIND_DATA* fd2 = &(*(Entry**)arg2)->_data;
219
220 int cmp = compareType(fd1, fd2);
221 if (cmp)
222 return cmp;
223
224 cmp = fd2->nFileSizeHigh - fd1->nFileSizeHigh;
225
226 if (cmp < 0)
227 return -1;
228 else if (cmp > 0)
229 return 1;
230
231 cmp = fd2->nFileSizeLow - fd1->nFileSizeLow;
232
233 return cmp<0? -1: cmp>0? 1: 0;
234 }
235
236 static int compareDate(const void* arg1, const void* arg2)
237 {
238 WIN32_FIND_DATA* fd1 = &(*(Entry**)arg1)->_data;
239 WIN32_FIND_DATA* fd2 = &(*(Entry**)arg2)->_data;
240
241 int cmp = compareType(fd1, fd2);
242 if (cmp)
243 return cmp;
244
245 return CompareFileTime(&fd2->ftLastWriteTime, &fd1->ftLastWriteTime);
246 }
247
248
249 static int (*sortFunctions[])(const void* arg1, const void* arg2) = {
250 compareName, // SORT_NAME
251 compareExt, // SORT_EXT
252 compareSize, // SORT_SIZE
253 compareDate // SORT_DATE
254 };
255
256
257 void Entry::sort_directory(SORT_ORDER sortOrder)
258 {
259 Entry* entry = _down;
260 Entry** array, **p;
261 int len;
262
263 len = 0;
264 for(entry=_down; entry; entry=entry->_next)
265 ++len;
266
267 if (len) {
268 array = (Entry**) alloca(len*sizeof(Entry*));
269
270 p = array;
271 for(entry=_down; entry; entry=entry->_next)
272 *p++ = entry;
273
274 // call qsort with the appropriate compare function
275 qsort(array, len, sizeof(array[0]), sortFunctions[sortOrder]);
276
277 _down = array[0];
278
279 for(p=array; --len; p++)
280 p[0]->_next = p[1];
281
282 (*p)->_next = 0;
283 }
284 }
285
286
287 void Entry::smart_scan(int scan_flags)
288 {
289 CONTEXT("Entry::smart_scan()");
290
291 if (!_scanned) {
292 free_subentries();
293 read_directory(SORT_NAME, scan_flags); // we could use IShellFolder2::GetDefaultColumn to determine sort order
294 }
295 }
296
297
298 ShellPath Entry::create_absolute_pidl() const
299 {
300 CONTEXT("Entry::create_absolute_pidl()");
301
302 TCHAR path[MAX_PATH];
303
304 if (get_path(path))
305 return ShellPath(path);
306
307 return ShellPath();
308 }
309
310
311 void Entry::extract_icon()
312 {
313 ICON_ID icon_id = ICID_NONE;
314
315 IExtractIcon* pExtract;
316 if (SUCCEEDED(GetUIObjectOf(0, IID_IExtractIcon, (LPVOID*)&pExtract))) {
317 TCHAR path[MAX_PATH];
318 unsigned flags;
319 int idx;
320
321 if (SUCCEEDED(pExtract->GetIconLocation(GIL_FORSHELL, path, MAX_PATH, &idx, &flags))) {
322 if (flags & GIL_NOTFILENAME)
323 icon_id = g_Globals._icon_cache.extract(pExtract, path, idx)._id;
324 else {
325 if (idx == -1)
326 idx = 0; // special case for some control panel applications ("System")
327
328 icon_id = g_Globals._icon_cache.extract_from_file(path, idx)._id;
329 }
330
331 /* using create_absolute_pidl() [see below] results in more correct icons for some control panel applets ("NVidia").
332 if (icon_id == ICID_NONE) {
333 SHFILEINFO sfi;
334
335 if (SHGetFileInfo(path, 0, &sfi, sizeof(sfi), SHGFI_ICON|SHGFI_SMALLICON))
336 icon_id = g_Globals._icon_cache.add(sfi.hIcon)._id;
337 }
338 */
339 /*
340 if (icon_id == ICID_NONE) {
341 LPBYTE b = (LPBYTE) alloca(0x10000);
342 SHFILEINFO sfi;
343
344 FILE* file = fopen(path, "rb");
345 if (file) {
346 int l = fread(b, 1, 0x10000, file);
347 fclose(file);
348
349 if (l)
350 icon_id = g_Globals._icon_cache.add(CreateIconFromResourceEx(b, l, TRUE, 0x00030000, 16, 16, LR_DEFAULTCOLOR));
351 }
352 }
353 */ }
354 }
355
356 if (icon_id == ICID_NONE) {
357 SHFILEINFO sfi;
358
359 const ShellPath& pidl_abs = create_absolute_pidl();
360 LPCITEMIDLIST pidl = pidl_abs;
361
362 if (SHGetFileInfo((LPCTSTR)pidl, 0, &sfi, sizeof(sfi), SHGFI_PIDL|SHGFI_ICON|SHGFI_SMALLICON)) //@@ besser SHGFI_SYSICONINDEX ?
363 icon_id = g_Globals._icon_cache.add(sfi.hIcon)._id;
364 }
365
366 _icon_id = icon_id;
367 }
368
369
370 BOOL Entry::launch_entry(HWND hwnd, UINT nCmdShow)
371 {
372 TCHAR cmd[MAX_PATH];
373
374 if (!get_path(cmd))
375 return FALSE;
376
377 // start program, open document...
378 return launch_file(hwnd, cmd, nCmdShow);
379 }
380
381
382 HRESULT Entry::GetUIObjectOf(HWND hWnd, REFIID riid, LPVOID* ppvOut)
383 {
384 TCHAR path[MAX_PATH];
385 /*
386 if (!get_path(path))
387 return E_FAIL;
388
389 ShellPath shell_path(path);
390
391 IShellFolder* pFolder;
392 LPCITEMIDLIST pidl_last = NULL;
393
394 static DynamicFct<HRESULT(WINAPI*)(LPCITEMIDLIST, REFIID, LPVOID*, LPCITEMIDLIST*)> SHBindToParent(TEXT("SHELL32"), "SHBindToParent");
395
396 if (!SHBindToParent)
397 return E_NOTIMPL;
398
399 HRESULT hr = (*SHBindToParent)(shell_path, IID_IShellFolder, (LPVOID*)&pFolder, &pidl_last);
400 if (FAILED(hr))
401 return hr;
402
403 ShellFolder shell_folder(pFolder);
404
405 shell_folder->Release();
406
407 return shell_folder->GetUIObjectOf(hWnd, 1, &pidl_last, riid, NULL, ppvOut);
408 */
409 if (!_up)
410 return E_INVALIDARG;
411
412 if (!_up->get_path(path))
413 return E_FAIL;
414
415 ShellPath shell_path(path);
416 ShellFolder shell_folder(shell_path);
417
418 #ifdef UNICODE
419 LPWSTR wname = _data.cFileName;
420 #else
421 WCHAR wname[MAX_PATH];
422 MultiByteToWideChar(CP_ACP, 0, _data.cFileName, -1, wname, MAX_PATH);
423 #endif
424
425 LPITEMIDLIST pidl_last = NULL;
426 HRESULT hr = shell_folder->ParseDisplayName(hWnd, NULL, wname, NULL, &pidl_last, NULL);
427
428 if (FAILED(hr))
429 return hr;
430
431 hr = shell_folder->GetUIObjectOf(hWnd, 1, (LPCITEMIDLIST*)&pidl_last, riid, NULL, ppvOut);
432
433 ShellMalloc()->Free((void*)pidl_last);
434
435 return hr;
436 }
437
438
439 // recursively free all child entries
440 void Entry::free_subentries()
441 {
442 Entry *entry, *next=_down;
443
444 if (next) {
445 _down = 0;
446
447 do {
448 entry = next;
449 next = entry->_next;
450
451 entry->free_subentries();
452 delete entry;
453 } while(next);
454 }
455 }