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