experimental ROS Shell without integrated explorer part
[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 "precomp.h"
30
31
32 // allocate and initialise a directory entry
33 Entry::Entry(ENTRY_TYPE etype)
34 : _etype(etype)
35 {
36 _up = NULL;
37 _next = NULL;
38 _down = NULL;
39 _expanded = false;
40 _scanned = false;
41 _bhfi_valid = false;
42 _level = 0;
43 _icon_id = ICID_UNKNOWN;
44 _display_name = _data.cFileName;
45 _type_name = NULL;
46 _content = NULL;
47 }
48
49 Entry::Entry(Entry* parent, ENTRY_TYPE etype)
50 : _up(parent),
51 _etype(etype)
52 {
53 _next = NULL;
54 _down = NULL;
55 _expanded = false;
56 _scanned = false;
57 _bhfi_valid = false;
58 _level = 0;
59 _icon_id = ICID_UNKNOWN;
60 _display_name = _data.cFileName;
61 _type_name = NULL;
62 _content = NULL;
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 _type_name = other._type_name? _tcsdup(other._type_name): NULL;
84 _content = other._content? _tcsdup(other._content): NULL;
85
86 _etype = other._etype;
87 _icon_id = other._icon_id;
88
89 _bhfi = other._bhfi;
90 _bhfi_valid = other._bhfi_valid;
91 }
92
93 // free a directory entry
94 Entry::~Entry()
95 {
96 if (_icon_id > ICID_NONE)
97 g_Globals._icon_cache.free_icon(_icon_id);
98
99 if (_display_name != _data.cFileName)
100 free(_display_name);
101
102 if (_type_name)
103 free(_type_name);
104
105 if (_content)
106 free(_content);
107 }
108
109
110 // read directory tree and expand to the given location
111 Entry* Entry::read_tree(const void* path, SORT_ORDER sortOrder)
112 {
113 CONTEXT("Entry::read_tree()");
114
115 HCURSOR old_cursor = SetCursor(LoadCursor(0, IDC_WAIT));
116
117 Entry* entry = this;
118
119 for(const void*p=path; p && entry; ) {
120 entry->read_directory(sortOrder);
121
122 if (entry->_down)
123 entry->_expanded = true;
124
125 Entry* next_entry = entry->find_entry(p);
126 p = entry->get_next_path_component(p);
127
128 entry = next_entry;
129 }
130
131 SetCursor(old_cursor);
132
133 return entry;
134 }
135
136
137 void Entry::read_directory(SORT_ORDER sortOrder, int scan_flags)
138 {
139 CONTEXT("Entry::read_directory(SORT_ORDER)");
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(int scan_flags)
312 {
313 CONTEXT("Entry::smart_scan()");
314
315 if (!_scanned) {
316 free_subentries();
317 read_directory(SORT_NAME, scan_flags); // 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))
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::GetUIObjectOf(HWND hWnd, REFIID riid, LPVOID* ppvOut)
407 {
408 TCHAR path[MAX_PATH];
409 /*
410 if (!get_path(path))
411 return E_FAIL;
412
413 ShellPath shell_path(path);
414
415 IShellFolder* pFolder;
416 LPCITEMIDLIST pidl_last = NULL;
417
418 static DynamicFct<HRESULT(WINAPI*)(LPCITEMIDLIST, REFIID, LPVOID*, LPCITEMIDLIST*)> SHBindToParent(TEXT("SHELL32"), "SHBindToParent");
419
420 if (!SHBindToParent)
421 return E_NOTIMPL;
422
423 HRESULT hr = (*SHBindToParent)(shell_path, IID_IShellFolder, (LPVOID*)&pFolder, &pidl_last);
424 if (FAILED(hr))
425 return hr;
426
427 ShellFolder shell_folder(pFolder);
428
429 shell_folder->Release();
430
431 return shell_folder->GetUIObjectOf(hWnd, 1, &pidl_last, riid, NULL, ppvOut);
432 */
433 if (!_up)
434 return E_INVALIDARG;
435
436 if (!_up->get_path(path))
437 return E_FAIL;
438
439 ShellPath shell_path(path);
440 ShellFolder shell_folder(shell_path);
441
442 #ifdef UNICODE
443 LPWSTR wname = _data.cFileName;
444 #else
445 WCHAR wname[MAX_PATH];
446 MultiByteToWideChar(CP_ACP, 0, _data.cFileName, -1, wname, MAX_PATH);
447 #endif
448
449 LPITEMIDLIST pidl_last = NULL;
450 HRESULT hr = shell_folder->ParseDisplayName(hWnd, NULL, wname, NULL, &pidl_last, NULL);
451
452 if (FAILED(hr))
453 return hr;
454
455 hr = shell_folder->GetUIObjectOf(hWnd, 1, (LPCITEMIDLIST*)&pidl_last, riid, NULL, ppvOut);
456
457 ShellMalloc()->Free((void*)pidl_last);
458
459 return hr;
460 }
461
462
463 // recursively free all child entries
464 void Entry::free_subentries()
465 {
466 Entry *entry, *next=_down;
467
468 if (next) {
469 _down = 0;
470
471 do {
472 entry = next;
473 next = entry->_next;
474
475 entry->free_subentries();
476 delete entry;
477 } while(next);
478 }
479 }