optimize favorites sidebar
[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 "../utility/utility.h"
30
31 #include "../explorer.h"
32 #include "../globals.h"
33
34 #include "../explorer_intres.h"
35
36
37 ShellBrowserChild::ShellBrowserChild(HWND hwnd, const ShellChildWndInfo& info)
38 : super(hwnd, info),
39 _create_info(info)
40 {
41 _pShellView = NULL;
42 _pDropTarget = NULL;
43 _himlSmall = 0;
44 _last_sel = 0;
45
46 // store path into history
47 if (info._path && *info._path)
48 _url_history.push(info._path);
49 }
50
51 ShellBrowserChild::~ShellBrowserChild()
52 {
53 if (_pShellView)
54 _pShellView->Release();
55
56 if (_pDropTarget) {
57 _pDropTarget->Release();
58 _pDropTarget = NULL;
59 }
60 }
61
62
63 LRESULT ShellBrowserChild::Init(LPCREATESTRUCT pcs)
64 {
65 CONTEXT("ShellBrowserChild::Init()");
66
67 if (super::Init(pcs))
68 return 1;
69
70 _hWndFrame = GetParent(pcs->hwndParent);
71
72 ClientRect rect(_hwnd);
73
74 SHFILEINFO sfi;
75
76 _himlSmall = (HIMAGELIST)SHGetFileInfo(TEXT("C:\\"), 0, &sfi, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX|SHGFI_SMALLICON);
77 // _himlLarge = (HIMAGELIST)SHGetFileInfo(TEXT("C:\\"), 0, &sfi, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX|SHGFI_LARGEICON);
78
79
80 const String& root_name = GetDesktopFolder().get_name(_create_info._root_shell_path, SHGDN_FORPARSING);
81
82 _root._drive_type = DRIVE_UNKNOWN;
83 lstrcpy(_root._volname, root_name); // most of the time "Desktop"
84 _root._fs_flags = 0;
85 lstrcpy(_root._fs, TEXT("Desktop"));
86
87 //@@ _root._entry->read_tree(shell_info._root_shell_path.get_folder(), info._shell_path, SORT_NAME/*_sortOrder*/);
88
89 /*@todo
90 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
91 -> see FileChildWindow::FileChildWindow()
92 */
93 _root._entry = new ShellDirectory(GetDesktopFolder(), _create_info._root_shell_path, _hwnd);
94 _root._entry->read_directory();
95
96 /* already filled by ShellDirectory constructor
97 lstrcpy(_root._entry->_data.cFileName, TEXT("Desktop")); */
98
99
100 // create explorer treeview
101 if (_create_info._open_mode & OWM_EXPLORE)
102 _left_hwnd = CreateWindowEx(0, WC_TREEVIEW, NULL,
103 WS_CHILD|WS_TABSTOP|WS_VISIBLE|WS_CHILD|TVS_HASLINES|TVS_LINESATROOT|TVS_HASBUTTONS|TVS_SHOWSELALWAYS,//|TVS_NOTOOLTIPS
104 0, rect.top, _split_pos-SPLIT_WIDTH/2, rect.bottom-rect.top,
105 _hwnd, (HMENU)IDC_FILETREE, g_Globals._hInstance, 0);
106
107 if (_left_hwnd) {
108 InitializeTree();
109
110 InitDragDrop();
111 } else
112 UpdateFolderView(_create_info._shell_path.get_folder());
113
114 return 0;
115 }
116
117
118 void ShellBrowserChild::InitializeTree()
119 {
120 CONTEXT("ShellBrowserChild::InitializeTree()");
121
122 TreeView_SetImageList(_left_hwnd, _himlSmall, TVSIL_NORMAL);
123 TreeView_SetScrollTime(_left_hwnd, 100);
124
125 TV_INSERTSTRUCT tvInsert;
126
127 tvInsert.hParent = 0;
128 tvInsert.hInsertAfter = TVI_LAST;
129
130 TV_ITEM& tvItem = tvInsert.item;
131 tvItem.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_CHILDREN;
132 tvItem.lParam = (LPARAM)_root._entry;
133 tvItem.pszText = LPSTR_TEXTCALLBACK;
134 tvItem.iImage = tvItem.iSelectedImage = I_IMAGECALLBACK;
135 tvItem.cChildren = 1;
136
137 HTREEITEM hItem = TreeView_InsertItem(_left_hwnd, &tvInsert);
138 TreeView_SelectItem(_left_hwnd, hItem);
139 TreeView_Expand(_left_hwnd, hItem, TVE_EXPAND);
140 }
141
142
143 bool ShellBrowserChild::InitDragDrop()
144 {
145 CONTEXT("ShellBrowserChild::InitDragDrop()");
146
147 _pDropTarget = new TreeDropTarget(_left_hwnd);
148
149 if (!_pDropTarget)
150 return false;
151
152 _pDropTarget->AddRef();
153
154 if (FAILED(RegisterDragDrop(_left_hwnd, _pDropTarget))) {//calls addref
155 _pDropTarget->Release(); // free TreeDropTarget
156 _pDropTarget = NULL;
157 return false;
158 }
159 else
160 _pDropTarget->Release();
161
162 FORMATETC ftetc;
163
164 ftetc.dwAspect = DVASPECT_CONTENT;
165 ftetc.lindex = -1;
166 ftetc.tymed = TYMED_HGLOBAL;
167 ftetc.cfFormat = CF_HDROP;
168
169 _pDropTarget->AddSuportedFormat(ftetc);
170
171 return true;
172 }
173
174
175 void ShellBrowserChild::OnTreeItemRClick(int idCtrl, LPNMHDR pnmh)
176 {
177 CONTEXT("ShellBrowserChild::OnTreeItemRClick()");
178
179 TVHITTESTINFO tvhti;
180
181 GetCursorPos(&tvhti.pt);
182 ScreenToClient(_left_hwnd, &tvhti.pt);
183
184 tvhti.flags = LVHT_NOWHERE;
185 TreeView_HitTest(_left_hwnd, &tvhti);
186
187 if (TVHT_ONITEM & tvhti.flags) {
188 ClientToScreen(_left_hwnd, &tvhti.pt);
189 Tree_DoItemMenu(_left_hwnd, tvhti.hItem , &tvhti.pt);
190 }
191 }
192
193 void ShellBrowserChild::Tree_DoItemMenu(HWND hwndTreeView, HTREEITEM hItem, LPPOINT pptScreen)
194 {
195 CONTEXT("ShellBrowserChild::Tree_DoItemMenu()");
196
197 LPARAM itemData = TreeView_GetItemData(hwndTreeView, hItem);
198
199 if (itemData) {
200 Entry* entry = (Entry*)itemData;
201
202 if (entry->_etype == ET_SHELL) {
203 ShellDirectory* dir = static_cast<ShellDirectory*>(entry->_up);
204 ShellFolder folder = dir? dir->_folder: GetDesktopFolder();
205 LPCITEMIDLIST pidl = static_cast<ShellEntry*>(entry)->_pidl;
206
207 CHECKERROR(ShellFolderContextMenu(folder, ::GetParent(hwndTreeView), 1, &pidl, pptScreen->x, pptScreen->y));
208 } else {
209 ShellPath shell_path = entry->create_absolute_pidl();
210 LPCITEMIDLIST pidl = shell_path;
211
212 ///@todo use parent folder instead of desktop
213 CHECKERROR(ShellFolderContextMenu(GetDesktopFolder(), _hwnd, 1, &pidl, pptScreen->x, pptScreen->y));
214 }
215 }
216 }
217
218 void ShellBrowserChild::OnTreeGetDispInfo(int idCtrl, LPNMHDR pnmh)
219 {
220 CONTEXT("ShellBrowserChild::OnTreeGetDispInfo()");
221
222 LPNMTVDISPINFO lpdi = (LPNMTVDISPINFO)pnmh;
223 ShellEntry* entry = (ShellEntry*)lpdi->item.lParam;
224
225 if (lpdi->item.mask & TVIF_TEXT)
226 lpdi->item.pszText = entry->_display_name;
227
228 if (lpdi->item.mask & (/*TVIF_TEXT|*/TVIF_IMAGE|TVIF_SELECTEDIMAGE)) {
229 ShellPath pidl_abs = entry->create_absolute_pidl(); // Caching of absolute PIDLs could enhance performance.
230 LPCITEMIDLIST pidl = pidl_abs;
231
232 SHFILEINFO sfi;
233 /*
234 if (lpdi->item.mask & TVIF_TEXT)
235 if (SHGetFileInfo((LPCTSTR)pidl, 0, &sfi, sizeof(sfi), SHGFI_PIDL|SHGFI_DISPLAYNAME))
236 lstrcpy(lpdi->item.pszText, sfi.szDisplayName); ///@todo look at cchTextMax if there is enough space available
237 else
238 lpdi->item.pszText = entry->_data.cFileName;
239 */
240 if (lpdi->item.mask & TVIF_IMAGE)
241 if ((HIMAGELIST)SHGetFileInfo((LPCTSTR)pidl, 0, &sfi, sizeof(sfi), SHGFI_PIDL|SHGFI_SYSICONINDEX|SHGFI_SMALLICON|SHGFI_LINKOVERLAY) == _himlSmall)
242 lpdi->item.iImage = sfi.iIcon;
243 else
244 lpdi->item.iImage = -1;
245
246 if (lpdi->item.mask & TVIF_SELECTEDIMAGE)
247 if ((HIMAGELIST)SHGetFileInfo((LPCTSTR)pidl, 0, &sfi, sizeof(sfi), SHGFI_PIDL|SHGFI_SYSICONINDEX|SHGFI_SMALLICON|SHGFI_OPENICON) == _himlSmall)
248 lpdi->item.iSelectedImage = sfi.iIcon;
249 else
250 lpdi->item.iSelectedImage = -1;
251 }
252 }
253
254 void ShellBrowserChild::OnTreeItemExpanding(int idCtrl, LPNMTREEVIEW pnmtv)
255 {
256 CONTEXT("ShellBrowserChild::OnTreeItemExpanding()");
257
258 if (pnmtv->action == TVE_COLLAPSE)
259 TreeView_Expand(_left_hwnd, pnmtv->itemNew.hItem, TVE_COLLAPSE|TVE_COLLAPSERESET);
260 else if (pnmtv->action == TVE_EXPAND) {
261 ShellDirectory* entry = (ShellDirectory*)TreeView_GetItemData(_left_hwnd, pnmtv->itemNew.hItem);
262
263 if (entry)
264 if (!InsertSubitems(pnmtv->itemNew.hItem, entry, entry->_folder)) {
265 entry->_shell_attribs &= ~SFGAO_HASSUBFOLDER;
266
267 // remove subitem "+"
268 TV_ITEM tvItem;
269
270 tvItem.mask = TVIF_CHILDREN;
271 tvItem.hItem = pnmtv->itemNew.hItem;
272 tvItem.cChildren = 0;
273
274 TreeView_SetItem(_left_hwnd, &tvItem);
275 }
276 }
277 }
278
279 int ShellBrowserChild::InsertSubitems(HTREEITEM hParentItem, Entry* entry, IShellFolder* pParentFolder)
280 {
281 CONTEXT("ShellBrowserChild::InsertSubitems()");
282
283 WaitCursor wait;
284
285 int cnt = 0;
286
287 SendMessage(_left_hwnd, WM_SETREDRAW, FALSE, 0);
288
289 try {
290 entry->smart_scan();
291 } catch(COMException& e) {
292 HandleException(e, g_Globals._hMainWnd);
293 }
294
295 TV_ITEM tvItem;
296 TV_INSERTSTRUCT tvInsert;
297
298 for(entry=entry->_down; entry; entry=entry->_next) {
299 #ifndef _LEFT_FILES
300 if (entry->_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
301 #endif
302 {
303 ZeroMemory(&tvItem, sizeof(tvItem));
304
305 tvItem.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_CHILDREN;
306 tvItem.pszText = LPSTR_TEXTCALLBACK;
307 tvItem.iImage = tvItem.iSelectedImage = I_IMAGECALLBACK;
308 tvItem.lParam = (LPARAM)entry;
309 tvItem.cChildren = entry->_shell_attribs & SFGAO_HASSUBFOLDER? 1: 0;
310
311 if (entry->_shell_attribs & SFGAO_SHARE) {
312 tvItem.mask |= TVIF_STATE;
313 tvItem.stateMask |= TVIS_OVERLAYMASK;
314 tvItem.state |= INDEXTOOVERLAYMASK(1);
315 }
316
317 tvInsert.item = tvItem;
318 tvInsert.hInsertAfter = TVI_LAST;
319 tvInsert.hParent = hParentItem;
320
321 TreeView_InsertItem(_left_hwnd, &tvInsert);
322 }
323
324 ++cnt;
325 }
326
327 SendMessage(_left_hwnd, WM_SETREDRAW, TRUE, 0);
328
329 return cnt;
330 }
331
332 void ShellBrowserChild::OnTreeItemSelected(int idCtrl, LPNMTREEVIEW pnmtv)
333 {
334 CONTEXT("ShellBrowserChild::OnTreeItemSelected()");
335
336 ShellEntry* entry = (ShellEntry*)pnmtv->itemNew.lParam;
337
338 _last_sel = pnmtv->itemNew.hItem;
339
340 if (entry->_etype == ET_SHELL) {
341 IShellFolder* folder;
342
343 if (entry->_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
344 folder = static_cast<ShellDirectory*>(entry)->_folder;
345 else
346 folder = entry->get_parent_folder();
347
348 if (!folder) {
349 assert(folder);
350 return;
351 }
352
353 TCHAR path[MAX_PATH];
354
355 if (entry->get_path(path)) {
356 String url;
357
358 if (path[0] == ':')
359 url.printf(TEXT("shell://%s"), path);
360 else
361 url.printf(TEXT("file://%s"), path);
362
363 set_url(url);
364 }
365
366 UpdateFolderView(folder);
367 }
368 }
369
370 void ShellBrowserChild::UpdateFolderView(IShellFolder* folder)
371 {
372 CONTEXT("ShellBrowserChild::UpdateFolderView()");
373
374 FOLDERSETTINGS fs;
375 IShellView* pLastShellView = _pShellView;
376
377 _folder = folder;
378
379 if (pLastShellView)
380 pLastShellView->GetCurrentInfo(&fs);
381 else {
382 fs.ViewMode = _create_info._open_mode&OWM_DETAILS? FVM_DETAILS: FVM_ICON;
383 fs.fFlags = FWF_NOCLIENTEDGE|FWF_BESTFITWINDOW;
384 }
385
386 HRESULT hr = folder->CreateViewObject(_hwnd, IID_IShellView, (void**)&_pShellView);
387
388 if (FAILED(hr)) {
389 _pShellView = NULL;
390 return;
391 }
392
393 RECT rect = {CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT};
394 hr = _pShellView->CreateViewWindow(pLastShellView, &fs, static_cast<IShellBrowser*>(this), &rect, &_right_hwnd/*&m_hWndListView*/);
395
396 if (pLastShellView) {
397 pLastShellView->GetCurrentInfo(&fs);
398 pLastShellView->UIActivate(SVUIA_DEACTIVATE);
399 pLastShellView->DestroyViewWindow();
400 pLastShellView->Release();
401
402 ClientRect clnt(_hwnd);
403 resize_children(clnt.right, clnt.bottom);
404 }
405
406 _pShellView->UIActivate(SVUIA_ACTIVATE_NOFOCUS);
407 }
408
409
410 LRESULT ShellBrowserChild::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam)
411 {
412 switch(nmsg) {
413 case WM_GETISHELLBROWSER: // for Registry Explorer Plugin
414 return (LRESULT)static_cast<IShellBrowser*>(this);
415
416 case PM_GET_SHELLBROWSER_PTR:
417 return (LRESULT)this;
418
419 case PM_DISPATCH_COMMAND: {
420 switch(LOWORD(wparam)) {
421 case ID_WINDOW_NEW: {CONTEXT("ShellBrowserChild PM_DISPATCH_COMMAND ID_WINDOW_NEW");
422 ShellBrowserChild::create(_create_info);
423 break;}
424
425 case ID_REFRESH:
426 //@todo refresh shell child
427 break;
428
429 default:
430 return super::WndProc(nmsg, wparam, lparam);
431 }
432 return TRUE;}
433
434 default:
435 return super::WndProc(nmsg, wparam, lparam);
436 }
437
438 return 0;
439 }
440
441 int ShellBrowserChild::Notify(int id, NMHDR* pnmh)
442 {
443 switch(pnmh->code) {
444 case TVN_GETDISPINFO: OnTreeGetDispInfo(id, pnmh); break;
445 case TVN_SELCHANGED: OnTreeItemSelected(id, (LPNMTREEVIEW)pnmh); break;
446 case TVN_ITEMEXPANDING: OnTreeItemExpanding(id, (LPNMTREEVIEW)pnmh); break;
447 case NM_RCLICK: OnTreeItemRClick(id, pnmh); break;
448 default: return super::Notify(id, pnmh);
449 }
450
451 return 0;
452 }
453
454
455 HRESULT ShellBrowserChild::OnDefaultCommand(LPIDA pida)
456 {
457 CONTEXT("ShellBrowserChild::OnDefaultCommand()");
458
459 if (pida->cidl >= 1) {
460 if (_left_hwnd) { // explorer mode
461 if (_last_sel) {
462 ShellDirectory* parent = (ShellDirectory*)TreeView_GetItemData(_left_hwnd, _last_sel);
463
464 if (parent) {
465 try {
466 parent->smart_scan();
467 } catch(COMException& e) {
468 return e.Error();
469 }
470
471 UINT firstOffset = pida->aoffset[1];
472 LPITEMIDLIST pidl = (LPITEMIDLIST)((LPBYTE)pida+firstOffset);
473
474 Entry* entry = parent->find_entry(pidl);
475
476 if (entry && (entry->_data.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY))
477 if (entry->_etype == ET_SHELL)
478 if (expand_folder(static_cast<ShellDirectory*>(entry)))
479 return S_OK;
480 }
481 }
482 } else { // no tree control
483 if (MainFrame::OpenShellFolders(pida, _hWndFrame))
484 return S_OK;
485
486 /* create new Frame Window
487 if (MainFrame::OpenShellFolders(pida, 0))
488 return S_OK;
489 */
490 }
491 }
492
493 return E_NOTIMPL;
494 }
495
496
497 bool ShellBrowserChild::expand_folder(ShellDirectory* entry)
498 {
499 CONTEXT("ShellBrowserChild::expand_folder()");
500
501 //HTREEITEM hitem_sel = TreeView_GetSelection(_left_hwnd);
502 if (!_last_sel)
503 return false;
504
505 if (!TreeView_Expand(_left_hwnd, _last_sel, TVE_EXPAND))
506 return false;
507
508 for(HTREEITEM hitem=TreeView_GetChild(_left_hwnd,_last_sel); hitem; hitem=TreeView_GetNextSibling(_left_hwnd,hitem)) {
509 if ((ShellDirectory*)TreeView_GetItemData(_left_hwnd,hitem) == entry) {
510 if (TreeView_SelectItem(_left_hwnd, hitem) &&
511 TreeView_Expand(_left_hwnd, hitem, TVE_EXPAND))
512 return true;
513
514 break;
515 }
516 }
517
518 return false;
519 }
520
521
522 String ShellBrowserChild::jump_to_int(LPCTSTR url)
523 {
524 String dir, fname;
525
526 if (!_tcsnicmp(url, TEXT("shell://"), 8)) {
527 if (jump_to_pidl(ShellPath(url+8)))
528 return url;
529 }
530
531 if (SplitFileSysURL(url, dir, fname))
532 if (jump_to_pidl(ShellPath(dir)))
533 return FmtString(TEXT("file://%s"), (LPCTSTR)dir);
534
535 return String();
536 }
537
538
539 bool ShellBrowserChild::jump_to_pidl(LPCITEMIDLIST pidl)
540 {
541
542 /*@todo
543 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
544 -> see FileChildWindow::FileChildWindow()
545 */
546
547 return false;
548 }