Merge 13159:13510 from trunk
[reactos.git] / reactos / subsys / system / explorer / shell / shellbrowser.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 // shellbrowser.cpp
24 //
25 // Martin Fuchs, 23.07.2003
26 //
27
28
29 #include "precomp.h"
30
31 #include "../explorer_intres.h"
32
33
34 ShellBrowser::ShellBrowser(HWND hwnd, HWND left_hwnd, WindowHandle& right_hwnd, ShellPathInfo& create_info,
35 HIMAGELIST himl, BrowserCallback* cb, CtxMenuInterfaces& cm_ifs)
36 : _hwnd(hwnd),
37 _left_hwnd(left_hwnd),
38 _right_hwnd(right_hwnd),
39 _create_info(create_info),
40 _himl(himl),
41 _callback(cb),
42 _cm_ifs(cm_ifs)
43 {
44 _pShellView = NULL;
45 _pDropTarget = NULL;
46 _last_sel = 0;
47
48 _cur_dir = NULL;
49 }
50
51 ShellBrowser::~ShellBrowser()
52 {
53 if (_pShellView)
54 _pShellView->Release();
55
56 if (_pDropTarget) {
57 _pDropTarget->Release();
58 _pDropTarget = NULL;
59 }
60
61 if (_right_hwnd) {
62 DestroyWindow(_right_hwnd);
63 _right_hwnd = 0;
64 }
65 }
66
67
68 LRESULT ShellBrowser::Init(HWND hWndFrame)
69 {
70 CONTEXT("ShellBrowser::Init()");
71
72 _hWndFrame = hWndFrame;
73
74 const String& root_name = GetDesktopFolder().get_name(_create_info._root_shell_path, SHGDN_FORPARSING);
75
76 _root._drive_type = DRIVE_UNKNOWN;
77 lstrcpy(_root._volname, root_name);
78 _root._fs_flags = 0;
79 lstrcpy(_root._fs, TEXT("Desktop"));
80
81 _root._entry = new ShellDirectory(GetDesktopFolder(), _create_info._root_shell_path, _hwnd);
82
83 jump_to(_create_info._shell_path);
84
85 // -> set_curdir()
86 _root._entry->read_directory();
87
88 /* already filled by ShellDirectory constructor
89 lstrcpy(_root._entry->_data.cFileName, TEXT("Desktop")); */
90
91 return 0;
92 }
93
94 void ShellBrowser::jump_to(LPCITEMIDLIST pidl)
95 {
96 Entry* entry = NULL;
97
98 //@@
99 if (!_cur_dir)
100 _cur_dir = static_cast<ShellDirectory*>(_root._entry);
101
102 /*@todo
103 we should call read_tree() here to iterate through the hierarchy and open all folders from shell_info._root_shell_path to shell_info._shell_path
104 _root._entry->read_tree(shell_info._root_shell_path.get_folder(), info._shell_path, SORT_NAME);
105 -> see FileChildWindow::FileChildWindow()
106 */
107
108 if (_cur_dir) {
109 static DynamicFct<LPITEMIDLIST(WINAPI*)(LPCITEMIDLIST, LPCITEMIDLIST)> ILFindChild(TEXT("SHELL32"), 24);
110
111 LPCITEMIDLIST child_pidl;
112
113 if (ILFindChild)
114 child_pidl = (*ILFindChild)(_cur_dir->create_absolute_pidl(), pidl);
115 else
116 child_pidl = pidl; // This is not correct in the common case, but works on the desktop level.
117
118 if (child_pidl) {
119 _cur_dir->smart_scan();
120
121 entry = _cur_dir->find_entry(child_pidl);
122
123 if (entry)
124 _callback->entry_selected(entry);
125 }
126 }
127
128 //@@ work around as long as we don't iterate correctly through the ShellEntry tree
129 if (!entry)
130 UpdateFolderView(ShellFolder(pidl));
131 }
132
133
134 void ShellBrowser::InitializeTree(HIMAGELIST himl)
135 {
136 CONTEXT("ShellBrowserChild::InitializeTree()");
137
138 TreeView_SetImageList(_left_hwnd, himl, TVSIL_NORMAL);
139 TreeView_SetScrollTime(_left_hwnd, 100);
140
141 TV_INSERTSTRUCT tvInsert;
142
143 tvInsert.hParent = 0;
144 tvInsert.hInsertAfter = TVI_LAST;
145
146 TV_ITEM& tvItem = tvInsert.item;
147 tvItem.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_CHILDREN;
148 tvItem.lParam = (LPARAM)_root._entry;
149 tvItem.pszText = LPSTR_TEXTCALLBACK;
150 tvItem.iImage = tvItem.iSelectedImage = I_IMAGECALLBACK;
151 tvItem.cChildren = 1;
152
153 HTREEITEM hItem = TreeView_InsertItem(_left_hwnd, &tvInsert);
154 TreeView_SelectItem(_left_hwnd, hItem);
155 TreeView_Expand(_left_hwnd, hItem, TVE_EXPAND);
156 }
157
158 bool ShellBrowser::InitDragDrop()
159 {
160 CONTEXT("ShellBrowser::InitDragDrop()");
161
162 _pDropTarget = new TreeDropTarget(_left_hwnd);
163
164 if (!_pDropTarget)
165 return false;
166
167 _pDropTarget->AddRef();
168
169 if (FAILED(RegisterDragDrop(_left_hwnd, _pDropTarget))) {//calls addref
170 _pDropTarget->Release(); // free TreeDropTarget
171 _pDropTarget = NULL;
172 return false;
173 }
174 else
175 _pDropTarget->Release();
176
177 FORMATETC ftetc;
178
179 ftetc.dwAspect = DVASPECT_CONTENT;
180 ftetc.lindex = -1;
181 ftetc.tymed = TYMED_HGLOBAL;
182 ftetc.cfFormat = CF_HDROP;
183
184 _pDropTarget->AddSuportedFormat(ftetc);
185
186 return true;
187 }
188
189
190 void ShellBrowser::OnTreeItemRClick(int idCtrl, LPNMHDR pnmh)
191 {
192 CONTEXT("ShellBrowser::OnTreeItemRClick()");
193
194 TVHITTESTINFO tvhti;
195
196 GetCursorPos(&tvhti.pt);
197 ScreenToClient(_left_hwnd, &tvhti.pt);
198
199 tvhti.flags = LVHT_NOWHERE;
200 TreeView_HitTest(_left_hwnd, &tvhti);
201
202 if (TVHT_ONITEM & tvhti.flags) {
203 LPARAM itemData = TreeView_GetItemData(_left_hwnd, tvhti.hItem);
204
205 if (itemData) {
206 Entry* entry = (Entry*)itemData;
207 ClientToScreen(_left_hwnd, &tvhti.pt);
208
209 CHECKERROR(entry->do_context_menu(_hwnd, tvhti.pt, _cm_ifs));
210 }
211 }
212 }
213
214 void ShellBrowser::OnTreeGetDispInfo(int idCtrl, LPNMHDR pnmh)
215 {
216 CONTEXT("ShellBrowser::OnTreeGetDispInfo()");
217
218 LPNMTVDISPINFO lpdi = (LPNMTVDISPINFO)pnmh;
219 ShellEntry* entry = (ShellEntry*)lpdi->item.lParam;
220
221 if (entry) {
222 if (lpdi->item.mask & TVIF_TEXT)
223 lpdi->item.pszText = entry->_display_name;
224
225 if (lpdi->item.mask & (/*TVIF_TEXT|*/TVIF_IMAGE|TVIF_SELECTEDIMAGE)) {
226 ShellPath pidl_abs = entry->create_absolute_pidl(); // Caching of absolute PIDLs could enhance performance.
227 LPCITEMIDLIST pidl = pidl_abs;
228
229 SHFILEINFO sfi;
230 /*
231 if (lpdi->item.mask & TVIF_TEXT)
232 if (SHGetFileInfo((LPCTSTR)pidl, 0, &sfi, sizeof(sfi), SHGFI_PIDL|SHGFI_DISPLAYNAME))
233 lstrcpy(lpdi->item.pszText, sfi.szDisplayName); ///@todo look at cchTextMax if there is enough space available
234 else
235 lpdi->item.pszText = entry->_data.cFileName;
236 */
237 if (lpdi->item.mask & TVIF_IMAGE)
238 if ((HIMAGELIST)SHGetFileInfo((LPCTSTR)pidl, 0, &sfi, sizeof(sfi), SHGFI_PIDL|SHGFI_SYSICONINDEX|SHGFI_SMALLICON|SHGFI_LINKOVERLAY) == _himl)
239 lpdi->item.iImage = sfi.iIcon;
240 else
241 lpdi->item.iImage = -1;
242
243 if (lpdi->item.mask & TVIF_SELECTEDIMAGE)
244 if ((HIMAGELIST)SHGetFileInfo((LPCTSTR)pidl, 0, &sfi, sizeof(sfi), SHGFI_PIDL|SHGFI_SYSICONINDEX|SHGFI_SMALLICON|SHGFI_OPENICON) == _himl)
245 lpdi->item.iSelectedImage = sfi.iIcon;
246 else
247 lpdi->item.iSelectedImage = -1;
248 }
249 }
250 }
251
252 void ShellBrowser::OnTreeItemExpanding(int idCtrl, LPNMTREEVIEW pnmtv)
253 {
254 CONTEXT("ShellBrowser::OnTreeItemExpanding()");
255
256 if (pnmtv->action == TVE_COLLAPSE)
257 TreeView_Expand(_left_hwnd, pnmtv->itemNew.hItem, TVE_COLLAPSE|TVE_COLLAPSERESET);
258 else if (pnmtv->action == TVE_EXPAND) {
259 ShellDirectory* entry = (ShellDirectory*)TreeView_GetItemData(_left_hwnd, pnmtv->itemNew.hItem);
260
261 if (entry)
262 if (!InsertSubitems(pnmtv->itemNew.hItem, entry, entry->_folder)) {
263 entry->_shell_attribs &= ~SFGAO_HASSUBFOLDER;
264
265 // remove subitem "+"
266 TV_ITEM tvItem;
267
268 tvItem.mask = TVIF_CHILDREN;
269 tvItem.hItem = pnmtv->itemNew.hItem;
270 tvItem.cChildren = 0;
271
272 TreeView_SetItem(_left_hwnd, &tvItem);
273 }
274 }
275 }
276
277 int ShellBrowser::InsertSubitems(HTREEITEM hParentItem, Entry* entry, IShellFolder* pParentFolder)
278 {
279 CONTEXT("ShellBrowser::InsertSubitems()");
280
281 WaitCursor wait;
282
283 int cnt = 0;
284
285 SendMessage(_left_hwnd, WM_SETREDRAW, FALSE, 0);
286
287 try {
288 entry->smart_scan();
289 } catch(COMException& e) {
290 HandleException(e, g_Globals._hMainWnd);
291 }
292
293 TV_ITEM tvItem;
294 TV_INSERTSTRUCT tvInsert;
295
296 for(entry=entry->_down; entry; entry=entry->_next) {
297 #ifndef _LEFT_FILES
298 if (entry->_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
299 #endif
300 {
301 ZeroMemory(&tvItem, sizeof(tvItem));
302
303 tvItem.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_CHILDREN;
304 tvItem.pszText = LPSTR_TEXTCALLBACK;
305 tvItem.iImage = tvItem.iSelectedImage = I_IMAGECALLBACK;
306 tvItem.lParam = (LPARAM)entry;
307 tvItem.cChildren = entry->_shell_attribs & SFGAO_HASSUBFOLDER? 1: 0;
308
309 if (entry->_shell_attribs & SFGAO_SHARE) {
310 tvItem.mask |= TVIF_STATE;
311 tvItem.stateMask |= TVIS_OVERLAYMASK;
312 tvItem.state |= INDEXTOOVERLAYMASK(1);
313 }
314
315 tvInsert.item = tvItem;
316 tvInsert.hInsertAfter = TVI_LAST;
317 tvInsert.hParent = hParentItem;
318
319 TreeView_InsertItem(_left_hwnd, &tvInsert);
320 }
321
322 ++cnt;
323 }
324
325 SendMessage(_left_hwnd, WM_SETREDRAW, TRUE, 0);
326
327 return cnt;
328 }
329
330 void ShellBrowser::OnTreeItemSelected(int idCtrl, LPNMTREEVIEW pnmtv)
331 {
332 CONTEXT("ShellBrowser::OnTreeItemSelected()");
333
334 ShellEntry* entry = (ShellEntry*)pnmtv->itemNew.lParam;
335
336 _last_sel = pnmtv->itemNew.hItem;
337
338 if (entry)
339 _callback->entry_selected(entry);
340 }
341
342 void ShellBrowser::UpdateFolderView(IShellFolder* folder)
343 {
344 CONTEXT("ShellBrowser::UpdateFolderView()");
345
346 FOLDERSETTINGS fs;
347 IShellView* pLastShellView = _pShellView;
348
349 _folder = folder;
350
351 if (pLastShellView)
352 pLastShellView->GetCurrentInfo(&fs);
353 else {
354 fs.ViewMode = _create_info._open_mode&OWM_DETAILS? FVM_DETAILS: FVM_ICON;
355 fs.fFlags = FWF_NOCLIENTEDGE|FWF_BESTFITWINDOW;
356 }
357
358 HRESULT hr = folder->CreateViewObject(_hwnd, IID_IShellView, (void**)&_pShellView);
359
360 if (FAILED(hr)) {
361 _pShellView = NULL;
362 return;
363 }
364
365 RECT rect = {CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT};
366 hr = _pShellView->CreateViewWindow(pLastShellView, &fs, static_cast<IShellBrowser*>(this), &rect, &_right_hwnd/*&m_hWndListView*/);
367
368 if (pLastShellView) {
369 pLastShellView->GetCurrentInfo(&fs);
370 pLastShellView->UIActivate(SVUIA_DEACTIVATE);
371 pLastShellView->DestroyViewWindow();
372 pLastShellView->Release();
373 }
374
375 _pShellView->UIActivate(SVUIA_ACTIVATE_NOFOCUS);
376 }
377
378
379 HRESULT ShellBrowser::OnDefaultCommand(LPIDA pida)
380 {
381 CONTEXT("ShellBrowser::OnDefaultCommand()");
382
383 if (pida->cidl >= 1) {
384 if (_left_hwnd) { // explorer mode
385 if (_last_sel) {
386 ShellDirectory* parent = (ShellDirectory*)TreeView_GetItemData(_left_hwnd, _last_sel);
387
388 if (parent) {
389 try {
390 parent->smart_scan();
391 } catch(COMException& e) {
392 return e.Error();
393 }
394
395 UINT firstOffset = pida->aoffset[1];
396 LPITEMIDLIST pidl = (LPITEMIDLIST)((LPBYTE)pida+firstOffset);
397
398 Entry* entry = parent->find_entry(pidl);
399
400 if (entry && (entry->_data.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY))
401 if (entry->_etype == ET_SHELL)
402 if (_last_sel && select_entry(_last_sel, entry))
403 return S_OK;
404 }
405 }
406 } else { // no tree control
407 if (MainFrameBase::OpenShellFolders(pida, _hWndFrame))
408 return S_OK;
409
410 /* create new Frame Window
411 if (MainFrame::OpenShellFolders(pida, 0))
412 return S_OK;
413 */
414 }
415 }
416
417 return E_NOTIMPL;
418 }
419
420
421 HTREEITEM ShellBrowser::select_entry(HTREEITEM hitem, Entry* entry, bool expand)
422 {
423 CONTEXT("ShellBrowser::select_entry()");
424
425 if (expand && !TreeView_Expand(_left_hwnd, hitem, TVE_EXPAND))
426 return 0;
427
428 for(hitem=TreeView_GetChild(_left_hwnd,hitem); hitem; hitem=TreeView_GetNextSibling(_left_hwnd,hitem)) {
429 if ((Entry*)TreeView_GetItemData(_left_hwnd,hitem) == entry) {
430 if (TreeView_SelectItem(_left_hwnd, hitem)) {
431 if (expand)
432 TreeView_Expand(_left_hwnd, hitem, TVE_EXPAND);
433
434 return hitem;
435 }
436
437 break;
438 }
439 }
440
441 return 0;
442 }
443
444
445 bool ShellBrowser::jump_to_pidl(LPCITEMIDLIST pidl)
446 {
447 if (!_root._entry)
448 return false;
449
450 // iterate through the hierarchy and open all folders to reach pidl
451 WaitCursor wait;
452
453 HTREEITEM hitem = TreeView_GetRoot(_left_hwnd);
454 Entry* entry = _root._entry;
455
456 for(const void*p=pidl;;) {
457 if (!p)
458 return true;
459
460 if (!entry || !hitem)
461 break;
462
463 entry->smart_scan(SORT_NAME);
464
465 Entry* found = entry->find_entry(p);
466 p = entry->get_next_path_component(p);
467
468 if (found)
469 hitem = select_entry(hitem, found);
470
471 entry = found;
472 }
473
474 return false;
475 }
476
477
478 #ifndef _NO_MDI
479
480 MDIShellBrowserChild::MDIShellBrowserChild(HWND hwnd, const ShellChildWndInfo& info)
481 : super(hwnd, info),
482 _create_info(info),
483 _shellpath_info(info) //@@ copies info -> no referenz to _create_info !
484 {
485 /**todo Conversion of shell path into path string -> store into URL history
486 // store path into history
487 if (info._path && *info._path)
488 _url_history.push(info._path);
489 */
490 }
491
492
493 MDIShellBrowserChild* MDIShellBrowserChild::create(const ShellChildWndInfo& info)
494 {
495 ChildWindow* child = ChildWindow::create(info, info._pos.rcNormalPosition,
496 WINDOW_CREATOR_INFO(MDIShellBrowserChild,ShellChildWndInfo), CLASSNAME_CHILDWND, NULL, info._pos.showCmd==SW_SHOWMAXIMIZED? WS_MAXIMIZE: 0);
497
498 return static_cast<MDIShellBrowserChild*>(child);
499 }
500
501
502 LRESULT MDIShellBrowserChild::Init(LPCREATESTRUCT pcs)
503 {
504 CONTEXT("MDIShellBrowserChild::Init()");
505
506 if (super::Init(pcs))
507 return 1;
508
509 init_himl();
510
511 update_shell_browser();
512
513 if (&*_shellBrowser)
514 if (_left_hwnd)
515 _shellBrowser->Init(_himlSmall);
516 else
517 _shellBrowser->UpdateFolderView(_create_info._shell_path.get_folder());
518
519 return 0;
520 }
521
522
523 LRESULT MDIShellBrowserChild::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam)
524 {
525 switch(nmsg) {
526 case PM_DISPATCH_COMMAND: {
527 switch(LOWORD(wparam)) {
528 case ID_WINDOW_NEW: {CONTEXT("MDIShellBrowserChild PM_DISPATCH_COMMAND ID_WINDOW_NEW");
529 MDIShellBrowserChild::create(_create_info);
530 break;}
531
532 case ID_REFRESH:
533 //@todo refresh shell child
534 break;
535
536 case ID_VIEW_SDI:
537 MainFrameBase::Create(_url, false);
538 break;
539
540 default:
541 return super::WndProc(nmsg, wparam, lparam);
542 }
543 return TRUE;}
544
545 default:
546 return super::WndProc(nmsg, wparam, lparam);
547 }
548
549 return 0;
550 }
551
552 void MDIShellBrowserChild::update_shell_browser()
553 {
554 int split_pos = DEFAULT_SPLIT_POS;
555
556 if (_shellBrowser.get()) {
557 split_pos = _split_pos;
558 delete _shellBrowser.release();
559 }
560
561 // create explorer treeview
562 if (_create_info._open_mode & OWM_EXPLORE) {
563 if (!_left_hwnd) {
564 ClientRect rect(_hwnd);
565
566 _left_hwnd = CreateWindowEx(0, WC_TREEVIEW, NULL,
567 WS_CHILD|WS_TABSTOP|WS_VISIBLE|WS_CHILD|TVS_HASLINES|TVS_LINESATROOT|TVS_HASBUTTONS|TVS_SHOWSELALWAYS,//|TVS_NOTOOLTIPS
568 0, rect.top, split_pos-SPLIT_WIDTH/2, rect.bottom-rect.top,
569 _hwnd, (HMENU)IDC_FILETREE, g_Globals._hInstance, 0);
570 }
571 } else {
572 if (_left_hwnd) {
573 DestroyWindow(_left_hwnd);
574 _left_hwnd = 0;
575 }
576 }
577
578 _shellBrowser = auto_ptr<ShellBrowser>(new ShellBrowser(_hwnd, _left_hwnd, _right_hwnd,
579 _shellpath_info, _himlSmall, this, _cm_ifs));
580
581 _shellBrowser->Init(_hwndFrame);
582 }
583
584
585 String MDIShellBrowserChild::jump_to_int(LPCTSTR url)
586 {
587 String dir, fname;
588
589 if (!_tcsnicmp(url, TEXT("shell://"), 8)) {
590 if (_shellBrowser->jump_to_pidl(ShellPath(url+8)))
591 return url;
592 }
593
594 if (SplitFileSysURL(url, dir, fname)) {
595
596 ///@todo use fname
597
598 if (_shellBrowser->jump_to_pidl(ShellPath(dir)))
599 return FmtString(TEXT("file://%s"), (LPCTSTR)dir);
600 }
601
602 return String();
603 }
604
605
606 void MDIShellBrowserChild::entry_selected(Entry* entry)
607 {
608 if (entry->_etype == ET_SHELL) {
609 ShellEntry* shell_entry = static_cast<ShellEntry*>(entry);
610 IShellFolder* folder;
611
612 if (shell_entry->_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
613 folder = static_cast<ShellDirectory*>(shell_entry)->_folder;
614 else
615 folder = shell_entry->get_parent_folder();
616
617 if (!folder) {
618 assert(folder);
619 return;
620 }
621
622 TCHAR path[MAX_PATH];
623
624 if (shell_entry->get_path(path)) {
625 String url;
626
627 if (path[0] == ':')
628 url.printf(TEXT("shell://%s"), path);
629 else
630 url.printf(TEXT("file://%s"), path);
631
632 set_url(url);
633 }
634
635 _shellBrowser->UpdateFolderView(folder);
636
637 // set size of new created shell view windows
638 ClientRect rt(_hwnd);
639 resize_children(rt.right, rt.bottom);
640 }
641 }
642
643 #endif