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