summaryrefslogtreecommitdiff
path: root/lib/icon_buffer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/icon_buffer.cpp')
-rw-r--r--lib/icon_buffer.cpp560
1 files changed, 323 insertions, 237 deletions
diff --git a/lib/icon_buffer.cpp b/lib/icon_buffer.cpp
index 44440be8..dd80f6dd 100644
--- a/lib/icon_buffer.cpp
+++ b/lib/icon_buffer.cpp
@@ -12,54 +12,52 @@
#ifdef FFS_WIN
#include <zen/dll.h>
-#include "Thumbnail/thumbnail.h"
#include <zen/win_ver.h>
+#include <wx/image.h>
+#include "Thumbnail/thumbnail.h"
#elif defined FFS_LINUX
#include <gtk/gtk.h>
+
+#elif defined FFS_MAC
+#include "osx_file_icon.h"
#endif
using namespace zen;
-warn_static("mac")
-
-#if defined FFS_MAC
-struct IconBuffer::Pimpl {};
-IconBuffer::IconBuffer(IconSize sz): pimpl(), icoSize(sz), genDirIcon(), genFileIcon() {}
-IconBuffer::~IconBuffer() {}
-int IconBuffer::getSize(IconSize icoSize) {return 16; }
-bool IconBuffer::requestFileIcon(const Zstring& filename, wxIcon* icon) { return false; }
-void IconBuffer::setWorkload(const std::vector<Zstring>& load) {}
-#else
-
namespace
{
-const size_t BUFFER_SIZE_MAX = 800; //maximum number of icons to buffer
+const size_t BUFFER_SIZE_MAX = 600; //maximum number of icons to hold in buffer
+
+#ifndef NDEBUG
+boost::thread::id mainThreadId = boost::this_thread::get_id();
+#endif
+
+#ifdef FFS_WIN
+#define DEF_DLL_FUN(name) DllFun<thumb::FunType_##name> name(thumb::getDllName(), thumb::funName_##name);
-class IconHolder //handle HICON/GdkPixbuf ownership WITHOUT ref-counting to allow thread-safe usage (in contrast to wxIcon)
+DEF_DLL_FUN(getIconByIndex); //
+DEF_DLL_FUN(getThumbnail); //let's spare the boost::call_once hustle and allocate statically
+DEF_DLL_FUN(releaseImageData); //
+#endif
+
+class IconHolder //handle HICON/GdkPixbuf ownership supporting thread-safe usage (in contrast to wxIcon/wxBitmap)
{
public:
#ifdef FFS_WIN
- typedef HICON HandleType;
+ typedef const thumb::ImageData* HandleType;
#elif defined FFS_LINUX
typedef GdkPixbuf* HandleType;
+#elif defined FFS_MAC
+ typedef osx::ImageData* HandleType;
#endif
explicit IconHolder(HandleType handle = nullptr) : handle_(handle) {} //take ownership!
- //icon holder has value semantics!
- IconHolder(const IconHolder& other) : handle_(other.handle_ == nullptr ? nullptr :
-#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(IconHolder&& other) : handle_(other.handle_) { other.handle_ = nullptr; }
+ IconHolder(IconHolder&& other) : handle_(other.release()) {}
- IconHolder& operator=(IconHolder other) //unifying assignment: no need for r-value reference optimization!
+ IconHolder& operator=(IconHolder other) //unifying assignment
{
other.swap(*this);
return *this;
@@ -69,72 +67,69 @@ public:
{
if (handle_ != nullptr)
#ifdef FFS_WIN
- ::DestroyIcon(handle_);
+ releaseImageData(handle_);
#elif defined FFS_LINUX
::g_object_unref(handle_); //superseedes "::gdk_pixbuf_unref"!
+#elif defined FFS_MAC
+ delete handle_;
#endif
}
+ HandleType release()
+ {
+ ZEN_ON_SCOPE_EXIT(handle_ = nullptr);
+ return handle_;
+ }
+
void swap(IconHolder& other) { std::swap(handle_, other.handle_); } //throw()
- wxIcon toWxIcon(int expectedSize) const //copy HandleType, caller needs to take ownership!
+ //destroys raw icon! Call from GUI thread only!
+ wxBitmap extractWxBitmap()
{
- if (handle_ == nullptr)
- return wxNullIcon;
+ ZEN_ON_SCOPE_EXIT(assert(!*this));
+ assert(boost::this_thread::get_id() == mainThreadId );
- IconHolder clone(*this);
+ if (!handle_)
+ return wxNullBitmap;
- 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))
- {
- if (icoInfo.hbmMask) //VC11 static analyzer warns this could be null
- ::DeleteObject(icoInfo.hbmMask); //nice potential for a GDI leak!
-
- if (icoInfo.hbmColor) //optional (for black and white bitmap)
- {
- ZEN_ON_SCOPE_EXIT(::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 (0 < expectedSize && expectedSize < maxExtent)
- {
- 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(expectedSize, expectedSize); //icon is stretched to this size if referenced HICON differs
+ ZEN_ON_SCOPE_EXIT(IconHolder().swap(*this)); //destroy after extraction
+
+ //let wxImage reference data without taking ownership:
+ wxImage fileIcon(handle_->width, handle_->height, handle_->rgb, true);
+ fileIcon.SetAlpha(handle_->alpha, true);
+ return wxBitmap(fileIcon);
#elif defined FFS_LINUX
- // transfer ownership!!
#if wxCHECK_VERSION(2, 9, 4)
- newIcon.CopyFromBitmap(wxBitmap(clone.handle_));
+ return wxBitmap(release()); //ownership passed!
#else
- newIcon.SetPixbuf(clone.handle_);
+ wxBitmap newIcon;
+ newIcon.SetPixbuf(release()); //ownership passed!
+ return newIcon;
#endif
-#endif //
- clone.handle_ = nullptr; //
- return newIcon;
+#elif defined FFS_MAC
+ ZEN_ON_SCOPE_EXIT(IconHolder().swap(*this)); //destroy after extraction
+
+ //let wxImage reference data without taking ownership:
+ if (!handle_->rgb.empty())
+ {
+ wxImage fileIcon(handle_->width, handle_->height, &handle_->rgb[0], true);
+ if (!handle_->alpha.empty())
+ fileIcon.SetAlpha(&handle_->alpha[0], true);
+ return wxBitmap(fileIcon);
+ }
+ assert(false); //rgb and alpha should never be empty
+ return wxBitmap();
+#endif
}
private:
HandleType handle_;
- struct ConversionToBool { int dummy; };
+ IconHolder(const IconHolder& other); //move semantics!
+ 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_ != nullptr ? &ConversionToBool::dummy : nullptr; }
@@ -151,51 +146,51 @@ Zstring getFileExtension(const Zstring& filename)
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");
+
+ priceyExtensions.insert(L"lnk"); //
+ priceyExtensions.insert(L"url"); //make sure shortcuts are pricey to get them to be detected by SHGetFileInfo
+ priceyExtensions.insert(L"pif"); //
+ priceyExtensions.insert(L"website"); //
+
});
return priceyExtensions.find(extension) == priceyExtensions.end();
}
-
const bool wereVistaOrLater = vistaOrLater(); //thread-safety: init at startup
-int getShilIconType(IconBuffer::IconSize sz)
+thumb::IconSizeType getThumbSizeType(IconBuffer::IconSize sz)
{
+ using namespace thumb;
switch (sz)
{
case IconBuffer::SIZE_SMALL:
- return SHIL_SMALL; //16x16, but the size can be customized by the user.
+ return ICON_SIZE_16;
case IconBuffer::SIZE_MEDIUM:
- return SHIL_EXTRALARGE; //typically 48x48, but the size can be customized by the user.
+ if (!wereVistaOrLater) return ICON_SIZE_32; //48x48 doesn't look sharp on XP
+ return ICON_SIZE_48;
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 ICON_SIZE_128;
}
- return SHIL_SMALL;
+ return ICON_SIZE_16;
}
-DllFun<thumb::FunType_getIconByIndex> 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!
@@ -205,16 +200,13 @@ IconHolder getIconByAttribute(LPCWSTR pszPath, DWORD dwFileAttributes, IconBuffe
&fileInfo,
sizeof(fileInfo),
SHGFI_SYSICONINDEX | SHGFI_USEFILEATTRIBUTES);
- //no need to IUnknown::Release() imgList!
- if (!imgList)
+ if (!imgList) //no need to IUnknown::Release() imgList!
return IconHolder();
- boost::call_once(initGetIconByIndexOnce, [] //thread-safe init
- {
- using namespace thumb;
- getIconByIndex = DllFun<FunType_getIconByIndex>(getDllName(), funName_getIconByIndex);
- });
- return IconHolder(getIconByIndex ? static_cast<HICON>(getIconByIndex(fileInfo.iIcon, getShilIconType(sz))) : nullptr);
+ if (!getIconByIndex)
+ return IconHolder();
+
+ return IconHolder(getIconByIndex(fileInfo.iIcon, getThumbSizeType(sz)));
}
@@ -224,33 +216,66 @@ IconHolder getAssociatedIconByExt(const Zstring& extension, IconBuffer::IconSize
return getIconByAttribute((L"dummy." + extension).c_str(), FILE_ATTRIBUTE_NORMAL, sz);
}
-
-DllFun<thumb::FunType_getThumbnail> getThumbnailIcon;
-boost::once_flag initThumbnailOnce = BOOST_ONCE_INIT;
+#elif defined FFS_LINUX
+IconHolder iconHolderFromGicon(GIcon* gicon, IconBuffer::IconSize sz)
+{
+ if (gicon)
+ if (GtkIconTheme* defaultTheme = ::gtk_icon_theme_get_default()) //not owned!
+ if (GtkIconInfo* iconInfo = ::gtk_icon_theme_lookup_by_gicon(defaultTheme, gicon, IconBuffer::getSize(sz), GTK_ICON_LOOKUP_USE_BUILTIN)) //this may fail if icon is not installed on system
+ {
+ ZEN_ON_SCOPE_EXIT(::gtk_icon_info_free(iconInfo);)
+ if (GdkPixbuf* pixBuf = ::gtk_icon_info_load_icon(iconInfo, nullptr))
+ return IconHolder(pixBuf); //pass ownership
+ }
+ return IconHolder();
+}
#endif
}
-//################################################################################################################################################
+//################################################################################################################################################
-IconHolder getThumbnail(const Zstring& filename, int requestedSize) //return 0 on failure
+IconHolder getThumbnailIcon(const Zstring& filename, int requestedSize) //return 0 on failure
{
#ifdef FFS_WIN
- boost::call_once(initThumbnailOnce, [] //note: "getThumbnail" function itself is already thread-safe
- {
- using namespace thumb;
- getThumbnailIcon = DllFun<FunType_getThumbnail>(getDllName(), funName_getThumbnail);
- });
- return IconHolder(getThumbnailIcon ? static_cast< ::HICON>(getThumbnailIcon(filename.c_str(), requestedSize)) : nullptr);
+ if (getThumbnail)
+ return IconHolder(getThumbnail(filename.c_str(), requestedSize));
#elif defined FFS_LINUX
- GdkPixbuf* pixBuf = gdk_pixbuf_new_from_file_at_size(filename.c_str(), requestedSize, requestedSize, nullptr);
- return IconHolder(pixBuf); //pass ownership (may be 0)
+ gint width = 0;
+ gint height = 0;
+ if (GdkPixbufFormat* fmt = ::gdk_pixbuf_get_file_info(filename.c_str(), &width, &height))
+ {
+ (void)fmt;
+ if (width > 0 && height > 0 && requestedSize > 0)
+ {
+ int trgWidth = width;
+ int trgHeight = height;
+
+ const int maxExtent = std::max(width, height); //don't stretch small images, but shrink large ones instead!
+ if (requestedSize < maxExtent)
+ {
+ trgWidth = width * requestedSize / maxExtent;
+ trgHeight = height * requestedSize / maxExtent;
+ }
+ if (GdkPixbuf* pixBuf = ::gdk_pixbuf_new_from_file_at_size(filename.c_str(), trgWidth, trgHeight, nullptr))
+ return IconHolder(pixBuf); //pass ownership
+ }
+ }
+
+#elif defined FFS_MAC
+ try
+ {
+ return IconHolder(new osx::ImageData(osx::getThumbnail(filename.c_str(), requestedSize))); //throw OsxError
+ }
+ catch (osx::OsxError&) {}
#endif
+ return IconHolder();
}
IconHolder getGenericFileIcon(IconBuffer::IconSize sz)
{
+ //we're called by getAssociatedIcon()! -> avoid endless recursion!
#ifdef FFS_WIN
return getIconByAttribute(L"dummy", FILE_ATTRIBUTE_NORMAL, sz);
@@ -259,18 +284,44 @@ IconHolder getGenericFileIcon(IconBuffer::IconSize sz)
{
"application-x-zerosize", //Kubuntu: /usr/share/icons/oxygen/48x48/mimetypes
"text-x-generic", //http://live.gnome.org/GnomeArt/Tutorials/IconThemes
- "empty", //
- "gtk-file", //Ubuntu: /usr/share/icons/Humanity/mimes/48
+ "empty", //Ubuntu: /usr/share/icons/Humanity/mimes/48
+ GTK_STOCK_FILE, //"gtk-file",
"gnome-fs-regular", //
};
- const int requestedSize = IconBuffer::getSize(sz);
-
if (GtkIconTheme* defaultTheme = gtk_icon_theme_get_default()) //not owned!
for (auto it = std::begin(mimeFileIcons); it != std::end(mimeFileIcons); ++it)
- if (GdkPixbuf* pixBuf = gtk_icon_theme_load_icon(defaultTheme, *it, requestedSize, GTK_ICON_LOOKUP_USE_BUILTIN, nullptr))
- return IconHolder(pixBuf); //pass ownership (may be nullptr)
+ if (GdkPixbuf* pixBuf = gtk_icon_theme_load_icon(defaultTheme, *it, IconBuffer::getSize(sz), GTK_ICON_LOOKUP_USE_BUILTIN, nullptr))
+ return IconHolder(pixBuf); //pass ownership
+ return IconHolder();
+
+#elif defined FFS_MAC
+ try
+ {
+ return IconHolder(new osx::ImageData(osx::getDefaultFileIcon(IconBuffer::getSize(sz)))); //throw OsxError
+ }
+ catch (osx::OsxError&) {}
+ return IconHolder();
+#endif
+}
+
+
+IconHolder getGenericDirectoryIcon(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
+ if (GIcon* dirIcon = ::g_content_type_get_icon("inode/directory")) //should contain fallback to GTK_STOCK_DIRECTORY ("gtk-directory")
+ return iconHolderFromGicon(dirIcon, sz);
return IconHolder();
+
+#elif defined FFS_MAC
+ try
+ {
+ return IconHolder(new osx::ImageData(osx::getDefaultFolderIcon(IconBuffer::getSize(sz)))); //throw OsxError
+ }
+ catch (osx::OsxError&) { return IconHolder(); }
#endif
}
@@ -284,15 +335,14 @@ IconHolder getAssociatedIcon(const Zstring& filename, IconBuffer::IconSize sz)
break;
case IconBuffer::SIZE_MEDIUM:
case IconBuffer::SIZE_LARGE:
- {
- IconHolder ico = getThumbnail(filename, IconBuffer::getSize(sz));
- if (ico)
+ if (IconHolder ico = getThumbnailIcon(filename, IconBuffer::getSize(sz)))
return ico;
//else: fallback to non-thumbnail icon
- }
- break;
+ break;
}
+ warn_static("problem: für folder links ist getThumbnail erfolgreich => SFGAO_LINK nicht gecheckt!")
+
//2. retrieve file icons
#ifdef FFS_WIN
//perf: optimize fallback case for SIZE_MEDIUM and SIZE_LARGE:
@@ -303,70 +353,52 @@ IconHolder getAssociatedIcon(const Zstring& filename, IconBuffer::IconSize sz)
//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(), //_In_ LPCTSTR pszPath, -> note: ::SHGetFileInfo() can't handle \\?\-prefix!
- 0, //DWORD dwFileAttributes,
- &fileInfo, //_Inout_ SHFILEINFO *psfi,
- sizeof(fileInfo), //UINT cbFileInfo,
- SHGFI_SYSICONINDEX); //UINT uFlags
-
- //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 (DWORD_PTR imgList = ::SHGetFileInfo(filename.c_str(), //_In_ LPCTSTR pszPath, -> note: ::SHGetFileInfo() can't handle \\?\-prefix!
+ 0, //DWORD dwFileAttributes,
+ &fileInfo, //_Inout_ SHFILEINFO *psfi,
+ sizeof(fileInfo), //UINT cbFileInfo,
+ SHGFI_SYSICONINDEX | SHGFI_ATTRIBUTES)) //UINT uFlags
+ {
+ //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
- if (!imgList)
- return IconHolder();
- //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
+ //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
- boost::call_once(initGetIconByIndexOnce, [] //thread-safe init
- {
- getIconByIndex = DllFun<thumb::FunType_getIconByIndex>(thumb::getDllName(), thumb::funName_getIconByIndex);
- });
- return IconHolder(getIconByIndex ? static_cast<HICON>(getIconByIndex(fileInfo.iIcon, getShilIconType(sz))) : nullptr);
+#ifndef SFGAO_LINK //Shobjidl.h
+#define SFGAO_LINK 0x00010000L // Shortcut (link) or symlinks
+#endif
-#elif defined FFS_LINUX
- const int requestedSize = IconBuffer::getSize(sz);
+ warn_static("support SFGAO_GHOSTED or hidden?")
- GFile* file = g_file_new_for_path(filename.c_str()); //never fails
- ZEN_ON_SCOPE_EXIT(g_object_unref(file);)
+ const bool isLink = (fileInfo.dwAttributes & SFGAO_LINK) != 0;
- if (GFileInfo* fileInfo = g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_ICON, G_FILE_QUERY_INFO_NONE, nullptr, nullptr))
- {
- ZEN_ON_SCOPE_EXIT(g_object_unref(fileInfo);)
-
- if (GIcon* gicon = g_file_info_get_icon(fileInfo)) //not owned!
- if (GtkIconTheme* defaultTheme = gtk_icon_theme_get_default()) //not owned!
- if (GtkIconInfo* iconInfo = gtk_icon_theme_lookup_by_gicon(defaultTheme, gicon, requestedSize, GTK_ICON_LOOKUP_USE_BUILTIN)) //this may fail if icon is not installed on system
- {
- ZEN_ON_SCOPE_EXIT(gtk_icon_info_free(iconInfo);)
- if (GdkPixbuf* pixBuf = gtk_icon_info_load_icon(iconInfo, nullptr))
- return IconHolder(pixBuf); //pass ownership (may be nullptr)
- }
+ if (getIconByIndex)
+ if (const thumb::ImageData* imgData = getIconByIndex(fileInfo.iIcon, getThumbSizeType(sz)))
+ return IconHolder(imgData);
}
- //fallback: icon lookup may fail because some icons are currently not present on system
- return ::getGenericFileIcon(sz);
-#endif
-}
-/* Dependency Diagram:
+#elif defined FFS_LINUX
+ GFile* file = ::g_file_new_for_path(filename.c_str()); //never fails
+ ZEN_ON_SCOPE_EXIT(::g_object_unref(file);)
-getGenericFileIcon()
- /|\
- |
-getAssociatedIcon()
- /|\
- |
-getGenericDirectoryIcon()
-*/
+ if (GFileInfo* fileInfo = ::g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_ICON, G_FILE_QUERY_INFO_NONE, nullptr, nullptr))
+ {
+ ZEN_ON_SCOPE_EXIT(::g_object_unref(fileInfo);)
+ if (GIcon* gicon = ::g_file_info_get_icon(fileInfo)) //not owned!
+ return iconHolderFromGicon(gicon, sz);
+ }
+ //need fallback: icon lookup may fail because some icons are currently not present on system
-IconHolder getGenericDirectoryIcon(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/"
+#elif defined FFS_MAC
+ try
+ {
+ return IconHolder(new osx::ImageData(osx::getFileIcon(filename.c_str(), IconBuffer::getSize(sz)))); //throw OsxError
+ }
+ catch (osx::OsxError&) {}
#endif
+ return ::getGenericFileIcon(sz); //make sure this does not internally call getAssociatedIcon("someDefaultFile.txt")!!! => endless recursion!
}
//################################################################################################################################################
@@ -377,6 +409,7 @@ class WorkLoad
public:
Zstring extractNextFile() //context of worker thread, blocking
{
+ assert(boost::this_thread::get_id() != mainThreadId );
boost::unique_lock<boost::mutex> dummy(lockFiles);
while (filesToLoad.empty())
@@ -387,8 +420,9 @@ public:
return fileName;
}
- void setWorkload(const std::vector<Zstring>& newLoad) //context of main thread
+ void setWorkload(const std::list<Zstring>& newLoad) //context of main thread
{
+ assert(boost::this_thread::get_id() == mainThreadId );
{
boost::unique_lock<boost::mutex> dummy(lockFiles);
filesToLoad = newLoad;
@@ -397,57 +431,95 @@ public:
//condition handling, see: http://www.boost.org/doc/libs/1_43_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref
}
+ void addToWorkload(const Zstring& newEntry) //context of main thread
+ {
+ assert(boost::this_thread::get_id() == mainThreadId );
+ {
+ boost::unique_lock<boost::mutex> dummy(lockFiles);
+ filesToLoad.push_back(newEntry); //set as next item to retrieve
+ }
+ conditionNewFiles.notify_all();
+ }
+
private:
- std::vector<Zstring> filesToLoad; //processes last elements of vector first!
+ std::list<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 like an int"
-typedef std::queue<Zstring> IconDbSequence; //entryName
-
class Buffer
{
public:
- bool requestFileIcon(const Zstring& fileName, IconHolder* icon = nullptr)
+ //called by main and worker thread:
+ bool hasFileIcon(const Zstring& fileName)
{
- boost::lock_guard<boost::mutex> dummy(lockBuffer);
+ boost::lock_guard<boost::mutex> dummy(lockIconList);
+ return iconList.find(fileName) != iconList.end();
+ }
- auto it = iconMappping.find(fileName);
- if (it != iconMappping.end())
+ //must be called by main thread only! => wxBitmap is NOT thread-safe like an int (non-atomic ref-count!!!)
+ Opt<wxBitmap> retrieveFileIcon(const Zstring& fileName)
+ {
+ assert(boost::this_thread::get_id() == mainThreadId );
+ boost::lock_guard<boost::mutex> dummy(lockIconList);
+ auto it = iconList.find(fileName);
+ if (it == iconList.end())
+ return NoValue();
+
+ IconData& idata = it->second;
+ if (idata.iconRaw) //if not yet converted...
{
- if (icon)
- *icon = it->second;
- return true;
+ idata.iconFmt = make_unique<wxBitmap>(idata.iconRaw.extractWxBitmap()); //convert in main thread!
+ assert(!idata.iconRaw);
}
- return false;
+ return idata.iconFmt ? *idata.iconFmt : wxNullBitmap; //idata.iconRaw may be inserted as empty from worker thread!
}
- void insertIntoBuffer(const Zstring& entryName, const IconHolder& icon) //called by worker thread
+ //must be called by main thread only! => ~wxBitmap() is NOT thread-safe!
+ //call at an appropriate time, e.g. after Workload::setWorkload()
+ void limitBufferSize() //critical because GDI resources are limited (e.g. 10000 on XP per process)
{
- boost::lock_guard<boost::mutex> dummy(lockBuffer);
+ assert(boost::this_thread::get_id() == mainThreadId );
+ boost::lock_guard<boost::mutex> dummy(lockIconList);
+ while (iconList.size() > BUFFER_SIZE_MAX)
+ {
+ iconList.erase(iconSequence.front()); //remove oldest element
+ iconSequence.pop();
+ }
+ }
+
+ //called by main and worker thread:
+ void moveIntoBuffer(const Zstring& entryName, IconHolder&& icon)
+ {
+ boost::lock_guard<boost::mutex> dummy(lockIconList);
- //thread saftey: icon uses ref-counting! But is NOT shared with main thread!
- auto rc = iconMappping.insert(std::make_pair(entryName, icon));
+ //thread safety: moving IconHolder is free from side effects, but ~wxBitmap() is NOT! => do NOT delete items from iconList here!
+ auto rc = iconList.insert(std::make_pair(entryName, IconData(std::move(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();
- }
+ assert(iconList.size() == iconSequence.size());
}
private:
- boost::mutex lockBuffer;
- NameIconMap iconMappping; //use synchronisation when accessing this!
- IconDbSequence iconSequence; //save sequence of buffer entry to delete oldest elements
+ struct IconData
+ {
+ IconData(IconHolder&& tmp) : iconRaw(std::move(tmp)) {}
+ IconData(IconData&& tmp) : iconRaw(std::move(tmp.iconRaw)), iconFmt(std::move(tmp.iconFmt)) {}
+
+ IconHolder iconRaw; //native icon representation: may be used by any thread
+
+ std::unique_ptr<wxBitmap> iconFmt; //use ONLY from main thread!
+ //wxBitmap is NOT thread-safe: non-atomic ref-count just to begin with...
+ //- prohibit implicit calls to wxBitmap(const wxBitmap&)
+ //- prohibit calls to ~wxBitmap() and transitively ~IconData()
+ //- prohibit even wxBitmap() default constructor - better be safe than sorry!
+ };
+
+ boost::mutex lockIconList;
+ std::map<Zstring, IconData, LessFilename> iconList; //shared resource; Zstring is thread-safe like an int
+ std::queue<Zstring> iconSequence; //save sequence of buffer entry to delete oldest elements
};
//################################################################################################################################################
@@ -457,17 +529,17 @@ 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) :
+ IconBuffer::IconSize st) :
workload_(workload),
buffer_(buffer),
- icoSize(sz) {}
+ iconSizeType(st) {}
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;
+ const IconBuffer::IconSize iconSizeType;
};
@@ -493,20 +565,20 @@ void WorkerThread::operator()() //thread entry
{
boost::this_thread::interruption_point();
- const Zstring fileName = workload_->extractNextFile(); //start work: get next icon to load
+ const Zstring fileName = workload_->extractNextFile(); //start work: blocks until next icon to load is retrieved
- if (buffer_->requestFileIcon(fileName))
- continue; //icon already in buffer: skip
-
- buffer_->insertIntoBuffer(fileName, getAssociatedIcon(fileName, icoSize));
+ if (!buffer_->hasFileIcon(fileName)) //perf: workload may contain duplicate entries?
+ buffer_->moveIntoBuffer(fileName, getAssociatedIcon(fileName, iconSizeType));
}
}
+
//######################### redirect to impl #####################################################
struct IconBuffer::Pimpl
{
- Pimpl() : workload(std::make_shared<WorkLoad>()),
- buffer(std::make_shared<Buffer>()) {}
+ Pimpl() :
+ workload(std::make_shared<WorkLoad>()),
+ buffer (std::make_shared<Buffer>()) {}
std::shared_ptr<WorkLoad> workload;
std::shared_ptr<Buffer> buffer;
@@ -516,9 +588,9 @@ struct IconBuffer::Pimpl
IconBuffer::IconBuffer(IconSize sz) : pimpl(make_unique<Pimpl>()),
- icoSize(sz),
- genDirIcon(::getGenericDirectoryIcon(sz).toWxIcon(IconBuffer::getSize(icoSize))),
- genFileIcon(::getGenericFileIcon(sz).toWxIcon(IconBuffer::getSize(icoSize)))
+ iconSizeType(sz),
+ genDirIcon (::getGenericDirectoryIcon(sz).extractWxBitmap()),
+ genFileIcon(::getGenericFileIcon (sz).extractWxBitmap())
{
pimpl->worker = boost::thread(WorkerThread(pimpl->workload, pimpl->buffer, sz));
}
@@ -526,7 +598,7 @@ IconBuffer::IconBuffer(IconSize sz) : pimpl(make_unique<Pimpl>()),
IconBuffer::~IconBuffer()
{
- setWorkload(std::vector<Zstring>()); //make sure interruption point is always reached!
+ setWorkload(std::list<Zstring>()); //make sure interruption point is always reached!
pimpl->worker.interrupt();
pimpl->worker.join(); //we assume precondition "worker.joinable()"!!!
}
@@ -543,7 +615,11 @@ int IconBuffer::getSize(IconSize icoSize)
return 24;
#endif
case IconBuffer::SIZE_MEDIUM:
+#ifdef FFS_WIN
+ if (!wereVistaOrLater) return 32; //48x48 doesn't look sharp on XP
+#endif
return 48;
+
case IconBuffer::SIZE_LARGE:
return 128;
}
@@ -552,41 +628,51 @@ int IconBuffer::getSize(IconSize icoSize)
}
-bool IconBuffer::requestFileIcon(const Zstring& filename, wxIcon* icon)
+bool IconBuffer::readyForRetrieval(const Zstring& filename)
{
- 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(IconBuffer::getSize(icoSize));
- return true;
- };
+#ifdef FFS_WIN
+ if (iconSizeType == IconBuffer::SIZE_SMALL)
+ if (isCheapExtension(getFileExtension(filename)))
+ return true;
+#endif
+ return pimpl->buffer->hasFileIcon(filename);
+}
+
+Opt<wxBitmap> IconBuffer::retrieveFileIcon(const Zstring& filename)
+{
#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!
+ if (iconSizeType == 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(IconBuffer::getSize(icoSize));
- }
- return true;
+ if (Opt<wxBitmap> ico = pimpl->buffer->retrieveFileIcon(extension))
+ return ico;
+
+ //make sure icon is in buffer, even if icon needs not be retrieved!
+ pimpl->buffer->moveIntoBuffer(extension, getAssociatedIconByExt(extension, iconSizeType));
+
+ Opt<wxBitmap> ico = pimpl->buffer->retrieveFileIcon(extension);
+ assert(ico);
+ return ico;
}
}
#endif
- return getIcon(filename);
+ if (Opt<wxBitmap> ico = pimpl->buffer->retrieveFileIcon(filename))
+ return ico;
+
+ //since this icon seems important right now, we don't want to wait until next setWorkload() to start retrieving
+ pimpl->workload->addToWorkload(filename);
+ pimpl->buffer->limitBufferSize();
+ return NoValue();
}
-void IconBuffer::setWorkload(const std::vector<Zstring>& load) { pimpl->workload->setWorkload(load); }
-#endif
+
+void IconBuffer::setWorkload(const std::list<Zstring>& load)
+{
+ pimpl->workload->setWorkload(load); //since buffer can only increase due to new workload,
+ pimpl->buffer->limitBufferSize(); //this is the place to impose the limit from main thread!
+}
bgstack15