merge ROS Shell without integrated explorer part into trunk
[reactos.git] / reactos / subsys / system / explorer / shell / entries.cpp
1 /*
2 * Copyright 2003, 2004 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 "precomp.h"
30
31 //#include "entries.h"
32
33
34 // allocate and initialise a directory entry
35 Entry::Entry(ENTRY_TYPE etype)
36 : _etype(etype)
37 {
38 _up = NULL;
39 _next = NULL;
40 _down = NULL;
41 _expanded = false;
42 _scanned = false;
43 _bhfi_valid = false;
44 _level = 0;
45 _icon_id = ICID_UNKNOWN;
46 _display_name = _data.cFileName;
47 _type_name = NULL;
48 _content = NULL;
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 _type_name = NULL;
64 _content = NULL;
65 }
66
67 Entry::Entry(const Entry& other)
68 {
69 _next = NULL;
70 _down = NULL;
71 _up = NULL;
72
73 assert(!other._next);
74 assert(!other._down);
75 assert(!other._up);
76
77 _expanded = other._expanded;
78 _scanned = other._scanned;
79 _level = other._level;
80
81 _data = other._data;
82
83 _shell_attribs = other._shell_attribs;
84 _display_name = other._display_name==other._data.cFileName? _data.cFileName: _tcsdup(other._display_name);
85 _type_name = other._type_name? _tcsdup(other._type_name): NULL;
86 _content = other._content? _tcsdup(other._content): NULL;
87
88 _etype = other._etype;
89 _icon_id = other._icon_id;
90
91 _bhfi = other._bhfi;
92 _bhfi_valid = other._bhfi_valid;
93 }
94
95 // free a directory entry
96 Entry::~Entry()
97 {
98 if (_icon_id > ICID_NONE)
99 g_Globals._icon_cache.free_icon(_icon_id);
100
101 if (_display_name != _data.cFileName)
102 free(_display_name);
103
104 if (_type_name)
105 free(_type_name);
106
107 if (_content)
108 free(_content);
109 }
110
111
112 // read directory tree and expand to the given location
113 Entry* Entry::read_tree(const void* path, SORT_ORDER sortOrder, int scan_flags)
114 {
115 CONTEXT("Entry::read_tree()");
116
117 WaitCursor wait;
118
119 Entry* entry = this;
120
121 for(const void*p=path; p && entry; ) {
122 entry->smart_scan(sortOrder, scan_flags);
123
124 if (entry->_down)
125 entry->_expanded = true;
126
127 Entry* found = entry->find_entry(p);
128 p = entry->get_next_path_component(p);
129
130 entry = found;
131 }
132
133 return entry;
134 }
135
136
137 void Entry::read_directory_base(SORT_ORDER sortOrder, int scan_flags)
138 {
139 CONTEXT("Entry::read_directory_base()");
140
141 // call into subclass
142 read_directory(scan_flags);
143
144 #ifndef ROSSHELL
145 if (g_Globals._prescan_nodes) { //@todo _prescan_nodes should not be used for reading the start menu.
146 for(Entry*entry=_down; entry; entry=entry->_next)
147 if (entry->_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
148 entry->read_directory(scan_flags);
149 entry->sort_directory(sortOrder);
150 }
151 }
152 #endif
153
154 sort_directory(sortOrder);
155 }
156
157
158 Root::Root()
159 {
160 memset(this, 0, sizeof(Root));
161 }
162
163 Root::~Root()
164 {
165 if (_entry)
166 _entry->free_subentries();
167 }
168
169
170 // directories first...
171 static int compareType(const WIN32_FIND_DATA* fd1, const WIN32_FIND_DATA* fd2)
172 {
173 int order1 = fd1->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
174 int order2 = fd2->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
175
176 /* Handle "." and ".." as special case and move them at the very first beginning. */
177 if (order1 && order2) {
178 order1 = fd1->cFileName[0]!='.'? 1: fd1->cFileName[1]=='.'? 2: fd1->cFileName[1]=='\0'? 3: 1;
179 order2 = fd2->cFileName[0]!='.'? 1: fd2->cFileName[1]=='.'? 2: fd2->cFileName[1]=='\0'? 3: 1;
180 }
181
182 return order2==order1? 0: order2<order1? -1: 1;
183 }
184
185
186 static int compareNothing(const void* arg1, const void* arg2)
187 {
188 return -1;
189 }
190
191 static int compareName(const void* arg1, const void* arg2)
192 {
193 const WIN32_FIND_DATA* fd1 = &(*(Entry**)arg1)->_data;
194 const WIN32_FIND_DATA* fd2 = &(*(Entry**)arg2)->_data;
195
196 int cmp = compareType(fd1, fd2);
197 if (cmp)
198 return cmp;
199
200 return lstrcmpi(fd1->cFileName, fd2->cFileName);
201 }
202
203 static int compareExt(const void* arg1, const void* arg2)
204 {
205 const WIN32_FIND_DATA* fd1 = &(*(Entry**)arg1)->_data;
206 const WIN32_FIND_DATA* fd2 = &(*(Entry**)arg2)->_data;
207 const TCHAR *name1, *name2, *ext1, *ext2;
208
209 int cmp = compareType(fd1, fd2);
210 if (cmp)
211 return cmp;
212
213 name1 = fd1->cFileName;
214 name2 = fd2->cFileName;
215
216 ext1 = _tcsrchr(name1, TEXT('.'));
217 ext2 = _tcsrchr(name2, TEXT('.'));
218
219 if (ext1)
220 ++ext1;
221 else
222 ext1 = TEXT("");
223
224 if (ext2)
225 ++ext2;
226 else
227 ext2 = TEXT("");
228
229 cmp = lstrcmpi(ext1, ext2);
230 if (cmp)
231 return cmp;
232
233 return lstrcmpi(name1, name2);
234 }
235
236 static int compareSize(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 cmp = fd2->nFileSizeHigh - fd1->nFileSizeHigh;
246
247 if (cmp < 0)
248 return -1;
249 else if (cmp > 0)
250 return 1;
251
252 cmp = fd2->nFileSizeLow - fd1->nFileSizeLow;
253
254 return cmp<0? -1: cmp>0? 1: 0;
255 }
256
257 static int compareDate(const void* arg1, const void* arg2)
258 {
259 WIN32_FIND_DATA* fd1 = &(*(Entry**)arg1)->_data;
260 WIN32_FIND_DATA* fd2 = &(*(Entry**)arg2)->_data;
261
262 int cmp = compareType(fd1, fd2);
263 if (cmp)
264 return cmp;
265
266 return CompareFileTime(&fd2->ftLastWriteTime, &fd1->ftLastWriteTime);
267 }
268
269
270 static int (*sortFunctions[])(const void* arg1, const void* arg2) = {
271 compareNothing, // SORT_NONE
272 compareName, // SORT_NAME
273 compareExt, // SORT_EXT
274 compareSize, // SORT_SIZE
275 compareDate // SORT_DATE
276 };
277
278
279 void Entry::sort_directory(SORT_ORDER sortOrder)
280 {
281 if (sortOrder != SORT_NONE) {
282 Entry* entry = _down;
283 Entry** array, **p;
284 int len;
285
286 len = 0;
287 for(entry=_down; entry; entry=entry->_next)
288 ++len;
289
290 if (len) {
291 array = (Entry**) alloca(len*sizeof(Entry*));
292
293 p = array;
294 for(entry=_down; entry; entry=entry->_next)
295 *p++ = entry;
296
297 // call qsort with the appropriate compare function
298 qsort(array, len, sizeof(array[0]), sortFunctions[sortOrder]);
299
300 _down = array[0];
301
302 for(p=array; --len; p++)
303 (*p)->_next = p[1];
304
305 (*p)->_next = 0;
306 }
307 }
308 }
309
310
311 void Entry::smart_scan(SORT_ORDER sortOrder, int scan_flags)
312 {
313 CONTEXT("Entry::smart_scan()");
314
315 if (!_scanned) {
316 free_subentries();
317 read_directory_base(sortOrder, scan_flags); ///@todo We could use IShellFolder2::GetDefaultColumn to determine sort order.
318 }
319 }
320
321
322 void Entry::extract_icon()
323 {
324 TCHAR path[MAX_PATH];
325
326 ICON_ID icon_id = ICID_NONE;
327
328 if (get_path(path) && _tcsncmp(path,TEXT("::{"),3))
329 icon_id = g_Globals._icon_cache.extract(path);
330
331 if (icon_id == ICID_NONE) {
332 IExtractIcon* pExtract;
333 if (SUCCEEDED(GetUIObjectOf(0, IID_IExtractIcon, (LPVOID*)&pExtract))) {
334 unsigned flags;
335 int idx;
336
337 if (SUCCEEDED(pExtract->GetIconLocation(GIL_FORSHELL, path, MAX_PATH, &idx, &flags))) {
338 if (flags & GIL_NOTFILENAME)
339 icon_id = g_Globals._icon_cache.extract(pExtract, path, idx);
340 else {
341 if (idx == -1)
342 idx = 0; // special case for some control panel applications ("System")
343
344 icon_id = g_Globals._icon_cache.extract(path, idx);
345 }
346
347 /* using create_absolute_pidl() [see below] results in more correct icons for some control panel applets ("NVidia").
348 if (icon_id == ICID_NONE) {
349 SHFILEINFO sfi;
350
351 if (SHGetFileInfo(path, 0, &sfi, sizeof(sfi), SHGFI_ICON|SHGFI_SMALLICON))
352 icon_id = g_Globals._icon_cache.add(sfi.hIcon)._id;
353 } */
354 /*
355 if (icon_id == ICID_NONE) {
356 LPBYTE b = (LPBYTE) alloca(0x10000);
357 SHFILEINFO sfi;
358
359 FILE* file = fopen(path, "rb");
360 if (file) {
361 int l = fread(b, 1, 0x10000, file);
362 fclose(file);
363
364 if (l)
365 icon_id = g_Globals._icon_cache.add(CreateIconFromResourceEx(b, l, TRUE, 0x00030000, 16, 16, LR_DEFAULTCOLOR));
366 }
367 } */
368 }
369 }
370
371 if (icon_id == ICID_NONE) {
372 SHFILEINFO sfi;
373
374 const ShellPath& pidl_abs = create_absolute_pidl();
375 LPCITEMIDLIST pidl = pidl_abs;
376
377 HIMAGELIST himlSys = (HIMAGELIST) SHGetFileInfo((LPCTSTR)pidl, 0, &sfi, sizeof(sfi), SHGFI_SYSICONINDEX|SHGFI_PIDL|SHGFI_SMALLICON);
378 if (himlSys)
379 icon_id = g_Globals._icon_cache.add(sfi.iIcon);
380 /*
381 if (SHGetFileInfo((LPCTSTR)pidl, 0, &sfi, sizeof(sfi), SHGFI_PIDL|SHGFI_ICON|SHGFI_SMALLICON))
382 icon_id = g_Globals._icon_cache.add(sfi.hIcon)._id;
383 */
384 }
385 }
386
387 _icon_id = icon_id;
388 }
389
390
391 BOOL Entry::launch_entry(HWND hwnd, UINT nCmdShow)
392 {
393 TCHAR cmd[MAX_PATH];
394
395 if (!get_path(cmd))
396 return FALSE;
397
398 // add path to the recent file list
399 SHAddToRecentDocs(SHARD_PATH, cmd);
400
401 // start program, open document...
402 return launch_file(hwnd, cmd, nCmdShow);
403 }
404
405
406 HRESULT Entry::do_context_menu(HWND hwnd, const POINT& pos, CtxMenuInterfaces& cm_ifs)
407 {
408 ShellPath shell_path = create_absolute_pidl();
409 LPCITEMIDLIST pidl_abs = shell_path;
410
411 if (!pidl_abs)
412 return S_FALSE; // no action for registry entries, etc.
413
414 static DynamicFct<HRESULT(WINAPI*)(LPCITEMIDLIST, REFIID, LPVOID*, LPCITEMIDLIST*)> SHBindToParent(TEXT("SHELL32"), "SHBindToParent");
415
416 if (SHBindToParent) {
417 IShellFolder* parentFolder;
418 LPCITEMIDLIST pidlLast;
419
420 // get and use the parent folder to display correct context menu in all cases -> correct "Properties" dialog for directories, ...
421 HRESULT hr = (*SHBindToParent)(pidl_abs, IID_IShellFolder, (LPVOID*)&parentFolder, &pidlLast);
422
423 if (SUCCEEDED(hr)) {
424 hr = ShellFolderContextMenu(parentFolder, hwnd, 1, &pidlLast, pos.x, pos.y, cm_ifs);
425
426 parentFolder->Release();
427 }
428
429 return hr;
430 } else {
431 /**@todo use parent folder instead of desktop folder
432 Entry* dir = _up;
433
434 ShellPath parent_path;
435
436 if (dir)
437 parent_path = dir->create_absolute_pidl();
438 else
439 parent_path = DesktopFolderPath();
440
441 ShellPath shell_path = create_relative_pidl(parent_path);
442 LPCITEMIDLIST pidl = shell_path;
443
444 ShellFolder parent_folder = parent_path;
445 return ShellFolderContextMenu(parent_folder, hwnd, 1, &pidl, pos.x, pos.y);
446 */
447 return ShellFolderContextMenu(GetDesktopFolder(), hwnd, 1, &pidl_abs, pos.x, pos.y, cm_ifs);
448 }
449 }
450
451
452 HRESULT Entry::GetUIObjectOf(HWND hWnd, REFIID riid, LPVOID* ppvOut)
453 {
454 TCHAR path[MAX_PATH];
455 /*
456 if (!get_path(path))
457 return E_FAIL;
458
459 ShellPath shell_path(path);
460
461 IShellFolder* pFolder;
462 LPCITEMIDLIST pidl_last = NULL;
463
464 static DynamicFct<HRESULT(WINAPI*)(LPCITEMIDLIST, REFIID, LPVOID*, LPCITEMIDLIST*)> SHBindToParent(TEXT("SHELL32"), "SHBindToParent");
465
466 if (!SHBindToParent)
467 return E_NOTIMPL;
468
469 HRESULT hr = (*SHBindToParent)(shell_path, IID_IShellFolder, (LPVOID*)&pFolder, &pidl_last);
470 if (FAILED(hr))
471 return hr;
472
473 ShellFolder shell_folder(pFolder);
474
475 shell_folder->Release();
476
477 return shell_folder->GetUIObjectOf(hWnd, 1, &pidl_last, riid, NULL, ppvOut);
478 */
479 if (!_up)
480 return E_INVALIDARG;
481
482 if (!_up->get_path(path))
483 return E_FAIL;
484
485 ShellPath shell_path(path);
486 ShellFolder shell_folder(shell_path);
487
488 #ifdef UNICODE
489 LPWSTR wname = _data.cFileName;
490 #else
491 WCHAR wname[MAX_PATH];
492 MultiByteToWideChar(CP_ACP, 0, _data.cFileName, -1, wname, MAX_PATH);
493 #endif
494
495 LPITEMIDLIST pidl_last = NULL;
496 HRESULT hr = shell_folder->ParseDisplayName(hWnd, NULL, wname, NULL, &pidl_last, NULL);
497
498 if (FAILED(hr))
499 return hr;
500
501 hr = shell_folder->GetUIObjectOf(hWnd, 1, (LPCITEMIDLIST*)&pidl_last, riid, NULL, ppvOut);
502
503 ShellMalloc()->Free((void*)pidl_last);
504
505 return hr;
506 }
507
508
509 // recursively free all child entries
510 void Entry::free_subentries()
511 {
512 Entry *entry, *next=_down;
513
514 if (next) {
515 _down = 0;
516
517 do {
518 entry = next;
519 next = entry->_next;
520
521 entry->free_subentries();
522 delete entry;
523 } while(next);
524 }
525 }
526
527
528 Entry* Root::read_tree(LPCTSTR path, int scan_flags)
529 {
530 Entry* entry;
531
532 if (path && *path)
533 entry = _entry->read_tree(path, _sort_order);
534 else {
535 entry = _entry->read_tree(NULL, _sort_order);
536
537 _entry->smart_scan();
538
539 if (_entry->_down)
540 _entry->_expanded = true;
541 }
542
543 return entry;
544 }
545
546
547 Entry* Root::read_tree(LPCITEMIDLIST pidl, int scan_flags)
548 {
549 return _entry->read_tree(pidl, _sort_order);
550 }