/* * Copyright 2003, 2004, 2005, 2006 Martin Fuchs * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // // Explorer clone // // shellbrowser.cpp // // Martin Fuchs, 23.07.2003 // #include #include "../resource.h" // work around GCC's wide string constant bug #ifdef __GNUC__ const LPCTSTR C_DRIVE = C_DRIVE_STR; #endif ShellBrowser::ShellBrowser(HWND hwnd, HWND hwndFrame, HWND left_hwnd, WindowHandle& right_hwnd, ShellPathInfo& create_info, BrowserCallback* cb, CtxMenuInterfaces& cm_ifs) #ifndef __MINGW32__ // IShellFolderViewCB missing in MinGW (as of 25.09.2005) : super(IID_IShellFolderViewCB), #else : #endif _hwnd(hwnd), _hwndFrame(hwndFrame), _left_hwnd(left_hwnd), _right_hwnd(right_hwnd), _create_info(create_info), _callback(cb), _cm_ifs(cm_ifs) { _pShellView = NULL; _pDropTarget = NULL; _last_sel = 0; _cur_dir = NULL; _himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_MASK|ILC_COLOR24, 2, 0); ImageList_SetBkColor(_himl, GetSysColor(COLOR_WINDOW)); } ShellBrowser::~ShellBrowser() { (void)TreeView_SetImageList(_left_hwnd, _himl_old, TVSIL_NORMAL); ImageList_Destroy(_himl); if (_pShellView) _pShellView->Release(); if (_pDropTarget) { _pDropTarget->Release(); _pDropTarget = NULL; } if (_right_hwnd) { DestroyWindow(_right_hwnd); _right_hwnd = 0; } } void ShellBrowser::Init() { CONTEXT("ShellBrowser::Init()"); const String& root_name = GetDesktopFolder().get_name(_create_info._root_shell_path, SHGDN_FORADDRESSBAR); _root._drive_type = DRIVE_UNKNOWN; lstrcpy(_root._volname, root_name); _root._fs_flags = 0; lstrcpy(_root._fs, TEXT("Desktop")); _root._entry = new ShellDirectory(GetDesktopFolder(), _create_info._root_shell_path, _hwnd); _root._entry->read_directory(SCAN_DONT_ACCESS|SCAN_NO_FILESYSTEM); // avoid to handle desktop root folder as file system directory if (_left_hwnd) { InitializeTree(); InitDragDrop(); } jump_to(_create_info._shell_path); /* already filled by ShellDirectory constructor lstrcpy(_root._entry->_data.cFileName, TEXT("Desktop")); */ } void ShellBrowser::jump_to(LPCITEMIDLIST pidl) { Entry* entry = NULL; if (!_cur_dir) _cur_dir = static_cast(_root._entry); //LOG(FmtString(TEXT("ShellBrowser::jump_to(): pidl=%s"), (LPCTSTR)FileSysShellPath(pidl))); // We could call read_tree() here to iterate through the hierarchy and open all folders // from _create_info._root_shell_path (_cur_dir) to _create_info._shell_path (pidl). // To make it easier we just use ILFindChild() instead. if (_cur_dir) { static DynamicFct ILFindChild(TEXT("SHELL32"), 24); if (ILFindChild) { for(;;) { LPCITEMIDLIST child_pidl = (*ILFindChild)(_cur_dir->create_absolute_pidl(), pidl); if (!child_pidl || !child_pidl->mkid.cb) break; _cur_dir->smart_scan(SORT_NAME, SCAN_DONT_ACCESS); entry = _cur_dir->find_entry(child_pidl); if (!entry) break; _cur_dir = static_cast(entry); _callback->entry_selected(entry); } } else { _cur_dir->smart_scan(SORT_NAME, SCAN_DONT_ACCESS); entry = _cur_dir->find_entry(pidl); // This is not correct in the common case, but works on the desktop level. if (entry) { _cur_dir = static_cast(entry); _callback->entry_selected(entry); } } } // If not already called, now directly call UpdateFolderView() using pidl if (!entry) UpdateFolderView(ShellFolder(pidl)); } void ShellBrowser::InitializeTree() { CONTEXT("ShellBrowserChild::InitializeTree()"); _himl_old = TreeView_SetImageList(_left_hwnd, _himl, TVSIL_NORMAL); TreeView_SetScrollTime(_left_hwnd, 100); TV_INSERTSTRUCT tvInsert; TV_ITEM& tvItem = tvInsert.item; tvInsert.hParent = 0; tvInsert.hInsertAfter = TVI_LAST; tvItem.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_CHILDREN; tvItem.lParam = (LPARAM)_root._entry; tvItem.pszText = _root._volname; //LPSTR_TEXTCALLBACK; tvItem.iImage = tvItem.iSelectedImage = I_IMAGECALLBACK; tvItem.cChildren = 1; HTREEITEM hItem = TreeView_InsertItem(_left_hwnd, &tvInsert); TreeView_SelectItem(_left_hwnd, hItem); TreeView_Expand(_left_hwnd, hItem, TVE_EXPAND); } bool ShellBrowser::InitDragDrop() { CONTEXT("ShellBrowser::InitDragDrop()"); _pDropTarget = new TreeDropTarget(_left_hwnd); if (!_pDropTarget) return false; _pDropTarget->AddRef(); if (FAILED(RegisterDragDrop(_left_hwnd, _pDropTarget))) {//calls addref _pDropTarget->Release(); // free TreeDropTarget _pDropTarget = NULL; return false; } else _pDropTarget->Release(); FORMATETC ftetc; ftetc.dwAspect = DVASPECT_CONTENT; ftetc.lindex = -1; ftetc.tymed = TYMED_HGLOBAL; ftetc.cfFormat = CF_HDROP; _pDropTarget->AddSuportedFormat(ftetc); return true; } void ShellBrowser::OnTreeGetDispInfo(int idCtrl, LPNMHDR pnmh) { CONTEXT("ShellBrowser::OnTreeGetDispInfo()"); LPNMTVDISPINFO lpdi = (LPNMTVDISPINFO)pnmh; ShellEntry* entry = (ShellEntry*)lpdi->item.lParam; if (entry) { if (lpdi->item.mask & TVIF_TEXT) lpdi->item.pszText = entry->_display_name; if (lpdi->item.mask & (TVIF_IMAGE|TVIF_SELECTEDIMAGE)) { if (lpdi->item.mask & TVIF_IMAGE) lpdi->item.iImage = get_image_idx( entry->safe_extract_icon((ICONCACHE_FLAGS)(ICF_HICON|ICF_OVERLAYS))); if (lpdi->item.mask & TVIF_SELECTEDIMAGE) lpdi->item.iSelectedImage = get_image_idx( entry->safe_extract_icon((ICONCACHE_FLAGS)(ICF_HICON|ICF_OVERLAYS|ICF_OPEN))); } } } int ShellBrowser::get_image_idx(int icon_id) { if (icon_id != ICID_NONE) { map::const_iterator found = _image_map.find(icon_id); if (found != _image_map.end()) return found->second; int idx = ImageList_AddIcon(_himl, g_Globals._icon_cache.get_icon(icon_id).get_hicon()); _image_map[icon_id] = idx; return idx; } else return -1; } void ShellBrowser::invalidate_cache() { (void)TreeView_SetImageList(_left_hwnd, _himl_old, TVSIL_NORMAL); ImageList_Destroy(_himl); _himl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_MASK|ILC_COLOR24, 2, 0); ImageList_SetBkColor(_himl, GetSysColor(COLOR_WINDOW)); _himl_old = TreeView_SetImageList(_left_hwnd, _himl, TVSIL_NORMAL); for(map::const_iterator it=_image_map.begin(); it!=_image_map.end(); ++it) g_Globals._icon_cache.free_icon(it->first); _image_map.clear(); } void ShellBrowser::OnTreeItemExpanding(int idCtrl, LPNMTREEVIEW pnmtv) { CONTEXT("ShellBrowser::OnTreeItemExpanding()"); if (pnmtv->action == TVE_COLLAPSE) TreeView_Expand(_left_hwnd, pnmtv->itemNew.hItem, TVE_COLLAPSE|TVE_COLLAPSERESET); else if (pnmtv->action == TVE_EXPAND) { ShellDirectory* entry = (ShellDirectory*)TreeView_GetItemData(_left_hwnd, pnmtv->itemNew.hItem); if (entry) if (!InsertSubitems(pnmtv->itemNew.hItem, entry, entry->_folder)) { entry->_shell_attribs &= ~SFGAO_HASSUBFOLDER; // remove subitem "+" TV_ITEM tvItem; tvItem.mask = TVIF_CHILDREN; tvItem.hItem = pnmtv->itemNew.hItem; tvItem.cChildren = 0; TreeView_SetItem(_left_hwnd, &tvItem); } } } int ShellBrowser::InsertSubitems(HTREEITEM hParentItem, Entry* entry, IShellFolder* pParentFolder) { CONTEXT("ShellBrowser::InsertSubitems()"); WaitCursor wait; int cnt = 0; SendMessage(_left_hwnd, WM_SETREDRAW, FALSE, 0); try { entry->smart_scan(SORT_NAME, SCAN_DONT_ACCESS); } catch(COMException& e) { HandleException(e, g_Globals._hMainWnd); } // remove old children items HTREEITEM hchild, hnext; hnext = TreeView_GetChild(_left_hwnd, hParentItem); while((hchild=hnext) != 0) { hnext = TreeView_GetNextSibling(_left_hwnd, hchild); TreeView_DeleteItem(_left_hwnd, hchild); } TV_ITEM tvItem; TV_INSERTSTRUCT tvInsert; for(entry=entry->_down; entry; entry=entry->_next) { #ifndef _LEFT_FILES if (entry->_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) #endif { // ignore hidden directories if (entry->_data.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) continue; // ignore directory entries "." and ".." if (entry->_data.cFileName[0]==TEXT('.') && (entry->_data.cFileName[1]==TEXT('\0') || (entry->_data.cFileName[1]==TEXT('.') && entry->_data.cFileName[2]==TEXT('\0')))) continue; ZeroMemory(&tvItem, sizeof(tvItem)); tvItem.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_CHILDREN; tvItem.pszText = LPSTR_TEXTCALLBACK; tvItem.iImage = tvItem.iSelectedImage = I_IMAGECALLBACK; tvItem.lParam = (LPARAM)entry; tvItem.cChildren = entry->_shell_attribs & SFGAO_HASSUBFOLDER? 1: 0; if (entry->_shell_attribs & SFGAO_SHARE) { tvItem.mask |= TVIF_STATE; tvItem.stateMask |= TVIS_OVERLAYMASK; tvItem.state |= INDEXTOOVERLAYMASK(1); } tvInsert.item = tvItem; tvInsert.hInsertAfter = TVI_LAST; tvInsert.hParent = hParentItem; (void)TreeView_InsertItem(_left_hwnd, &tvInsert); ++cnt; } } SendMessage(_left_hwnd, WM_SETREDRAW, TRUE, 0); return cnt; } void ShellBrowser::OnTreeItemSelected(int idCtrl, LPNMTREEVIEW pnmtv) { CONTEXT("ShellBrowser::OnTreeItemSelected()"); Entry* entry = (Entry*)pnmtv->itemNew.lParam; _last_sel = pnmtv->itemNew.hItem; if (entry) _callback->entry_selected(entry); } void ShellBrowser::OnTreeItemRClick(int idCtrl, LPNMHDR pnmh) { CONTEXT("ShellBrowser::OnTreeItemRClick()"); TVHITTESTINFO tvhti; GetCursorPos(&tvhti.pt); ScreenToClient(_left_hwnd, &tvhti.pt); tvhti.flags = LVHT_NOWHERE; (void)TreeView_HitTest(_left_hwnd, &tvhti); if (TVHT_ONITEM & tvhti.flags) { LPARAM itemData = TreeView_GetItemData(_left_hwnd, tvhti.hItem); if (itemData) { Entry* entry = (Entry*)itemData; ClientToScreen(_left_hwnd, &tvhti.pt); HRESULT hr = entry->do_context_menu(_hwnd, tvhti.pt, _cm_ifs); if (SUCCEEDED(hr)) refresh(); else CHECKERROR(hr); } } } void ShellBrowser::UpdateFolderView(IShellFolder* folder) { CONTEXT("ShellBrowser::UpdateFolderView()"); FOLDERSETTINGS fs; IShellView* pLastShellView = _pShellView; _folder = folder; if (pLastShellView) pLastShellView->GetCurrentInfo(&fs); else { fs.ViewMode = _create_info._open_mode&OWM_DETAILS? FVM_DETAILS: FVM_ICON; fs.fFlags = FWF_NOCLIENTEDGE|FWF_BESTFITWINDOW; } #ifndef __MINGW32__ // IShellFolderViewCB missing in MinGW (as of 25.09.2005) SFV_CREATE sfv_create; sfv_create.cbSize = sizeof(SFV_CREATE); sfv_create.pshf = folder; sfv_create.psvOuter = NULL; sfv_create.psfvcb = this; HRESULT hr = SHCreateShellFolderView(&sfv_create, &_pShellView); #else HRESULT hr = folder->CreateViewObject(_hwnd, IID_IShellView, (void**)&_pShellView); #endif if (FAILED(hr)) { _pShellView = NULL; return; } RECT rect = {CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT}; hr = _pShellView->CreateViewWindow(pLastShellView, &fs, static_cast(this), &rect, &_right_hwnd/*&m_hWndListView*/); if (pLastShellView) { pLastShellView->GetCurrentInfo(&fs); pLastShellView->UIActivate(SVUIA_DEACTIVATE); pLastShellView->DestroyViewWindow(); pLastShellView->Release(); } _pShellView->UIActivate(SVUIA_ACTIVATE_NOFOCUS); } #ifndef __MINGW32__ // IShellFolderViewCB missing in MinGW (as of 25.09.2005) /// shell view callback HRESULT STDMETHODCALLTYPE ShellBrowser::MessageSFVCB(UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == SFVM_INITMENUPOPUP) { ///@todo never reached InsertMenu((HMENU)lParam, 0, MF_BYPOSITION, 12345, TEXT("TEST ENTRY")); return S_OK; } return E_NOTIMPL; } #endif HRESULT ShellBrowser::OnDefaultCommand(LPIDA pida) { CONTEXT("ShellBrowser::OnDefaultCommand()"); if (pida->cidl >= 1) { if (_left_hwnd) { // explorer mode if (_last_sel) { ShellDirectory* parent = (ShellDirectory*)TreeView_GetItemData(_left_hwnd, _last_sel); if (parent) { try { parent->smart_scan(SORT_NAME, SCAN_DONT_ACCESS); } catch(COMException& e) { return e.Error(); } UINT firstOffset = pida->aoffset[1]; LPITEMIDLIST pidl = (LPITEMIDLIST)((LPBYTE)pida+firstOffset); Entry* entry = parent->find_entry(pidl); if (entry && (entry->_data.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)) if (entry->_etype == ET_SHELL) if (_last_sel && select_entry(_last_sel, entry)) return S_OK; ///@todo look for hidden or new subfolders and refresh/add new entry instead of opening a new window return E_NOTIMPL; } } } else { // no tree control if (MainFrameBase::OpenShellFolders(pida, _hwndFrame)) return S_OK; /* create new Frame Window if (MainFrame::OpenShellFolders(pida, 0)) return S_OK; */ } } return E_NOTIMPL; } HTREEITEM ShellBrowser::select_entry(HTREEITEM hitem, Entry* entry, bool expand) { CONTEXT("ShellBrowser::select_entry()"); if (expand && !TreeView_Expand(_left_hwnd, hitem, TVE_EXPAND)) return 0; for(hitem=TreeView_GetChild(_left_hwnd,hitem); hitem; hitem=TreeView_GetNextSibling(_left_hwnd,hitem)) { if ((Entry*)TreeView_GetItemData(_left_hwnd,hitem) == entry) { if (TreeView_SelectItem(_left_hwnd, hitem)) { if (expand) TreeView_Expand(_left_hwnd, hitem, TVE_EXPAND); return hitem; } break; } } return 0; } bool ShellBrowser::jump_to_pidl(LPCITEMIDLIST pidl) { if (!_root._entry) return false; // iterate through the hierarchy and open all folders to reach pidl WaitCursor wait; HTREEITEM hitem = TreeView_GetRoot(_left_hwnd); Entry* entry = _root._entry; for(const void*p=pidl;;) { if (!p) return true; if (!entry || !hitem) break; entry->smart_scan(SORT_NAME, SCAN_DONT_ACCESS); Entry* found = entry->find_entry(p); p = entry->get_next_path_component(p); if (found) hitem = select_entry(hitem, found); entry = found; } return false; } bool ShellBrowser::select_folder(Entry* entry, bool expand) { CONTEXT("ShellBrowser::expand_folder()"); if (!_last_sel) return false; if (!TreeView_Expand(_left_hwnd, _last_sel, TVE_EXPAND)) return false; for(HTREEITEM hitem=TreeView_GetChild(_left_hwnd,_last_sel); hitem; hitem=TreeView_GetNextSibling(_left_hwnd,hitem)) { if ((ShellDirectory*)TreeView_GetItemData(_left_hwnd,hitem) == entry) { if (TreeView_SelectItem(_left_hwnd, hitem)) { if (expand) if (!TreeView_Expand(_left_hwnd, hitem, TVE_EXPAND)) return false; return true; } break; } } return false; } void ShellBrowser::refresh() { ///@todo } #ifndef _NO_MDI MDIShellBrowserChild::MDIShellBrowserChild(HWND hwnd, const ShellChildWndInfo& info) : super(hwnd, info), _create_info(info), _shellpath_info(info) //@@ copies info -> no reference to _create_info ! { /**todo Conversion of shell path into path string -> store into URL history const String& path = GetDesktopFolder().get_name(info._shell_path, SHGDN_FORADDRESSBAR); const String& parsingpath = GetDesktopFolder().get_name(info._shell_path, SHGDN_FORPARSING); // store path into history if (info._path && *info._path) _url_history.push(info._path); */ } MDIShellBrowserChild* MDIShellBrowserChild::create(const ShellChildWndInfo& info) { ChildWindow* child = ChildWindow::create(info, info._pos.rcNormalPosition, WINDOW_CREATOR_INFO(MDIShellBrowserChild,ShellChildWndInfo), CLASSNAME_CHILDWND, NULL, info._pos.showCmd==SW_SHOWMAXIMIZED? WS_MAXIMIZE: 0); return static_cast(child); } LRESULT MDIShellBrowserChild::Init(LPCREATESTRUCT pcs) { CONTEXT("MDIShellBrowserChild::Init()"); if (super::Init(pcs)) return 1; update_shell_browser(); return 0; } LRESULT MDIShellBrowserChild::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam) { switch(nmsg) { case PM_DISPATCH_COMMAND: { switch(LOWORD(wparam)) { case ID_WINDOW_NEW: {CONTEXT("MDIShellBrowserChild PM_DISPATCH_COMMAND ID_WINDOW_NEW"); MDIShellBrowserChild::create(_create_info); break;} case ID_REFRESH: ///@todo refresh shell child _shellBrowser->invalidate_cache(); break; case ID_VIEW_SDI: MainFrameBase::Create(ExplorerCmd(_url, false)); break; default: return super::WndProc(nmsg, wparam, lparam); } return TRUE;} default: return super::WndProc(nmsg, wparam, lparam); } return 0; } void MDIShellBrowserChild::update_shell_browser() { int split_pos = DEFAULT_SPLIT_POS; if (_shellBrowser.get()) { split_pos = _split_pos; delete _shellBrowser.release(); } // create explorer treeview if (_create_info._open_mode & OWM_EXPLORE) { if (!_left_hwnd) { ClientRect rect(_hwnd); _left_hwnd = CreateWindowEx(0, WC_TREEVIEW, NULL, WS_CHILD|WS_TABSTOP|WS_VISIBLE|TVS_HASLINES|TVS_LINESATROOT|TVS_HASBUTTONS|TVS_SHOWSELALWAYS,//|TVS_NOTOOLTIPS 0, rect.top, split_pos-SPLIT_WIDTH/2, rect.bottom-rect.top, _hwnd, (HMENU)IDC_FILETREE, g_Globals._hInstance, 0); } } else { if (_left_hwnd) { DestroyWindow(_left_hwnd); _left_hwnd = 0; } } _shellBrowser = auto_ptr(new ShellBrowser(_hwnd, _hwndFrame, _left_hwnd, _right_hwnd, _shellpath_info, this, _cm_ifs)); _shellBrowser->Init(); } String MDIShellBrowserChild::jump_to_int(LPCTSTR url) { String dir, fname; if (!_tcsnicmp(url, TEXT("shell://"), 8)) { if (_shellBrowser->jump_to_pidl(ShellPath(url+8))) return url; } if (SplitFileSysURL(url, dir, fname)) { ///@todo use fname if (_shellBrowser->jump_to_pidl(ShellPath(dir))) return FmtString(TEXT("file://%s"), (LPCTSTR)dir); } return String(); } void MDIShellBrowserChild::entry_selected(Entry* entry) { if (_left_hwnd) _shellBrowser->select_folder(entry, false); _shellBrowser->UpdateFolderView(entry->get_shell_folder()); // set size of new created shell view windows ClientRect rt(_hwnd); resize_children(rt.right, rt.bottom); // set new URL TCHAR path[MAX_PATH]; if (entry->get_path(path, COUNTOF(path))) { String url; if (path[0] == ':') url.printf(TEXT("shell://%s"), path); else url.printf(TEXT("file://%s"), path); set_url(url); } } #endif