diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:15:16 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:15:16 +0200 |
commit | bd6336c629841c6db3a6ca53a936d629d34db53b (patch) | |
tree | 3721ef997864108df175ce677a8a7d4342a6f1d2 /library/icon_buffer.cpp | |
parent | 4.0 (diff) | |
download | FreeFileSync-bd6336c629841c6db3a6ca53a936d629d34db53b.tar.gz FreeFileSync-bd6336c629841c6db3a6ca53a936d629d34db53b.tar.bz2 FreeFileSync-bd6336c629841c6db3a6ca53a936d629d34db53b.zip |
4.1
Diffstat (limited to 'library/icon_buffer.cpp')
-rw-r--r-- | library/icon_buffer.cpp | 613 |
1 files changed, 0 insertions, 613 deletions
diff --git a/library/icon_buffer.cpp b/library/icon_buffer.cpp deleted file mode 100644 index b8ee66cd..00000000 --- a/library/icon_buffer.cpp +++ /dev/null @@ -1,613 +0,0 @@ -// ************************************************************************** -// * This file is part of the FreeFileSync project. It is distributed under * -// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * -// ************************************************************************** - -#include "icon_buffer.h" -#include <queue> -#include <set> -#include "../shared/boost_thread_wrap.h" //include <boost/thread.hpp> -#include "../shared/loki/ScopeGuard.h" -#include <boost/thread/once.hpp> - -#ifdef FFS_WIN -#include <wx/msw/wrapwin.h> //includes "windows.h" -#include "../shared/dll_loader.h" -#include "../shared/Thumbnail/thumbnail.h" - -#elif defined FFS_LINUX -#include <giomm/file.h> -#include <gtkmm/icontheme.h> -#include <gtkmm/main.h> -#endif - -using namespace zen; - - -const size_t BUFFER_SIZE_MAX = 800; //maximum number of icons to buffer - - -int zen::IconBuffer::cvrtSize(IconSize sz) //get size in pixel -{ - switch (sz) - { - case SIZE_SMALL: -#ifdef FFS_WIN - return 16; -#elif defined FFS_LINUX - return 24; -#endif - case SIZE_MEDIUM: - return 48; - case SIZE_LARGE: - return 128; - } - assert(false); - return 0; -} - - -class IconHolder //handle HICON/GdkPixbuf ownership WITHOUT ref-counting to allow thread-safe usage (in contrast to wxIcon) -{ -public: -#ifdef FFS_WIN - typedef HICON HandleType; -#elif defined FFS_LINUX - typedef GdkPixbuf* HandleType; -#endif - - IconHolder(HandleType handle = 0) : handle_(handle) {} //take ownership! - - //icon holder has value semantics! - IconHolder(const IconHolder& other) : handle_(other.handle_ == NULL ? NULL : -#ifdef FFS_WIN - ::CopyIcon(other.handle_) -#elif defined FFS_LINUX - ::gdk_pixbuf_copy(other.handle_) //create new Pix buf with reference count 1 or return 0 on error -#endif - ) {} - - IconHolder& operator=(const IconHolder& other) - { - IconHolder(other).swap(*this); - return *this; - } - - ~IconHolder() - { - if (handle_ != NULL) -#ifdef FFS_WIN - ::DestroyIcon(handle_); -#elif defined FFS_LINUX - ::g_object_unref(handle_); -#endif - } - - void swap(IconHolder& other) { std::swap(handle_, other.handle_); } //throw() - - wxIcon toWxIcon(int expectedSize) const //copy HandleType, caller needs to take ownership! - { - if (handle_ == NULL) - return wxNullIcon; - - IconHolder clone(*this); - - wxIcon newIcon; //attention: wxIcon uses reference counting! -#ifdef FFS_WIN - newIcon.SetHICON(clone.handle_); - - { //this block costs ~0.04 ms - ICONINFO icoInfo = {}; - if (::GetIconInfo(clone.handle_, &icoInfo)) - { - ::DeleteObject(icoInfo.hbmMask); //nice potential for a GDI leak! - LOKI_ON_BLOCK_EXIT2(::DeleteObject(icoInfo.hbmColor)); // - - BITMAP bmpInfo = {}; - if (::GetObject(icoInfo.hbmColor, //__in HGDIOBJ hgdiobj, - sizeof(BITMAP), //__in int cbBuffer, - &bmpInfo) != 0) // __out LPVOID lpvObject - { - const int maxExtent = std::max(bmpInfo.bmWidth, bmpInfo.bmHeight); - if (maxExtent > expectedSize) - { - bmpInfo.bmWidth = bmpInfo.bmWidth * expectedSize / maxExtent; //scale those Vista jumbo 256x256 icons down! - bmpInfo.bmHeight = bmpInfo.bmHeight * expectedSize / maxExtent; // - } - newIcon.SetSize(bmpInfo.bmWidth, bmpInfo.bmHeight); //wxIcon is stretched to this size - } - } - } - //no stretching for now - //newIcon.SetSize(defaultSize, defaultSize); //icon is stretched to this size if referenced HICON differs - -#elif defined FFS_LINUX // - newIcon.SetPixbuf(clone.handle_); // transfer ownership!! -#endif // - clone.handle_ = NULL; // - return newIcon; - } - -private: - HandleType handle_; - struct ConversionToBool { int dummy; }; - -public: - //use member pointer as implicit conversion to bool (C++ Templates - Vandevoorde/Josuttis; chapter 20) - operator int ConversionToBool::* () const { return handle_ != NULL ? &ConversionToBool::dummy : NULL; } -}; - - -#ifdef FFS_WIN -namespace -{ -Zstring getFileExtension(const Zstring& filename) -{ - const Zstring shortName = afterLast(filename, Zchar('\\')); //warning: using windows file name separator! - - return shortName.find(Zchar('.')) != Zstring::npos ? - filename.AfterLast(Zchar('.')) : - Zstring(); -} - -std::set<Zstring, LessFilename> priceyExtensions; //thread-safe! -boost::once_flag initExtensionsOnce = BOOST_ONCE_INIT; // - - -//test for extension for non-thumbnail icons that physically have to be retrieved from disc -bool isCheapExtension(const Zstring& extension) -{ - boost::call_once(initExtensionsOnce, []() - { - priceyExtensions.insert(L"exe"); - priceyExtensions.insert(L"lnk"); - priceyExtensions.insert(L"ico"); - priceyExtensions.insert(L"ani"); - priceyExtensions.insert(L"cur"); - priceyExtensions.insert(L"url"); - priceyExtensions.insert(L"msc"); - priceyExtensions.insert(L"scr"); - }); - return priceyExtensions.find(extension) == priceyExtensions.end(); -} - - -bool vistaOrLater() -{ - OSVERSIONINFO osvi = {}; - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - - //IFileOperation is supported with Vista and later - if (::GetVersionEx(&osvi)) - return osvi.dwMajorVersion > 5; - //XP has majorVersion == 5, minorVersion == 1 - //Vista has majorVersion == 6, minorVersion == 0 - //version overview: http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx - return false; -} - - -bool wereVistaOrLater = false; -boost::once_flag initVistaFlagOnce = BOOST_ONCE_INIT; - - -int getShilIconType(IconBuffer::IconSize sz) -{ - boost::call_once(initVistaFlagOnce, []() - { - wereVistaOrLater = vistaOrLater(); - }); - - switch (sz) - { - case IconBuffer::SIZE_SMALL: - return SHIL_SMALL; //16x16, but the size can be customized by the user. - case IconBuffer::SIZE_MEDIUM: - return SHIL_EXTRALARGE; //typically 48x48, but the size can be customized by the user. - case IconBuffer::SIZE_LARGE: - return wereVistaOrLater ? SHIL_JUMBO //normally 256x256 pixels -> will be scaled down by IconHolder - : SHIL_EXTRALARGE; //XP doesn't have jumbo icons - } - return SHIL_SMALL; -} - - -util::DllFun<thumb::GetIconByIndexFct> getIconByIndex; -boost::once_flag initGetIconByIndexOnce = BOOST_ONCE_INIT; - - -IconHolder getIconByAttribute(LPCWSTR pszPath, DWORD dwFileAttributes, IconBuffer::IconSize sz) -{ - //NOTE: CoInitializeEx()/CoUninitialize() needs to be called for THIS thread! - SHFILEINFO fileInfo = {}; //initialize hIcon - DWORD_PTR imgList = ::SHGetFileInfo(pszPath, //Windows 7 doesn't like this parameter to be an empty string - dwFileAttributes, - &fileInfo, - sizeof(fileInfo), - SHGFI_SYSICONINDEX | SHGFI_USEFILEATTRIBUTES); - //no need to IUnknown::Release() imgList! - if (!imgList) - return NULL; - - boost::call_once(initGetIconByIndexOnce, []() //thread-safe init - { - getIconByIndex = util::DllFun<thumb::GetIconByIndexFct>(thumb::getDllName(), thumb::getIconByIndexFctName); - }); - return getIconByIndex ? static_cast<HICON>(getIconByIndex(fileInfo.iIcon, getShilIconType(sz))) : NULL; -} - - -IconHolder getAssociatedIconByExt(const Zstring& extension, IconBuffer::IconSize sz) -{ - //no read-access to disk! determine icon by extension - return getIconByAttribute((Zstr("dummy.") + extension).c_str(), FILE_ATTRIBUTE_NORMAL, sz); -} - - -util::DllFun<thumb::GetThumbnailFct> getThumbnailIcon; -boost::once_flag initThumbnailOnce = BOOST_ONCE_INIT; -} -#endif -//################################################################################################################################################ - - -IconHolder getThumbnail(const Zstring& filename, int requestedSize) //return 0 on failure -{ -#ifdef FFS_WIN - using namespace thumb; - - boost::call_once(initThumbnailOnce, []() //note: "getThumbnail" function itself is already thread-safe - { - getThumbnailIcon = util::DllFun<GetThumbnailFct>(getDllName(), getThumbnailFctName); - }); - return getThumbnailIcon ? static_cast< ::HICON>(getThumbnailIcon(filename.c_str(), requestedSize)) : NULL; - -#elif defined FFS_LINUX - //call Gtk::Main::init_gtkmm_internals() on application startup!! - try - { - Glib::RefPtr<Gdk::Pixbuf> iconPixbuf = Gdk::Pixbuf::create_from_file(filename.c_str(), requestedSize, requestedSize); - if (iconPixbuf) - return IconHolder(iconPixbuf->gobj_copy()); //copy and pass icon ownership (may be 0) - } - catch (const Glib::Error&) {} - - return IconHolder(); -#endif -} - - -IconHolder getAssociatedIcon(const Zstring& filename, IconBuffer::IconSize sz) -{ - //1. try to load thumbnails - switch (sz) - { - case IconBuffer::SIZE_SMALL: - break; - case IconBuffer::SIZE_MEDIUM: - case IconBuffer::SIZE_LARGE: - { - IconHolder ico = getThumbnail(filename, IconBuffer::cvrtSize(sz)); - if (ico) - return ico; - //else: fallback to non-thumbnail icon - } - break; - } - - //2. retrieve file icons -#ifdef FFS_WIN - //perf: optimize fallback case for SIZE_MEDIUM and SIZE_LARGE: - const Zstring& extension = getFileExtension(filename); - if (isCheapExtension(extension)) //"pricey" extensions are stored with fullnames and are read from disk, while cheap ones require just the extension - return getAssociatedIconByExt(extension, sz); - //result will not be buffered under extension name, but full filename; this is okay, since we're in SIZE_MEDIUM or SIZE_LARGE context, - //which means the access to get thumbnail failed: thumbnail failure is not dependent from extension in general! - - SHFILEINFO fileInfo = {}; - DWORD_PTR imgList = ::SHGetFileInfo(filename.c_str(), //zen::removeLongPathPrefix(fileName), //::SHGetFileInfo() can't handle \\?\-prefix! - 0, - &fileInfo, - sizeof(fileInfo), - SHGFI_SYSICONINDEX); - //Quote: "The IImageList pointer type, such as that returned in the ppv parameter, can be cast as an HIMAGELIST as - // needed; for example, for use in a list view. Conversely, an HIMAGELIST can be cast as a pointer to an IImageList." - //http://msdn.microsoft.com/en-us/library/windows/desktop/bb762185(v=vs.85).aspx - - if (!imgList) - return NULL; - //imgList->Release(); //empiric study: crash on XP if we release this! Seems we do not own it... -> also no GDI leak on Win7 -> okay - //another comment on http://msdn.microsoft.com/en-us/library/bb762179(v=VS.85).aspx describes exact same behavior on Win7/XP - - boost::call_once(initGetIconByIndexOnce, []() //thread-safe init - { - getIconByIndex = util::DllFun<thumb::GetIconByIndexFct>(thumb::getDllName(), thumb::getIconByIndexFctName); - }); - return getIconByIndex ? static_cast<HICON>(getIconByIndex(fileInfo.iIcon, getShilIconType(sz))) : NULL; - -#elif defined FFS_LINUX - const int requestedSize = IconBuffer::cvrtSize(sz); - //call Gtk::Main::init_gtkmm_internals() on application startup!! - try - { - Glib::RefPtr<Gio::File> fileObj = Gio::File::create_for_path(filename.c_str()); //never fails - Glib::RefPtr<Gio::FileInfo> fileInfo = fileObj->query_info(G_FILE_ATTRIBUTE_STANDARD_ICON); - if (fileInfo) - { - Glib::RefPtr<Gio::Icon> gicon = fileInfo->get_icon(); - if (gicon) - { - Glib::RefPtr<Gtk::IconTheme> iconTheme = Gtk::IconTheme::get_default(); - if (iconTheme) - { - Gtk::IconInfo iconInfo = iconTheme->lookup_icon(gicon, requestedSize, Gtk::ICON_LOOKUP_USE_BUILTIN); //this may fail if icon is not installed on system - if (iconInfo) - { - Glib::RefPtr<Gdk::Pixbuf> iconPixbuf = iconInfo.load_icon(); //render icon into Pixbuf - if (iconPixbuf) - return IconHolder(iconPixbuf->gobj_copy()); //copy and pass icon ownership (may be 0) - } - } - } - } - } - catch (const Glib::Error&) {} - - try //fallback: icon lookup may fail because some icons are currently not present on system - { - Glib::RefPtr<Gtk::IconTheme> iconTheme = Gtk::IconTheme::get_default(); - if (iconTheme) - { - Glib::RefPtr<Gdk::Pixbuf> iconPixbuf = iconTheme->load_icon("misc", requestedSize, Gtk::ICON_LOOKUP_USE_BUILTIN); - if (!iconPixbuf) - iconPixbuf = iconTheme->load_icon("text-x-generic", requestedSize, Gtk::ICON_LOOKUP_USE_BUILTIN); - if (iconPixbuf) - return IconHolder(iconPixbuf->gobj_copy()); //copy and pass icon ownership (may be 0) - } - } - catch (const Glib::Error&) {} - - //fallback fallback - return IconHolder(); -#endif -} - - -IconHolder getDirectoryIcon(IconBuffer::IconSize sz) -{ -#ifdef FFS_WIN - return getIconByAttribute(L"dummy", //Windows 7 doesn't like this parameter to be an empty string! - FILE_ATTRIBUTE_DIRECTORY, sz); -#elif defined FFS_LINUX - return ::getAssociatedIcon(Zstr("/usr/"), sz); //all directories will look like "/usr/" -#endif -} - - -IconHolder getFileIcon(IconBuffer::IconSize sz) -{ -#ifdef FFS_WIN - return getIconByAttribute(L"dummy", FILE_ATTRIBUTE_NORMAL, sz); -#elif defined FFS_LINUX - const int requestedSize = IconBuffer::cvrtSize(sz); - try - { - Glib::RefPtr<Gtk::IconTheme> iconTheme = Gtk::IconTheme::get_default(); - if (iconTheme) - { - Glib::RefPtr<Gdk::Pixbuf> iconPixbuf = iconTheme->load_icon("misc", requestedSize, Gtk::ICON_LOOKUP_USE_BUILTIN); - if (!iconPixbuf) - iconPixbuf = iconTheme->load_icon("text-x-generic", requestedSize, Gtk::ICON_LOOKUP_USE_BUILTIN); - if (iconPixbuf) - return IconHolder(iconPixbuf->gobj_copy()); // transfer ownership!! - } - } - catch (const Glib::Error&) {} - - return IconHolder(); -#endif -} -//################################################################################################################################################ - - -//---------------------- Shared Data ------------------------- -struct WorkLoad -{ -public: - Zstring extractNextFile() //context of worker thread, blocking - { - boost::unique_lock<boost::mutex> dummy(lockFiles); - - while (filesToLoad.empty()) - conditionNewFiles.timed_wait(dummy, boost::get_system_time() + boost::posix_time::milliseconds(50)); //interruption point! - - Zstring fileName = filesToLoad.back(); - filesToLoad.pop_back(); - return fileName; - } - - void setWorkload(const std::vector<Zstring>& newLoad) //context of main thread - { - { - boost::unique_lock<boost::mutex> dummy(lockFiles); - filesToLoad = newLoad; - } - conditionNewFiles.notify_one(); - //condition handling, see: http://www.boost.org/doc/libs/1_43_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref - } - -private: - std::vector<Zstring> filesToLoad; //processes last elements of vector first! - boost::mutex lockFiles; - boost::condition_variable conditionNewFiles; //signal event: data for processing available -}; - - -typedef std::map<Zstring, IconHolder, LessFilename> NameIconMap; //entryName/icon -> note: Zstring is thread-safe -typedef std::queue<Zstring> IconDbSequence; //entryName - -class Buffer -{ -public: - bool requestFileIcon(const Zstring& fileName, IconHolder* icon = NULL) - { - boost::lock_guard<boost::mutex> dummy(lockBuffer); - - auto iter = iconMappping.find(fileName); - if (iter != iconMappping.end()) - { - if (icon != NULL) - *icon = iter->second; - return true; - } - return false; - } - - void insertIntoBuffer(const Zstring& entryName, const IconHolder& icon) //called by worker thread - { - boost::lock_guard<boost::mutex> dummy(lockBuffer); - - //thread saftey: icon uses ref-counting! But is NOT shared with main thread! - auto rc = iconMappping.insert(std::make_pair(entryName, icon)); - if (rc.second) //if insertion took place - iconSequence.push(entryName); //note: sharing Zstring with IconDB!!! - - assert(iconMappping.size() == iconSequence.size()); - - //remove elements if buffer becomes too big: - if (iconMappping.size() > BUFFER_SIZE_MAX) //limit buffer size: critical because GDI resources are limited (e.g. 10000 on XP per process) - { - //remove oldest element - iconMappping.erase(iconSequence.front()); - iconSequence.pop(); - } - } - -private: - boost::mutex lockBuffer; - NameIconMap iconMappping; //use synchronisation when accessing this! - IconDbSequence iconSequence; //save sequence of buffer entry to delete oldest elements -}; -//------------------------------------------------------------- -//################################################################################################################################################ - -class WorkerThread //lifetime is part of icon buffer -{ -public: - WorkerThread(const std::shared_ptr<WorkLoad>& workload, - const std::shared_ptr<Buffer>& buffer, - IconBuffer::IconSize sz) : - workload_(workload), - buffer_(buffer), - icoSize(sz) {} - - void operator()(); //thread entry - -private: - std::shared_ptr<WorkLoad> workload_; //main/worker thread may access different shared_ptr instances safely (even though they have the same target!) - std::shared_ptr<Buffer> buffer_; //http://www.boost.org/doc/libs/1_43_0/libs/smart_ptr/shared_ptr.htm?sess=8153b05b34d890e02d48730db1ff7ddc#ThreadSafety - const IconBuffer::IconSize icoSize; -}; - - -void WorkerThread::operator()() //thread entry -{ - //failure to initialize COM for each thread is a source of hard to reproduce bugs: https://sourceforge.net/tracker/?func=detail&aid=3160472&group_id=234430&atid=1093080 -#ifdef FFS_WIN - //Prerequisites, see thumbnail.h - - //1. Initialize COM - ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); - LOKI_ON_BLOCK_EXIT2(::CoUninitialize()); - - //2. Initialize system image list - typedef BOOL (WINAPI *FileIconInitFun)(BOOL fRestoreCache); - const util::DllFun<FileIconInitFun> fileIconInit(L"Shell32.dll", reinterpret_cast<LPCSTR>(660)); - assert(fileIconInit); - if (fileIconInit) - fileIconInit(false); //TRUE to restore the system image cache from disk; FALSE otherwise. -#endif - - while (true) - { - boost::this_thread::interruption_point(); - - const Zstring fileName = workload_->extractNextFile(); //start work: get next icon to load - - if (buffer_->requestFileIcon(fileName)) - continue; //icon already in buffer: skip - - buffer_->insertIntoBuffer(fileName, getAssociatedIcon(fileName, icoSize)); - } -} -//######################### redirect to impl ##################################################### - -struct IconBuffer::Pimpl -{ - Pimpl() : - workload(std::make_shared<WorkLoad>()), - buffer(std::make_shared<Buffer>()) {} - - std::shared_ptr<WorkLoad> workload; - std::shared_ptr<Buffer> buffer; - - boost::thread worker; -}; - - -IconBuffer::IconBuffer(IconSize sz) : - pimpl(new Pimpl), - icoSize(sz), - genDirIcon(::getDirectoryIcon(sz).toWxIcon(cvrtSize(icoSize))), - genFileIcon(::getFileIcon(sz).toWxIcon(cvrtSize(icoSize))) -{ - pimpl->worker = boost::thread(WorkerThread(pimpl->workload, pimpl->buffer, sz)); -} - - -IconBuffer::~IconBuffer() -{ - setWorkload(std::vector<Zstring>()); //make sure interruption point is always reached! - pimpl->worker.interrupt(); - pimpl->worker.join(); -} - - -bool IconBuffer::requestFileIcon(const Zstring& filename, wxIcon* icon) -{ - auto getIcon = [&](const Zstring& entryName) -> bool - { - if (!icon) - return pimpl->buffer->requestFileIcon(entryName); - - IconHolder heldIcon; - if (!pimpl->buffer->requestFileIcon(entryName, &heldIcon)) - return false; - *icon = heldIcon.toWxIcon(cvrtSize(icoSize)); - return true; - }; - -#ifdef FFS_WIN - //perf: let's read icons which don't need file access right away! No async delay justified! - if (icoSize == IconBuffer::SIZE_SMALL) //non-thumbnail view, we need file type icons only! - { - const Zstring& extension = getFileExtension(filename); - if (isCheapExtension(extension)) //"pricey" extensions are stored with fullnames and are read from disk, while cheap ones require just the extension - { - if (!getIcon(extension)) - { - IconHolder heldIcon = getAssociatedIconByExt(extension, icoSize); //fast! - pimpl->buffer->insertIntoBuffer(extension, heldIcon); - if (icon) - *icon = heldIcon.toWxIcon(cvrtSize(icoSize)); - } - return true; - } - } -#endif - - return getIcon(filename); -} - -void IconBuffer::setWorkload(const std::vector<Zstring>& load) { pimpl->workload->setWorkload(load); } |