merge ROS Shell without integrated explorer part into trunk
[reactos.git] / reactos / subsys / system / explorer / shell / entries.cpp
index f0316fa..ab75f9d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2003 Martin Fuchs
+ * Copyright 2003, 2004 Martin Fuchs
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  //
 
 
-#include "../utility/utility.h"
-#include "../utility/shellclasses.h"
-#include "../globals.h"        // for _prescan_nodes
+#include "precomp.h"
 
-#include "entries.h"
+//#include "entries.h"
 
 
  // allocate and initialise a directory entry
@@ -42,67 +40,116 @@ Entry::Entry(ENTRY_TYPE etype)
        _down = NULL;
        _expanded = false;
        _scanned = false;
+       _bhfi_valid = false;
        _level = 0;
-       _hicon = 0;
+       _icon_id = ICID_UNKNOWN;
+       _display_name = _data.cFileName;
+       _type_name = NULL;
+       _content = NULL;
 }
 
-Entry::Entry(Entry* parent)
- :     _etype(parent->_etype),
-       _up(parent)
+Entry::Entry(Entry* parent, ENTRY_TYPE etype)
+ :     _up(parent),
+       _etype(etype)
 {
        _next = NULL;
        _down = NULL;
        _expanded = false;
        _scanned = false;
+       _bhfi_valid = false;
        _level = 0;
-       _hicon = 0;
+       _icon_id = ICID_UNKNOWN;
+       _display_name = _data.cFileName;
+       _type_name = NULL;
+       _content = NULL;
+}
+
+Entry::Entry(const Entry& other)
+{
+       _next = NULL;
+       _down = NULL;
+       _up = NULL;
+
+       assert(!other._next);
+       assert(!other._down);
+       assert(!other._up);
+
+       _expanded = other._expanded;
+       _scanned = other._scanned;
+       _level = other._level;
+
+       _data = other._data;
+
+       _shell_attribs = other._shell_attribs;
+       _display_name = other._display_name==other._data.cFileName? _data.cFileName: _tcsdup(other._display_name);
+       _type_name = other._type_name? _tcsdup(other._type_name): NULL;
+       _content = other._content? _tcsdup(other._content): NULL;
+
+       _etype = other._etype;
+       _icon_id = other._icon_id;
+
+       _bhfi = other._bhfi;
+       _bhfi_valid = other._bhfi_valid;
 }
 
  // free a directory entry
 Entry::~Entry()
 {
-       if (_hicon && _hicon!=(HICON)-1)
-               DestroyIcon(_hicon);
+       if (_icon_id > ICID_NONE)
+               g_Globals._icon_cache.free_icon(_icon_id);
+
+       if (_display_name != _data.cFileName)
+               free(_display_name);
+
+       if (_type_name)
+               free(_type_name);
+
+       if (_content)
+               free(_content);
 }
 
 
  // read directory tree and expand to the given location
-Entry* Entry::read_tree(const void* path, SORT_ORDER sortOrder)
+Entry* Entry::read_tree(const void* path, SORT_ORDER sortOrder, int scan_flags)
 {
-       HCURSOR old_cursor = SetCursor(LoadCursor(0, IDC_WAIT));
+       CONTEXT("Entry::read_tree()");
 
-       Entry* entry = this;
-       Entry* next_entry = entry;
+       WaitCursor wait;
 
-       for(const void*p=path; p&&next_entry; p=entry->get_next_path_component(p)) {
-               entry = next_entry;
+       Entry* entry = this;
 
-               entry->read_directory(sortOrder);
+       for(const void*p=path; p && entry; ) {
+               entry->smart_scan(sortOrder, scan_flags);
 
                if (entry->_down)
                        entry->_expanded = true;
 
-               next_entry = entry->find_entry(p);
-       }
+               Entry* found = entry->find_entry(p);
+               p = entry->get_next_path_component(p);
 
-       SetCursor(old_cursor);
+               entry = found;
+       }
 
        return entry;
 }
 
 
-void Entry::read_directory(SORT_ORDER sortOrder)
+void Entry::read_directory_base(SORT_ORDER sortOrder, int scan_flags)
 {
+       CONTEXT("Entry::read_directory_base()");
+
         // call into subclass
-       read_directory();
+       read_directory(scan_flags);
 
-       if (g_Globals._prescan_nodes) {
+#ifndef ROSSHELL
+       if (g_Globals._prescan_nodes) { //@todo _prescan_nodes should not be used for reading the start menu.
                for(Entry*entry=_down; entry; entry=entry->_next)
                        if (entry->_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
-                               entry->read_directory();
+                               entry->read_directory(scan_flags);
                                entry->sort_directory(sortOrder);
                        }
        }
+#endif
 
        sort_directory(sortOrder);
 }
@@ -123,13 +170,24 @@ Root::~Root()
  // directories first...
 static int compareType(const WIN32_FIND_DATA* fd1, const WIN32_FIND_DATA* fd2)
 {
-       int dir1 = fd1->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
-       int dir2 = fd2->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
+       int order1 = fd1->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
+       int order2 = fd2->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
+
+       /* Handle "." and ".." as special case and move them at the very first beginning. */
+       if (order1 && order2) {
+               order1 = fd1->cFileName[0]!='.'? 1: fd1->cFileName[1]=='.'? 2: fd1->cFileName[1]=='\0'? 3: 1;
+               order2 = fd2->cFileName[0]!='.'? 1: fd2->cFileName[1]=='.'? 2: fd2->cFileName[1]=='\0'? 3: 1;
+       }
 
-       return dir2==dir1? 0: dir2<dir1? -1: 1;
+       return order2==order1? 0: order2<order1? -1: 1;
 }
 
 
+static int compareNothing(const void* arg1, const void* arg2)
+{
+       return -1;
+}
+
 static int compareName(const void* arg1, const void* arg2)
 {
        const WIN32_FIND_DATA* fd1 = &(*(Entry**)arg1)->_data;
@@ -210,6 +268,7 @@ static int compareDate(const void* arg1, const void* arg2)
 
 
 static int (*sortFunctions[])(const void* arg1, const void* arg2) = {
+       compareNothing, // SORT_NONE
        compareName,    // SORT_NAME
        compareExt,     // SORT_EXT
        compareSize,    // SORT_SIZE
@@ -219,40 +278,113 @@ static int (*sortFunctions[])(const void* arg1, const void* arg2) = {
 
 void Entry::sort_directory(SORT_ORDER sortOrder)
 {
-       Entry* entry = _down;
-       Entry** array, **p;
-       int len;
+       if (sortOrder != SORT_NONE) {
+               Entry* entry = _down;
+               Entry** array, **p;
+               int len;
 
-       len = 0;
-       for(entry=_down; entry; entry=entry->_next)
-               ++len;
+               len = 0;
+               for(entry=_down; entry; entry=entry->_next)
+                       ++len;
 
-       if (len) {
-               array = (Entry**) alloca(len*sizeof(Entry*));
+               if (len) {
+                       array = (Entry**) alloca(len*sizeof(Entry*));
 
-               p = array;
-               for(entry=_down; entry; entry=entry->_next)
-                       *p++ = entry;
+                       p = array;
+                       for(entry=_down; entry; entry=entry->_next)
+                               *p++ = entry;
 
-                // call qsort with the appropriate compare function
-               qsort(array, len, sizeof(array[0]), sortFunctions[sortOrder]);
+                        // call qsort with the appropriate compare function
+                       qsort(array, len, sizeof(array[0]), sortFunctions[sortOrder]);
 
-               _down = array[0];
+                       _down = array[0];
 
-               for(p=array; --len; p++)
-                       p[0]->_next = p[1];
+                       for(p=array; --len; p++)
+                               (*p)->_next = p[1];
 
-               (*p)->_next = 0;
+                       (*p)->_next = 0;
+               }
        }
 }
 
 
-void Entry::smart_scan()
+void Entry::smart_scan(SORT_ORDER sortOrder, int scan_flags)
 {
+       CONTEXT("Entry::smart_scan()");
+
        if (!_scanned) {
                free_subentries();
-               read_directory(SORT_NAME);      // we could use IShellFolder2::GetDefaultColumn to determine sort order
+               read_directory_base(sortOrder, scan_flags);     ///@todo We could use IShellFolder2::GetDefaultColumn to determine sort order.
+       }
+}
+
+
+void Entry::extract_icon()
+{
+       TCHAR path[MAX_PATH];
+
+       ICON_ID icon_id = ICID_NONE;
+
+       if (get_path(path) && _tcsncmp(path,TEXT("::{"),3))
+               icon_id = g_Globals._icon_cache.extract(path);
+
+       if (icon_id == ICID_NONE) {
+               IExtractIcon* pExtract;
+               if (SUCCEEDED(GetUIObjectOf(0, IID_IExtractIcon, (LPVOID*)&pExtract))) {
+                       unsigned flags;
+                       int idx;
+
+                       if (SUCCEEDED(pExtract->GetIconLocation(GIL_FORSHELL, path, MAX_PATH, &idx, &flags))) {
+                               if (flags & GIL_NOTFILENAME)
+                                       icon_id = g_Globals._icon_cache.extract(pExtract, path, idx);
+                               else {
+                                       if (idx == -1)
+                                               idx = 0;        // special case for some control panel applications ("System")
+
+                                       icon_id = g_Globals._icon_cache.extract(path, idx);
+                               }
+
+                       /* using create_absolute_pidl() [see below] results in more correct icons for some control panel applets ("NVidia").
+                               if (icon_id == ICID_NONE) {
+                                       SHFILEINFO sfi;
+
+                                       if (SHGetFileInfo(path, 0, &sfi, sizeof(sfi), SHGFI_ICON|SHGFI_SMALLICON))
+                                               icon_id = g_Globals._icon_cache.add(sfi.hIcon)._id;
+                               } */
+                       /*
+                               if (icon_id == ICID_NONE) {
+                                       LPBYTE b = (LPBYTE) alloca(0x10000);
+                                       SHFILEINFO sfi;
+
+                                       FILE* file = fopen(path, "rb");
+                                       if (file) {
+                                               int l = fread(b, 1, 0x10000, file);
+                                               fclose(file);
+
+                                               if (l)
+                                                       icon_id = g_Globals._icon_cache.add(CreateIconFromResourceEx(b, l, TRUE, 0x00030000, 16, 16, LR_DEFAULTCOLOR));
+                                       }
+                               } */
+                       }
+               }
+
+               if (icon_id == ICID_NONE) {
+                       SHFILEINFO sfi;
+
+                       const ShellPath& pidl_abs = create_absolute_pidl();
+                       LPCITEMIDLIST pidl = pidl_abs;
+
+                       HIMAGELIST himlSys = (HIMAGELIST) SHGetFileInfo((LPCTSTR)pidl, 0, &sfi, sizeof(sfi), SHGFI_SYSICONINDEX|SHGFI_PIDL|SHGFI_SMALLICON);
+                       if (himlSys)
+                               icon_id = g_Globals._icon_cache.add(sfi.iIcon);
+                       /*
+                       if (SHGetFileInfo((LPCTSTR)pidl, 0, &sfi, sizeof(sfi), SHGFI_PIDL|SHGFI_ICON|SHGFI_SMALLICON))
+                               icon_id = g_Globals._icon_cache.add(sfi.hIcon)._id;
+                       */
+               }
        }
+
+       _icon_id = icon_id;
 }
 
 
@@ -260,13 +392,120 @@ BOOL Entry::launch_entry(HWND hwnd, UINT nCmdShow)
 {
        TCHAR cmd[MAX_PATH];
 
-       get_path(cmd);
+       if (!get_path(cmd))
+               return FALSE;
+
+        // add path to the recent file list
+       SHAddToRecentDocs(SHARD_PATH, cmd);
 
          // start program, open document...
        return launch_file(hwnd, cmd, nCmdShow);
 }
 
 
+HRESULT Entry::do_context_menu(HWND hwnd, const POINT& pos, CtxMenuInterfaces& cm_ifs)
+{
+       ShellPath shell_path = create_absolute_pidl();
+       LPCITEMIDLIST pidl_abs = shell_path;
+
+       if (!pidl_abs)
+               return S_FALSE; // no action for registry entries, etc.
+
+       static DynamicFct<HRESULT(WINAPI*)(LPCITEMIDLIST, REFIID, LPVOID*, LPCITEMIDLIST*)> SHBindToParent(TEXT("SHELL32"), "SHBindToParent");
+
+       if (SHBindToParent) {
+               IShellFolder* parentFolder;
+               LPCITEMIDLIST pidlLast;
+
+                // get and use the parent folder to display correct context menu in all cases -> correct "Properties" dialog for directories, ...
+               HRESULT hr = (*SHBindToParent)(pidl_abs, IID_IShellFolder, (LPVOID*)&parentFolder, &pidlLast);
+
+               if (SUCCEEDED(hr)) {
+                       hr = ShellFolderContextMenu(parentFolder, hwnd, 1, &pidlLast, pos.x, pos.y, cm_ifs);
+
+                       parentFolder->Release();
+               }
+
+               return hr;
+       } else {
+               /**@todo use parent folder instead of desktop folder
+               Entry* dir = _up;
+
+               ShellPath parent_path;
+
+               if (dir)
+                       parent_path = dir->create_absolute_pidl();
+               else
+                       parent_path = DesktopFolderPath();
+
+               ShellPath shell_path = create_relative_pidl(parent_path);
+               LPCITEMIDLIST pidl = shell_path;
+
+               ShellFolder parent_folder = parent_path;
+               return ShellFolderContextMenu(parent_folder, hwnd, 1, &pidl, pos.x, pos.y);
+               */
+               return ShellFolderContextMenu(GetDesktopFolder(), hwnd, 1, &pidl_abs, pos.x, pos.y, cm_ifs);
+       }
+}
+
+
+HRESULT Entry::GetUIObjectOf(HWND hWnd, REFIID riid, LPVOID* ppvOut)
+{
+       TCHAR path[MAX_PATH];
+/*
+       if (!get_path(path))
+               return E_FAIL;
+
+       ShellPath shell_path(path);
+
+       IShellFolder* pFolder;
+       LPCITEMIDLIST pidl_last = NULL;
+
+       static DynamicFct<HRESULT(WINAPI*)(LPCITEMIDLIST, REFIID, LPVOID*, LPCITEMIDLIST*)> SHBindToParent(TEXT("SHELL32"), "SHBindToParent");
+
+       if (!SHBindToParent)
+               return E_NOTIMPL;
+
+       HRESULT hr = (*SHBindToParent)(shell_path, IID_IShellFolder, (LPVOID*)&pFolder, &pidl_last);
+       if (FAILED(hr))
+               return hr;
+
+       ShellFolder shell_folder(pFolder);
+
+       shell_folder->Release();
+
+       return shell_folder->GetUIObjectOf(hWnd, 1, &pidl_last, riid, NULL, ppvOut);
+*/
+       if (!_up)
+               return E_INVALIDARG;
+
+       if (!_up->get_path(path))
+               return E_FAIL;
+
+       ShellPath shell_path(path);
+       ShellFolder shell_folder(shell_path);
+
+#ifdef UNICODE
+       LPWSTR wname = _data.cFileName;
+#else
+       WCHAR wname[MAX_PATH];
+       MultiByteToWideChar(CP_ACP, 0, _data.cFileName, -1, wname, MAX_PATH);
+#endif
+
+       LPITEMIDLIST pidl_last = NULL;
+       HRESULT hr = shell_folder->ParseDisplayName(hWnd, NULL, wname, NULL, &pidl_last, NULL);
+
+       if (FAILED(hr))
+               return hr;
+
+       hr = shell_folder->GetUIObjectOf(hWnd, 1, (LPCITEMIDLIST*)&pidl_last, riid, NULL, ppvOut);
+
+       ShellMalloc()->Free((void*)pidl_last);
+
+       return hr;
+}
+
+
  // recursively free all child entries
 void Entry::free_subentries()
 {
@@ -284,3 +523,28 @@ void Entry::free_subentries()
                } while(next);
        }
 }
+
+
+Entry* Root::read_tree(LPCTSTR path, int scan_flags)
+{
+       Entry* entry;
+
+       if (path && *path)
+               entry = _entry->read_tree(path, _sort_order);
+       else {
+               entry = _entry->read_tree(NULL, _sort_order);
+
+               _entry->smart_scan();
+
+               if (_entry->_down)
+                       _entry->_expanded = true;
+       }
+
+       return entry;
+}
+
+
+Entry* Root::read_tree(LPCITEMIDLIST pidl, int scan_flags)
+{
+       return _entry->read_tree(pidl, _sort_order);
+}