summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2014-04-18 17:23:48 +0200
committerDaniel Wilhelm <daniel@wili.li>2014-04-18 17:23:48 +0200
commitee1c8c5c25d25dfa42120125a8a45dc9831ee412 (patch)
tree67aa287157db954e0cadeee05b4aad331eb2ecf2 /lib
parent5.13 (diff)
downloadFreeFileSync-ee1c8c5c25d25dfa42120125a8a45dc9831ee412.tar.gz
FreeFileSync-ee1c8c5c25d25dfa42120125a8a45dc9831ee412.tar.bz2
FreeFileSync-ee1c8c5c25d25dfa42120125a8a45dc9831ee412.zip
5.14
Diffstat (limited to 'lib')
-rw-r--r--lib/Batch.icobin103260 -> 103260 bytes
-rw-r--r--lib/ShadowCopy/Shadow_Server2003.vcxproj14
-rw-r--r--lib/ShadowCopy/Shadow_Windows7.vcxproj14
-rw-r--r--lib/ShadowCopy/Shadow_XP.vcxproj14
-rw-r--r--lib/ShadowCopy/shadow.cpp18
-rw-r--r--lib/Thumbnail/Thumbnail.vcxproj12
-rw-r--r--lib/Thumbnail/thumbnail.cpp514
-rw-r--r--lib/Thumbnail/thumbnail.h45
-rw-r--r--lib/dir_exist_async.h5
-rw-r--r--lib/ffs_paths.cpp25
-rw-r--r--lib/help_provider.h72
-rw-r--r--lib/icon_buffer.cpp560
-rw-r--r--lib/icon_buffer.h26
-rw-r--r--lib/localization.cpp6
-rw-r--r--lib/osx_file_icon.h36
-rw-r--r--lib/osx_file_icon.mm179
-rw-r--r--lib/parse_lng.h1
-rw-r--r--lib/process_xml.cpp32
-rw-r--r--lib/resolve_path.cpp84
-rw-r--r--lib/resources.cpp20
-rw-r--r--lib/resources.h8
21 files changed, 1154 insertions, 531 deletions
diff --git a/lib/Batch.ico b/lib/Batch.ico
index d27f35d1..faa2db64 100644
--- a/lib/Batch.ico
+++ b/lib/Batch.ico
Binary files differ
diff --git a/lib/ShadowCopy/Shadow_Server2003.vcxproj b/lib/ShadowCopy/Shadow_Server2003.vcxproj
index 50a3a830..4622c246 100644
--- a/lib/ShadowCopy/Shadow_Server2003.vcxproj
+++ b/lib/ShadowCopy/Shadow_Server2003.vcxproj
@@ -227,20 +227,6 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\..\zen\debug_memory_leaks.cpp" />
- <ClCompile Include="dll_main.cpp">
- <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- </PrecompiledHeader>
- <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</CompileAsManaged>
- <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
- </PrecompiledHeader>
- <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</CompileAsManaged>
- <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- </PrecompiledHeader>
- <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</CompileAsManaged>
- <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
- </PrecompiledHeader>
- <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</CompileAsManaged>
- </ClCompile>
<ClCompile Include="shadow.cpp" />
</ItemGroup>
<ItemGroup>
diff --git a/lib/ShadowCopy/Shadow_Windows7.vcxproj b/lib/ShadowCopy/Shadow_Windows7.vcxproj
index aa32253b..c735371b 100644
--- a/lib/ShadowCopy/Shadow_Windows7.vcxproj
+++ b/lib/ShadowCopy/Shadow_Windows7.vcxproj
@@ -227,20 +227,6 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\..\zen\debug_memory_leaks.cpp" />
- <ClCompile Include="dll_main.cpp">
- <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- </PrecompiledHeader>
- <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</CompileAsManaged>
- <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
- </PrecompiledHeader>
- <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</CompileAsManaged>
- <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- </PrecompiledHeader>
- <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</CompileAsManaged>
- <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
- </PrecompiledHeader>
- <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</CompileAsManaged>
- </ClCompile>
<ClCompile Include="shadow.cpp" />
</ItemGroup>
<ItemGroup>
diff --git a/lib/ShadowCopy/Shadow_XP.vcxproj b/lib/ShadowCopy/Shadow_XP.vcxproj
index 70b792ec..a5d07786 100644
--- a/lib/ShadowCopy/Shadow_XP.vcxproj
+++ b/lib/ShadowCopy/Shadow_XP.vcxproj
@@ -228,20 +228,6 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\..\zen\debug_memory_leaks.cpp" />
- <ClCompile Include="dll_main.cpp">
- <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- </PrecompiledHeader>
- <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</CompileAsManaged>
- <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
- </PrecompiledHeader>
- <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</CompileAsManaged>
- <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- </PrecompiledHeader>
- <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</CompileAsManaged>
- <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
- </PrecompiledHeader>
- <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</CompileAsManaged>
- </ClCompile>
<ClCompile Include="shadow.cpp" />
</ItemGroup>
<ItemGroup>
diff --git a/lib/ShadowCopy/shadow.cpp b/lib/ShadowCopy/shadow.cpp
index 65387f3b..4f6881ad 100644
--- a/lib/ShadowCopy/shadow.cpp
+++ b/lib/ShadowCopy/shadow.cpp
@@ -88,25 +88,25 @@ shadow::ShadowData createShadowCopy(const wchar_t* volumeName) //throw ComError
}
}
- ZEN_CHECK_COM(backupComp->InitializeForBackup()); //throw ComError
- ZEN_CHECK_COM(backupComp->SetBackupState(false, false, VSS_BT_FULL)); //throw ComError
+ ZEN_COM_CHECK(backupComp->InitializeForBackup()); //throw ComError
+ ZEN_COM_CHECK(backupComp->SetBackupState(false, false, VSS_BT_FULL)); //throw ComError
auto waitForComFuture = [](IVssAsync& fut)
{
- ZEN_CHECK_COM(fut.Wait());
+ ZEN_COM_CHECK(fut.Wait());
HRESULT hr = S_OK;
- ZEN_CHECK_COM(fut.QueryStatus(&hr, nullptr)); //check if the async operation succeeded...
+ ZEN_COM_CHECK(fut.QueryStatus(&hr, nullptr)); //check if the async operation succeeded...
if (FAILED(hr))
throw ComError(L"Error calling \"fut->QueryStatus\".", hr);
};
ComPtr<IVssAsync> gatherAsync;
- ZEN_CHECK_COM(backupComp->GatherWriterMetadata(gatherAsync.init()));
+ ZEN_COM_CHECK(backupComp->GatherWriterMetadata(gatherAsync.init()));
waitForComFuture(*gatherAsync); //failure can happen if XP-version of VSS is used on Windows Vista (which needs at least VSS-Server2003 build)
VSS_ID snapshotSetId = {};
- ZEN_CHECK_COM(backupComp->StartSnapshotSet(&snapshotSetId));
+ ZEN_COM_CHECK(backupComp->StartSnapshotSet(&snapshotSetId));
ScopeGuard guardSnapShot = makeGuard([&] { backupComp->AbortBackup(); });
//Quote: "This method must be called if a backup operation terminates after the creation of a
//shadow copy set with "StartSnapshotSet" and before "DoSnapshotSet" returns."
@@ -127,16 +127,16 @@ shadow::ShadowData createShadowCopy(const wchar_t* volumeName) //throw ComError
}
ComPtr<IVssAsync> prepareAsync;
- ZEN_CHECK_COM(backupComp->PrepareForBackup(prepareAsync.init()));
+ ZEN_COM_CHECK(backupComp->PrepareForBackup(prepareAsync.init()));
waitForComFuture(*prepareAsync);
ComPtr<IVssAsync> snapshotAsync;
- ZEN_CHECK_COM(backupComp->DoSnapshotSet(snapshotAsync.init()));
+ ZEN_COM_CHECK(backupComp->DoSnapshotSet(snapshotAsync.init()));
guardSnapShot.dismiss();
waitForComFuture(*snapshotAsync);
VSS_SNAPSHOT_PROP props = {};
- ZEN_CHECK_COM(backupComp->GetSnapshotProperties(SnapShotId, &props));
+ ZEN_COM_CHECK(backupComp->GetSnapshotProperties(SnapShotId, &props));
ZEN_ON_SCOPE_EXIT(::VssFreeSnapshotProperties(&props));
//finally: write volume name of newly created shadow copy
diff --git a/lib/Thumbnail/Thumbnail.vcxproj b/lib/Thumbnail/Thumbnail.vcxproj
index de9b22ae..2045f10e 100644
--- a/lib/Thumbnail/Thumbnail.vcxproj
+++ b/lib/Thumbnail/Thumbnail.vcxproj
@@ -64,10 +64,10 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
- <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">OBJ\</OutDir>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\</OutDir>
<IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
<LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</LinkIncremental>
- <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">OBJ\</OutDir>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\</OutDir>
<IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">OBJ\$(ProjectName)_$(Configuration)_$(Platform)\</IntDir>
<LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</LinkIncremental>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\</OutDir>
@@ -87,7 +87,7 @@
</BuildLog>
<ClCompile>
<Optimization>Disabled</Optimization>
- <PreprocessorDefinitions>FFS_WIN;_DEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>FFS_WIN;FFS_WIN;_DEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>true</MinimalRebuild>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
@@ -122,7 +122,7 @@
</Midl>
<ClCompile>
<Optimization>Disabled</Optimization>
- <PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>FFS_WIN;_DEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>true</MinimalRebuild>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
@@ -155,7 +155,7 @@
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
- <PreprocessorDefinitions>FFS_WIN;NDEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>FFS_WIN;FFS_WIN;NDEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader>
@@ -192,7 +192,7 @@
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
- <PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>FFS_WIN;NDEBUG;_WINDOWS;_USRDLL;THUMBNAIL_DLL_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader>
diff --git a/lib/Thumbnail/thumbnail.cpp b/lib/Thumbnail/thumbnail.cpp
index 284d1093..b00ce81d 100644
--- a/lib/Thumbnail/thumbnail.cpp
+++ b/lib/Thumbnail/thumbnail.cpp
@@ -5,169 +5,481 @@
// **************************************************************************
#include "thumbnail.h"
+#include <algorithm>
#include <string>
#define WIN32_LEAN_AND_MEAN
#include <zen/win.h>
+#include <zen/win_ver.h>
#define STRICT_TYPED_ITEMIDS //better type safety for IDLists
#include <Shlobj.h>
#include <Shellapi.h>
#include <CommonControls.h>
+
+#include <zen/com_error.h>
#include <zen/com_ptr.h>
#include <zen/string_tools.h>
#include <zen/scope_guard.h>
+#include <zen/basic_math.h>
+//#include <zen/perf.h>
using namespace zen;
+namespace
+{
+thumb::ImageData* allocImageData(int width, int height) //throw ComError; return value always bound!
+{
+ ZEN_COM_ASSERT(width >= 0 && height >= 0); //throw ComError
+
+ std::unique_ptr<thumb::ImageData> idata = make_unique<thumb::ImageData>();
+
+ idata->width = width;
+ idata->height = height;
+ idata->rgb = new unsigned char[width * height * 4];
+ idata->alpha = idata->rgb + width * height * 3;
+
+ return idata.release();
+}
-thumb::HICON thumb::getThumbnail(const wchar_t* filename, int requestedSize) //return 0 on failure, caller takes ownership!
+void releaseImageData_impl(const thumb::ImageData* id)
{
- const std::wstring filenameStr(filename);
+ if (id)
+ {
+ delete [] id->rgb;
+ delete id;
+ }
+}
- ComPtr<IShellFolder> desktopFolder;
+
+//caller takes ownership!
+HICON createIconFromBitmap(HBITMAP bitmap) //throw ComError
+{
+ BITMAP bmpInfo = {};
+ ZEN_COM_ASSERT(::GetObject(bitmap, //__in HGDIOBJ hgdiobj,
+ sizeof(bmpInfo), //__in int cbBuffer,
+ &bmpInfo)); //__out LPVOID lpvObject
+ //no documented extended error info
+
+ HDC hScreenDC = ::GetDC(nullptr);
+ ZEN_COM_ASSERT(hScreenDC); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::ReleaseDC(nullptr, hScreenDC));
+
+ HBITMAP bitmapMask = ::CreateCompatibleBitmap(hScreenDC, bmpInfo.bmWidth, bmpInfo.bmHeight);
+ ZEN_COM_ASSERT(bitmapMask); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::DeleteObject(bitmapMask));
+
+ ICONINFO iconInfo = {};
+ iconInfo.fIcon = true;
+ iconInfo.hbmColor = bitmap;
+ iconInfo.hbmMask = bitmapMask;
+
+ HICON result = ::CreateIconIndirect(&iconInfo);
+ if (!result) throw ComError(L"Error calling \"CreateIconIndirect\".", GetLastError());
+ return result;
+}
+
+
+//caller takes ownership!
+thumb::ImageData* convertToImageData(HBITMAP bmp) //throw ComError
+{
+ //GetDIBits ????
+
+ BITMAP bmpInfo = {};
+ ZEN_COM_ASSERT(::GetObject(bmp, //__in HGDIOBJ hgdiobj,
+ sizeof(BITMAP), //__in int cbBuffer,
+ &bmpInfo)); //__out LPVOID lpvObject
+
+ HDC hScreenDC = ::GetDC(nullptr);
+ ZEN_COM_ASSERT(hScreenDC); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::ReleaseDC(nullptr, hScreenDC));
+
+ //32-bit RGB with alpha channel support
+ BITMAPV5HEADER bi = {};
+ bi.bV5Size = sizeof(bi);
+ bi.bV5Width = bmpInfo.bmWidth;
+ bi.bV5Height = -bmpInfo.bmHeight; //negative for top left origin
+ bi.bV5Planes = 1;
+ bi.bV5BitCount = 32;
+ bi.bV5Compression = BI_BITFIELDS;
+ bi.bV5AlphaMask = 0xFF000000;
+ bi.bV5RedMask = 0x00FF0000;
+ bi.bV5GreenMask = 0x0000FF00;
+ bi.bV5BlueMask = 0x000000FF;
+ unsigned char* bitsRgbBmp = nullptr;
+
+ HBITMAP rgbBmp = ::CreateDIBSection(hScreenDC, //_In_ HDC hdc,
+ reinterpret_cast<const BITMAPINFO*>(&bi), //_In_ const BITMAPINFO *pbmi,
+ DIB_RGB_COLORS, //_In_ UINT iUsage,
+ reinterpret_cast<VOID**>(&bitsRgbBmp), //_Out_ VOID **ppvBits,
+ nullptr, //_In_ HANDLE hSection,
+ 0); //_In_ DWORD dwOffset
+ ZEN_COM_ASSERT(rgbBmp); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::DeleteObject(rgbBmp););
+ ZEN_COM_ASSERT(bitsRgbBmp); //check after rgbBmp is owned by us
+
+ HDC memDCSrc = ::CreateCompatibleDC(hScreenDC);
+ ZEN_COM_ASSERT(memDCSrc); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::DeleteDC(memDCSrc));
+
+ HDC memDCTrg = ::CreateCompatibleDC(hScreenDC);
+ ZEN_COM_ASSERT(memDCTrg); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::DeleteDC(memDCTrg));
+
+ HGDIOBJ hgdiSrcOld = ::SelectObject(memDCSrc, bmp);
+ ZEN_COM_ASSERT(hgdiSrcOld); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::SelectObject(memDCSrc, hgdiSrcOld));
+
+ HGDIOBJ hgdiTrgOld = ::SelectObject(memDCTrg, rgbBmp);
+ ZEN_COM_ASSERT(hgdiTrgOld); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::SelectObject(memDCTrg, hgdiTrgOld));
+
+ if (!::BitBlt(memDCTrg, //_In_ HDC hdcDest,
+ 0, //_In_ int nXDest,
+ 0, //_In_ int nYDest,
+ bmpInfo.bmWidth, //_In_ int nWidth,
+ bmpInfo.bmHeight, //_In_ int nHeight,
+ memDCSrc, //_In_ HDC hdcSrc,
+ 0, //_In_ int nXSrc,
+ 0, //_In_ int nYSrc,
+ SRCCOPY)) //_In_ DWORD dwRop
+ throw ComError(L"Error calling \"BitBlt\".", GetLastError());
+
+ //CreateDIBSection: "Access to the bitmap must be synchronized. [...]. This applies to any use of the pointer to the bitmap bit values."
+ /*bool rv = */
+ ::GdiFlush();
+
+ thumb::ImageData* imgOut = allocImageData(bmpInfo.bmWidth, bmpInfo.bmHeight); //throw ComError
+ ScopeGuard guardImgData = zen::makeGuard([&] { releaseImageData_impl(imgOut); });
+
+ unsigned char* rgbPtr = imgOut->rgb;
+ unsigned char* alphaPtr = imgOut->alpha;
+
+ for (int i = 0; i < bmpInfo.bmWidth * bmpInfo.bmHeight; ++i)
{
- HRESULT hr = ::SHGetDesktopFolder(desktopFolder.init());
- if (FAILED(hr) || !desktopFolder)
- return nullptr;
+ unsigned char b = *bitsRgbBmp++;
+ unsigned char g = *bitsRgbBmp++;
+ unsigned char r = *bitsRgbBmp++;
+ unsigned char a = *bitsRgbBmp++;
+
+ *rgbPtr++ = r;
+ *rgbPtr++ = g;
+ *rgbPtr++ = b;
+ *alphaPtr++ = a;
}
+ guardImgData.dismiss();
+ return imgOut;
+}
+
+
+//caller takes ownership!
+const thumb::ImageData* getThumbnail_impl(const wchar_t* filename, int requestedSize) //throw ComError
+{
+ const std::wstring filenameStr(filename);
+
+ ComPtr<IShellFolder> desktopFolder;
+ ZEN_COM_CHECK(::SHGetDesktopFolder(desktopFolder.init())); //throw ComError
+ ZEN_COM_ASSERT(desktopFolder); //throw ComError -> better safe than sorry?
+
PIDLIST_RELATIVE pidlFolder = nullptr;
{
- const std::wstring& pathName = beforeLast(filenameStr, L'\\');
- HRESULT hr = desktopFolder->ParseDisplayName(nullptr, // [in] HWND hwnd,
- nullptr, // [in] IBindCtx *pbc,
- const_cast<LPWSTR>(pathName.c_str()), // [in] LPWSTR pszDisplayName,
- nullptr, // [out] ULONG *pchEaten,
- &pidlFolder, // [out] PIDLIST_RELATIVE* ppidl,
- nullptr); // [in, out] ULONG *pdwAttributes
- if (FAILED(hr) || !pidlFolder)
- return nullptr;
+ std::wstring pathName = beforeLast(filenameStr, L'\\');
+ ZEN_COM_CHECK(desktopFolder->ParseDisplayName(nullptr, // [in] HWND hwnd,
+ nullptr, // [in] IBindCtx *pbc,
+ const_cast<LPWSTR>(pathName.c_str()), // [in] LPWSTR pszDisplayName,
+ nullptr, // [out] ULONG *pchEaten,
+ &pidlFolder, // [out] PIDLIST_RELATIVE* ppidl,
+ nullptr)); // [in, out] ULONG *pdwAttributes
}
+ ZEN_COM_ASSERT(pidlFolder);
ZEN_ON_SCOPE_EXIT(::ILFree(pidlFolder)); //older version: ::CoTaskMemFree
ComPtr<IShellFolder> imageFolder;
- {
- HRESULT hr = desktopFolder->BindToObject(pidlFolder, // [in] PCUIDLIST_RELATIVE pidl,
- nullptr, // [in] IBindCtx *pbc,
- IID_PPV_ARGS(imageFolder.init()));
- if (FAILED(hr) || !imageFolder)
- return nullptr;
- }
+ ZEN_COM_CHECK(desktopFolder->BindToObject(pidlFolder, // [in] PCUIDLIST_RELATIVE pidl,
+ nullptr, // [in] IBindCtx *pbc,
+ IID_PPV_ARGS(imageFolder.init())));
+ ZEN_COM_ASSERT(imageFolder);
PIDLIST_RELATIVE pidImage = nullptr;
{
- const std::wstring& shortName = afterLast(filenameStr, L'\\');
- HRESULT hr = imageFolder->ParseDisplayName(nullptr, // [in] HWND hwnd,
- nullptr, // [in] IBindCtx *pbc,
- const_cast<LPWSTR>(shortName.c_str()), // [in] LPWSTR pszDisplayName,
- nullptr, // [out] ULONG *pchEaten,
- &pidImage, // [out] PIDLIST_RELATIVE *ppidl,
- nullptr); // [in, out] ULONG *pdwAttributes
- if (FAILED(hr) || !pidImage)
- return nullptr;
+ std::wstring shortName = afterLast(filenameStr, L'\\');
+ ZEN_COM_CHECK(imageFolder->ParseDisplayName(nullptr, // [in] HWND hwnd,
+ nullptr, // [in] IBindCtx *pbc,
+ const_cast<LPWSTR>(shortName.c_str()), // [in] LPWSTR pszDisplayName,
+ nullptr, // [out] ULONG *pchEaten,
+ &pidImage, // [out] PIDLIST_RELATIVE *ppidl,
+ nullptr)); // [in, out] ULONG *pdwAttributes
}
+ ZEN_COM_ASSERT(pidImage);
ZEN_ON_SCOPE_EXIT(::ILFree(pidImage)); //older version: ::CoTaskMemFree
ComPtr<IExtractImage> extractImage;
- {
- PCUITEMID_CHILD_ARRAY pidlIn = reinterpret_cast<PCUITEMID_CHILD_ARRAY>(&pidImage);
- //this is where STRICT_TYPED_ITEMIDS gets us ;)
-
- HRESULT hr = imageFolder->GetUIObjectOf(nullptr, // [in] HWND hwndOwner,
- 1, // [in] UINT cidl,
- pidlIn, // [in] PCUITEMID_CHILD_ARRAY apidl,
- IID_IExtractImage, // [in] REFIID riid,
- nullptr, // [in, out] UINT *rgfReserved,
- reinterpret_cast<void**>(extractImage.init())); // [out] void **ppv
- if (FAILED(hr) || !extractImage)
- return nullptr;
- }
+ ZEN_COM_CHECK(imageFolder->GetUIObjectOf(nullptr, // [in] HWND hwndOwner,
+ 1, // [in] UINT cidl,
+ reinterpret_cast<PCUITEMID_CHILD_ARRAY>(&pidImage), // [in] PCUITEMID_CHILD_ARRAY apidl,
+ //this is where STRICT_TYPED_ITEMIDS gets us ;)
+ IID_IExtractImage, // [in] REFIID riid,
+ nullptr, // [in, out] UINT *rgfReserved,
+ reinterpret_cast<void**>(extractImage.init()))); // [out] void **ppv
+ ZEN_COM_ASSERT(extractImage);
{
wchar_t pathBuffer[MAX_PATH];
DWORD priority = 0;
- const SIZE prgSize = { requestedSize, requestedSize }; //preferred size only!
- DWORD clrDepth = 32;
+ const SIZE prgSize = { requestedSize, requestedSize };
+ DWORD clrDepth = 32; //"recommended color depth"
DWORD flags = IEIFLAG_SCREEN | IEIFLAG_OFFLINE;
- HRESULT hr = extractImage->GetLocation(pathBuffer, // [out] LPWSTR pszPathBuffer,
- MAX_PATH, // [in] DWORD cchMax,
- &priority, // [out] DWORD *pdwPriority,
- &prgSize, // [in] const SIZE *prgSize,
- clrDepth, // [in] DWORD dwRecClrDepth,
- &flags); // [in, out] DWORD *pdwFlags
- if (FAILED(hr))
- return nullptr;
+ ZEN_COM_CHECK(extractImage->GetLocation(pathBuffer, // [out] LPWSTR pszPathBuffer,
+ MAX_PATH, // [in] DWORD cchMax,
+ &priority, // [out] DWORD *pdwPriority,
+ &prgSize, // [in] const SIZE *prgSize,
+ clrDepth, // [in] DWORD dwRecClrDepth,
+ &flags)); // [in, out] DWORD *pdwFlags
}
HBITMAP bitmap = nullptr;
+ ZEN_COM_CHECK(extractImage->Extract(&bitmap));
+ ZEN_COM_ASSERT(bitmap);
+ ZEN_ON_SCOPE_EXIT(::DeleteObject(bitmap));
+
+ return convertToImageData(bitmap); //throw ComError, pass ownership
+}
+
+
+const bool wereVistaOrLater = vistaOrLater(); //thread-safety: init at startup
+
+//caller takes ownership!
+const thumb::ImageData* getIconByIndex_impl(int iconIndex, thumb::IconSizeType st) //throw ComError
+{
+ //Note:
+ //- using IExtractIcon::Extract is *no* alternative, just as ::SHGetFileInfo(), it only supports small (16x16) and large (32x32) icons
+ //- IShellItemImageFactory::GetImage requires Vista or later
+
+ using namespace thumb;
+ int requestedSize = 16;
+ int shilIconType = SHIL_SMALL; //16x16, size can be customized by the user.
{
- HRESULT hr = extractImage->Extract(&bitmap);
- if (FAILED(hr) || !bitmap)
- return nullptr;
+ if (!wereVistaOrLater && //XP doesn't have jumbo icons
+ (st == ICON_SIZE_128 ||
+ st == ICON_SIZE_256))
+ st = ICON_SIZE_48;
+
+ switch (st)
+ {
+ case ICON_SIZE_16:
+ break;
+ case ICON_SIZE_32:
+ requestedSize = 32;
+ shilIconType = SHIL_LARGE; //32x32, may be 48x48 if "Use large icon" option is set in Display Properties
+ break;
+ case ICON_SIZE_48:
+ requestedSize = 48;
+ shilIconType = SHIL_EXTRALARGE; //48x48, size can be customized by the user.
+ break;
+ case ICON_SIZE_128:
+ requestedSize = 128;
+ shilIconType = SHIL_JUMBO; //256x256 pixels -> scale down!
+ break;
+ case ICON_SIZE_256:
+ requestedSize = 256;
+ shilIconType = SHIL_JUMBO; //256x256 pixels; Vista and later only
+ break;
+ }
}
- ZEN_ON_SCOPE_EXIT(::DeleteObject(bitmap));
+ ComPtr<IImageList> imageList; //perf: 0,12 µs only to get the image list
+ ZEN_COM_CHECK(::SHGetImageList(shilIconType, //__in int iImageList,
+ IID_PPV_ARGS(imageList.init())));
+ ZEN_COM_ASSERT(imageList);
- BITMAP bmpInfo = {};
- if (::GetObject(bitmap, //__in HGDIOBJ hgdiobj,
- sizeof(bmpInfo), //__in int cbBuffer,
- &bmpInfo) == 0) //__out LPVOID lpvObject
- return nullptr;
+ int srcWidth = 0;
+ int srcHeight = 0;
+ ZEN_COM_CHECK(imageList->GetIconSize(&srcWidth, &srcHeight));
- HDC hDC = ::GetDC(nullptr);
- if (!hDC)
- return nullptr;
- ZEN_ON_SCOPE_EXIT(::ReleaseDC(nullptr, hDC));
+ int targetWidth = srcWidth;
+ int targetHeight = srcHeight;
+ bool needDownScale = false; //scale down if required (e.g Vista Jumbo icons/user-customized icon sizes)
- HBITMAP bitmapMask = ::CreateCompatibleBitmap(hDC, bmpInfo.bmWidth, bmpInfo.bmHeight);
- if (!bitmapMask)
- return nullptr;
- ZEN_ON_SCOPE_EXIT(::DeleteObject(bitmapMask));
+ ZEN_COM_ASSERT(srcWidth > 0 && srcHeight > 0 && requestedSize > 0);
- ICONINFO iconInfo = {};
- iconInfo.fIcon = true;
- iconInfo.hbmColor = bitmap;
- iconInfo.hbmMask = bitmapMask;
+ const int maxExtent = std::max(srcWidth, srcHeight);
+ if (requestedSize < maxExtent)
+ {
+ needDownScale = true;
+ targetWidth = srcWidth * requestedSize / maxExtent;
+ targetHeight = srcHeight * requestedSize / maxExtent;
+ }
- return ::CreateIconIndirect(&iconInfo);
-}
+ HDC hScreenDC = ::GetDC(nullptr);
+ ZEN_COM_ASSERT(hScreenDC); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::ReleaseDC(nullptr, hScreenDC));
+ auto createRGBDib = [&](unsigned char** rawBits) -> HBITMAP
+ {
+ BITMAPINFO bi = {};
+ bi.bmiHeader.biSize = sizeof(bi);
+ bi.bmiHeader.biWidth = targetWidth;
+ bi.bmiHeader.biHeight = -targetHeight; //negative for top left origin
+ bi.bmiHeader.biPlanes = 1;
+ bi.bmiHeader.biBitCount = 24; //we don't want an alpha channel
+ bi.bmiHeader.biCompression = BI_RGB;
-thumb::HICON thumb::getIconByIndex(int iconIndex, int shilIconType) //return 0 on failure, caller takes ownership!
-{
- //Note: using IExtractIcon::Extract is *no* alternative, just as ::SHGetFileInfo(), it only supports small (16x16) and large (32x32) icons
+ return ::CreateDIBSection(hScreenDC, //_In_ HDC hdc,
+ &bi, //_In_ const BITMAPINFO *pbmi,
+ DIB_RGB_COLORS, //_In_ UINT iUsage,
+ reinterpret_cast<VOID**>(rawBits), //_Out_ VOID **ppvBits,
+ nullptr, //_In_ HANDLE hSection,
+ 0); //_In_ DWORD dwOffset
+ };
- ComPtr<IImageList> imageList; //perf: 0,12 µs only to get the image list
+ unsigned char* bitsBlackBg = nullptr;
+ HBITMAP bmpBlackBg = createRGBDib(&bitsBlackBg);
+ ZEN_COM_ASSERT(bmpBlackBg); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::DeleteObject(bmpBlackBg););
+ ZEN_COM_ASSERT(bitsBlackBg); //check after bmpBlackBg is owned by us
+
+ HDC memDC = ::CreateCompatibleDC(hScreenDC);
+ ZEN_COM_ASSERT(memDC); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::DeleteDC(memDC));
+
+ HGDIOBJ hgdiOld = ::SelectObject(memDC, bmpBlackBg);
+ ZEN_COM_ASSERT(hgdiOld); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::SelectObject(memDC, hgdiOld));
+
+ IMAGELISTDRAWPARAMS drawParams = {};
+ drawParams.cbSize = sizeof(drawParams);
+ drawParams.hdcDst = memDC;
+ drawParams.i = iconIndex;
+ drawParams.rgbBk = 0x000000; //black
+ drawParams.fStyle = ILD_NORMAL;
+ //other flags: http://msdn.microsoft.com/en-us/library/windows/desktop/bb775230(v=vs.85).aspx
+
+ if (needDownScale)
{
- HRESULT hr = ::SHGetImageList(shilIconType, //__in int iImageList,
- IID_PPV_ARGS(imageList.init()));
- if (FAILED(hr) || !imageList)
- return nullptr;
+ drawParams.fStyle |= ILD_SCALE;
+ drawParams.cx = targetWidth;
+ drawParams.cy = targetHeight;
}
- bool hasAlpha = false; //perf: 0,14 µs
+ //IDO_SHGIOI_LINK does not draw properly in some cases:
+ //Win7: draws link overlay *twice* if SHIL_JUMBO is requested, but icon does not have this size
+ //XP: drawing IDO_SHGIOI_LINK generally draws corrupted icons and links
+ //if (addShortcutOverlay)
+ //{
+ // int linkOverlay = ::SHGetIconOverlayIndex(nullptr, IDO_SHGIOI_LINK); //-1 on error
+ // if (linkOverlay != -1)
+ // {
+ // //int imgIndex = 0;
+ // //if (SUCCEEDED(imageList->GetOverlayImage(linkOverlay, &imgIndex)))
+ // //{
+ // // drawParams.i = imgIndex;
+ // // ZEN_COM_CHECK(imageList->Draw(&drawParams));
+ // //}
+
+ // drawParams.fStyle |= INDEXTOOVERLAYMASK(linkOverlay);
+ // }
+ //}
+
+ ZEN_COM_CHECK(imageList->Draw(&drawParams));
+
+ //-----------------------------------------------
+ //we draw the icon twice on different backgrounds to extract the alpha channel:
+ //- IImageList::GetIcon doesn't properly render SHIL_JUMBO for icons that don't have jumbo sizes, but IImageList::Draw does!
+ //- minor: each HICON consumes 3 GDI handles
+ //- IImageList::Draw does not reliably support alpha channel on device context (Windows XP)
+ //- wxBitmap created from HBITMAP is very unreliable; often drawn incorrectly by wxDC::DrawBitmap => support wxImage instead
+
+ unsigned char* bitsWhiteBg = nullptr;
+ HBITMAP bmpWhiteBg = createRGBDib(&bitsWhiteBg);
+ ZEN_COM_ASSERT(bmpWhiteBg); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::DeleteObject(bmpWhiteBg));
+ ZEN_COM_ASSERT(bitsWhiteBg); //check after bmpWhiteBg is owned by us
+
+ HGDIOBJ hgdiOld2 = ::SelectObject(memDC, bmpWhiteBg);
+ ZEN_COM_ASSERT(hgdiOld2); //no documented extended error info
+ ZEN_ON_SCOPE_EXIT(::SelectObject(memDC, hgdiOld2));
+
+ drawParams.rgbBk = 0xFFFFFF; //white
+
+ ZEN_COM_CHECK(imageList->Draw(&drawParams));
+
+ //#####################################################################################
+
+ //"Access to the bitmap must be synchronized. [...]. This applies to any use of the pointer to the bitmap bit values."
+ /*bool rv = */
+ ::GdiFlush();
+
+ ImageData* imgOut = allocImageData(targetWidth, targetHeight); //throw ComError
+ ScopeGuard guardImgData = zen::makeGuard([&] { releaseImageData_impl(imgOut); });
+
+ unsigned char* rgbPtr = imgOut->rgb;
+ unsigned char* alphaPtr = imgOut->alpha;
+
+ for (int i = 0; i < targetWidth * targetHeight; ++i)
+ {
+ unsigned char b_black = *bitsBlackBg++;
+ unsigned char g_black = *bitsBlackBg++;
+ unsigned char r_black = *bitsBlackBg++;
+
+ unsigned char b_white = *bitsWhiteBg++;
+ unsigned char g_white = *bitsWhiteBg++;
+ unsigned char r_white = *bitsWhiteBg++;
+
+ const int tmp = 255 + r_black - r_white + //mixed mode arithmetics!
+ 255 + g_black - g_white +
+ 255 + b_black - b_white;
+ unsigned char alpha = static_cast<unsigned char>(numeric::confineCpy(tmp / 3, 0, 255));
+
+ auto calcColor = [&](unsigned char c_black, unsigned char c_white)
+ {
+ return static_cast<unsigned char>(tmp == 0 ? 0 : numeric::confineCpy
+ (255 * (3 * (-255 + c_white + c_black) + tmp) / (2 * tmp), //mixed mode arithmetics!
+ 0, 255));
+ };
+
+ *rgbPtr++ = calcColor(r_black, r_white);
+ *rgbPtr++ = calcColor(g_black, g_white);
+ *rgbPtr++ = calcColor(b_black, b_white);
+ *alphaPtr++ = alpha;
+ }
+
+ guardImgData.dismiss();
+ return imgOut;
+}
+}
+
+
+const thumb::ImageData* thumb::getThumbnail(const wchar_t* filename, int requestedSize) //return 0 on failure, caller takes ownership!
+{
+ try
+ {
+ return getThumbnail_impl(filename, requestedSize); //throw ComError
+ }
+ catch (const ComError&)
{
- DWORD flags = 0;
- HRESULT hr = imageList->GetItemFlags(iconIndex, //[in] int i,
- &flags); //[out] DWORD *dwFlags
- if (SUCCEEDED(hr))
- hasAlpha = flags & ILIF_ALPHA;
+ return nullptr;
}
+}
- ::HICON hIcon = nullptr; //perf: 1,5 ms - the dominant block
+
+const thumb::ImageData* thumb::getIconByIndex(int iconIndex, thumb::IconSizeType st) //return 0 on failure, caller takes ownership!
+{
+ try
{
- HRESULT hr = imageList->GetIcon(iconIndex, // [in] int i,
- (hasAlpha ? ILD_IMAGE : ILD_NORMAL), // [in] UINT flags,
- //ILD_IMAGE -> do not draw mask - fixes glitch with ILD_NORMAL where both mask *and* alpha channel are applied, e.g. for ffs_batch and folder icon
- //other flags: http://msdn.microsoft.com/en-us/library/windows/desktop/bb775230(v=vs.85).aspx
- &hIcon); // [out] HICON *picon
- if (FAILED(hr) || !hIcon)
- return nullptr;
+ return getIconByIndex_impl(iconIndex, st); //throw ComError
}
+ catch (const ComError&)
+ {
+ return nullptr;
+ }
+}
- return hIcon;
-} \ No newline at end of file
+
+void thumb::releaseImageData(const thumb::ImageData* id)
+{
+ releaseImageData_impl(id);
+}
diff --git a/lib/Thumbnail/thumbnail.h b/lib/Thumbnail/thumbnail.h
index b2be2239..307fc7cc 100644
--- a/lib/Thumbnail/thumbnail.h
+++ b/lib/Thumbnail/thumbnail.h
@@ -30,34 +30,49 @@ PREREQUISITES:
/*--------------
|declarations|
--------------*/
-typedef void* HICON;
+struct ImageData //consider alignment!
+{
+ unsigned char* rgb; //rgb-byte order for use with wxImage
+ unsigned char* alpha;
+ int width;
+ int height;
+};
DLL_FUNCTION_DECLARATION
-HICON getThumbnail(const wchar_t* filename, int requestedSize); //return 0 on failure, caller takes ownership!
-//Note: not all file types support thumbnails! make sure to implement fallback to file icon!
+const ImageData* getThumbnail(const wchar_t* filename, int requestedSize); //return nullptr on failure, release after use
+//Note: not all file types support thumbnails! implement fallback to file icon!
+enum IconSizeType
+{
+ ICON_SIZE_16,
+ ICON_SIZE_32,
+ ICON_SIZE_48,
+ ICON_SIZE_128,
+ ICON_SIZE_256,
+};
+//"iconIndex" as returned by ::SHGetFileInfo()
DLL_FUNCTION_DECLARATION
-HICON getIconByIndex(int iconIndex, int shilIconType); //return 0 on failure, caller takes ownership!
-/*
-"iconType" refers to parameter "iImageList" of ::SHGetImageList(); sample values:
- SHIL_SMALL - 16x16, but the size can be customized by the user.
- SHIL_EXTRALARGE - 48x48, but the size can be customized by the user.
- SHIL_JUMBO - 256x256 pixels; Vista and later only
-"iconIndex" as returned by ::SHGetFileInfo()
-*/
+const ImageData* getIconByIndex(int iconIndex, IconSizeType st); //return nullptr on failure, release after use
+
+DLL_FUNCTION_DECLARATION
+void releaseImageData(const ImageData* id);
+
/*----------
|typedefs|
----------*/
-typedef HICON (*FunType_getThumbnail )(const wchar_t* filename, int requestedSize);
-typedef HICON (*FunType_getIconByIndex)(int iconIndex, int shilIconType);
+typedef const ImageData* (*FunType_getThumbnail )(const wchar_t* filename, int requestedSize);
+typedef const ImageData* (*FunType_getIconByIndex )(int iconIndex, IconSizeType st);
+typedef void (*FunType_releaseImageData)(const ImageData* id);
+
/*--------------
|symbol names|
--------------*/
//(use const pointers to ensure internal linkage)
-const char funName_getThumbnail [] = "getThumbnail";
-const char funName_getIconByIndex[] = "getIconByIndex";
+const char funName_getThumbnail [] = "getThumbnail";
+const char funName_getIconByIndex [] = "getIconByIndex";
+const char funName_releaseImageData[] = "releaseImageData";
/*---------------
|library names|
diff --git a/lib/dir_exist_async.h b/lib/dir_exist_async.h
index 33af8fa8..678a0235 100644
--- a/lib/dir_exist_async.h
+++ b/lib/dir_exist_async.h
@@ -13,6 +13,8 @@
#include "process_callback.h"
#include "resolve_path.h"
+namespace zen
+{
namespace
{
//directory existence checking may hang for non-existent network drives => run asynchronously and update UI!
@@ -57,8 +59,9 @@ std::set<Zstring, LessFilename> getExistingDirsUpdating(const std::vector<Zstrin
}
return output;
}
+}
-
+inline //also silences Clang "unused function" for compilation units depending from getExistingDirsUpdating() only
bool dirExistsUpdating(const Zstring& dirname, bool allowUserInteraction, ProcessCallback& procCallback)
{
std::vector<Zstring> dirnames;
diff --git a/lib/ffs_paths.cpp b/lib/ffs_paths.cpp
index 3a0b557d..82232b5c 100644
--- a/lib/ffs_paths.cpp
+++ b/lib/ffs_paths.cpp
@@ -12,7 +12,9 @@
#ifdef FFS_MAC
#include <vector>
#include <zen/scope_guard.h>
-#include <CoreServices/CoreServices.h> //keep in .cpp file to not pollute global namespace! e.g. with UInt64
+#include <zen/osx_string.h>
+//keep in .cpp file to not pollute global namespace! e.g. with UInt64:
+#include <ApplicationServices/ApplicationServices.h> //LSFindApplicationForInfo
#endif
using namespace zen;
@@ -102,26 +104,11 @@ Zstring zen::getFreeFileSyncLauncher()
{
#ifdef FFS_WIN
return getInstallDir() + Zstr("FreeFileSync.exe");
+
#elif defined FFS_LINUX
return getExecutableDir() + Zstr("FreeFileSync");
-#elif defined FFS_MAC
- auto CFStringRefToUtf8 = [](const CFStringRef& cfs) -> Zstring
- {
- if (cfs)
- {
- CFIndex length = ::CFStringGetLength(cfs);
- if (length > 0)
- {
- CFIndex bufferSize = ::CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8);
- std::vector<char> buffer(bufferSize);
-
- if (::CFStringGetCString(cfs, &buffer[0], bufferSize, kCFStringEncodingUTF8))
- return Zstring(&buffer[0]);
- }
- }
- return Zstring();
- };
+#elif defined FFS_MAC
CFURLRef appURL = nullptr;
ZEN_ON_SCOPE_EXIT(if (appURL) ::CFRelease(appURL));
@@ -138,7 +125,7 @@ Zstring zen::getFreeFileSyncLauncher()
if (CFStringRef path = ::CFURLCopyFileSystemPath(absUrl, kCFURLPOSIXPathStyle))
{
ZEN_ON_SCOPE_EXIT(::CFRelease(path));
- return appendSeparator(CFStringRefToUtf8(path)) + "Contents/MacOS/FreeFileSync";
+ return appendSeparator(osx::cfStringToZstring(path)) + "Contents/MacOS/FreeFileSync";
}
}
return Zstr("./FreeFileSync"); //fallback: at least give some hint...
diff --git a/lib/help_provider.h b/lib/help_provider.h
index 215b7dac..8227efb5 100644
--- a/lib/help_provider.h
+++ b/lib/help_provider.h
@@ -7,8 +7,14 @@
#ifndef HELPPROVIDER_H_INCLUDED
#define HELPPROVIDER_H_INCLUDED
-#include <wx/help.h>
+#ifdef FFS_WIN
#include <zen/zstring.h>
+#include <wx/msw/helpchm.h>
+
+#elif defined FFS_LINUX || defined FFS_MAC
+#include <wx/html/helpctrl.h>
+#endif
+
#include "ffs_paths.h"
namespace zen
@@ -25,45 +31,67 @@ void displayHelpEntry(const wxString& section, wxWindow* parent);
+//######################## implementation ########################
+namespace impl
+{
+//finish wxWidgets' job
+#ifdef FFS_WIN
+class FfsHelpController
+{
+public:
+ FfsHelpController()
+ {
+ chmHlp.Initialize(utfCvrtTo<wxString>(zen::getResourceDir()) + L"FreeFileSync.chm");
+ }
+ void openSection(const wxString& section, wxWindow* parent)
+ {
+ if (section.empty())
+ chmHlp.DisplayContents();
+ else
+ chmHlp.DisplaySection(replaceCpy(section, L'/', utfCvrtTo<wxString>(FILE_NAME_SEPARATOR)));
+ }
+private:
+ wxCHMHelpController chmHlp;
+};
+#elif defined FFS_LINUX || defined FFS_MAC
+class FfsHelpController
+{
+public:
+ void openSection(const wxString& section, wxWindow* parent)
+ {
+ wxHtmlModalHelp dlg(parent, utfCvrtTo<wxString>(zen::getResourceDir()) + L"Help/FreeFileSync.hhp", section,
+ wxHF_DEFAULT_STYLE | wxHF_DIALOG | wxHF_MODAL | wxHF_MERGE_BOOKS);
+ (void)dlg;
+ //-> solves modal help craziness on OSX!
+ //-> Suse Linux: avoids program hang on exit if user closed help parent dialog before the help dialog itself was closed (why is this even possible???)
+ // avoids ESC key not being recognized by help dialog (but by parent dialog instead)
+ }
+};
+#endif
-//######################## implementation ########################
inline
-wxHelpController& getHelpCtrl()
+FfsHelpController& getHelpCtrl()
{
- static wxHelpController controller; //external linkage, despite inline definition!
- static bool initialized = false;
- if (!initialized)
- {
- initialized = true;
- controller.Initialize(utfCvrtTo<wxString>(zen::getResourceDir()) +
-#ifdef FFS_WIN
- L"FreeFileSync.chm");
-#elif defined FFS_LINUX || defined FFS_MAC
- L"Help/FreeFileSync.hhp");
-#endif
- }
- return controller;
+ static FfsHelpController ctrl; //external linkage, despite inline definition!
+ return ctrl;
+}
}
inline
void displayHelpEntry(const wxString& section, wxWindow* parent)
{
- getHelpCtrl().SetParentWindow(parent); //this nicely solves modal issues on OSX with help file going to the background
- getHelpCtrl().DisplaySection(replaceCpy(section, L'/', utfCvrtTo<wxString>(FILE_NAME_SEPARATOR)));
- getHelpCtrl().SetParentWindow(nullptr);
+ impl::getHelpCtrl().openSection(section, parent);
}
inline
void displayHelpEntry(wxWindow* parent)
{
- getHelpCtrl().SetParentWindow(parent);
- getHelpCtrl().DisplayContents();
- getHelpCtrl().SetParentWindow(nullptr);
+ impl::getHelpCtrl().openSection(wxString(), parent);
}
}
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!
+}
diff --git a/lib/icon_buffer.h b/lib/icon_buffer.h
index ba34faa2..efa5179f 100644
--- a/lib/icon_buffer.h
+++ b/lib/icon_buffer.h
@@ -7,11 +7,11 @@
#ifndef ICONBUFFER_H_INCLUDED
#define ICONBUFFER_H_INCLUDED
+#include <list>
#include <memory>
-#include <wx/bitmap.h>
-#include <wx/icon.h>
#include <zen/zstring.h>
-
+#include <zen/optional.h>
+#include <wx/bitmap.h>
namespace zen
{
@@ -28,22 +28,24 @@ public:
IconBuffer(IconSize sz);
~IconBuffer();
- static int getSize(IconSize icoSize); //*maximum* icon size in pixel
- int getSize() const { return getSize(icoSize); } //
+ static int getSize(IconSize icoSizeType); //expected and *maximum* icon size in pixel
+ int getSize() const { return getSize(iconSizeType); } //
+
+ const wxBitmap& genericFileIcon() { return genFileIcon; }
+ const wxBitmap& genericDirIcon () { return genDirIcon; }
- const wxIcon& genericFileIcon() { return genFileIcon; }
- const wxIcon& genericDirIcon () { return genDirIcon; }
+ bool readyForRetrieval(const Zstring& filename);
+ Opt<wxBitmap> retrieveFileIcon(const Zstring& filename);
- bool requestFileIcon(const Zstring& filename, wxIcon* icon = nullptr); //returns false if icon is not in buffer
- void setWorkload(const std::vector<Zstring>& load); //(re-)set new workload of icons to be retrieved;
+ void setWorkload(const std::list<Zstring>& load); //(re-)set new workload of icons to be retrieved;
private:
struct Pimpl;
std::unique_ptr<Pimpl> pimpl;
- const IconSize icoSize;
- const wxIcon genDirIcon;
- const wxIcon genFileIcon;
+ const IconSize iconSizeType;
+ const wxBitmap genDirIcon;
+ const wxBitmap genFileIcon;
};
}
diff --git a/lib/localization.cpp b/lib/localization.cpp
index 33494cf4..c29860f7 100644
--- a/lib/localization.cpp
+++ b/lib/localization.cpp
@@ -5,7 +5,6 @@
// **************************************************************************
#include "localization.h"
-//#include <fstream>
#include <map>
#include <list>
#include <iterator>
@@ -15,7 +14,6 @@
#include <zen/i18n.h>
#include <zen/format_unit.h>
#include <wx/intl.h>
-//#include <wx/msgdlg.h>
#include "parse_plural.h"
#include "parse_lng.h"
#include "ffs_paths.h"
@@ -214,9 +212,9 @@ ExistingTranslations::ExistingTranslations()
locMapping.push_back(newEntry);
}
}
- catch (lngfile::ParsingError&) {} //better not show an error message here; scenario: batch jobs
+ catch (lngfile::ParsingError&) { assert(false); } //better not show an error message here; scenario: batch jobs
}
- catch (...) {}
+ catch (...) { assert(false); }
std::sort(locMapping.begin(), locMapping.end(), LessTranslation());
}
diff --git a/lib/osx_file_icon.h b/lib/osx_file_icon.h
new file mode 100644
index 00000000..1d57a00e
--- /dev/null
+++ b/lib/osx_file_icon.h
@@ -0,0 +1,36 @@
+// **************************************************************************
+// * 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) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
+// **************************************************************************
+
+#ifndef OSX_FILE_ICON_8427508422345342
+#define OSX_FILE_ICON_8427508422345342
+
+#include <vector>
+#include <zen/osx_error.h>
+
+namespace osx
+{
+struct ImageData
+{
+ ImageData(int w, int h) : width(w), height(h), rgb(w * h * 3), alpha(w * h) {}
+ ImageData(ImageData&& tmp) : width(tmp.width), height(tmp.height) { rgb.swap(tmp.rgb); alpha.swap(tmp.alpha); }
+
+ const int width;
+ const int height;
+ std::vector<unsigned char> rgb; //rgb-byte order for use with wxImage
+ std::vector<unsigned char> alpha;
+
+private:
+ ImageData(const ImageData&);
+ ImageData& operator=(const ImageData&);
+};
+
+ImageData getThumbnail(const char* filename, int requestedSize); //throw OsxError
+ImageData getFileIcon (const char* filename, int requestedSize); //throw OsxError
+ImageData getDefaultFileIcon (int requestedSize); //throw OsxError
+ImageData getDefaultFolderIcon(int requestedSize); //throw OsxError
+}
+
+#endif //OSX_FILE_ICON_8427508422345342
diff --git a/lib/osx_file_icon.mm b/lib/osx_file_icon.mm
new file mode 100644
index 00000000..6a068998
--- /dev/null
+++ b/lib/osx_file_icon.mm
@@ -0,0 +1,179 @@
+// **************************************************************************
+// * 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) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
+// **************************************************************************
+
+#include "osx_file_icon.h"
+#include <zen/osx_throw_exception.h>
+#include <zen/scope_guard.h>
+#include <zen/basic_math.h>
+
+namespace
+{
+osx::ImageData extractBytes(NSImage* nsImg, int requestedSize) //throw OsxError; NSException?
+{
+ /*
+ wxBitmap(NSImage*) is not good enough: it calls "[NSBitmapImageRep imageRepWithData:[img TIFFRepresentation]]"
+ => inefficient: TIFFRepresentation converts all contained images of an NSImage
+ => lacking: imageRepWithData extracts the first contained image only!
+ => wxBitmap(NSImage*) is wxCocoa only, deprecated!
+ => wxWidgets generally is not thread-safe so care must be taken to use wxBitmap from main thread only! (e.g. race-condition on non-atomic ref-count!!!)
+
+ -> we need only a single bitmap at a specific size => extract raw bytes for use with wxImage in a thread-safe way!
+ */
+
+ //we choose the Core Graphics solution; for the equivalent App-Kit way see: http://www.cocoabuilder.com/archive/cocoa/193131-is-lockfocus-main-thread-specific.html#193191
+
+ ZEN_OSX_ASSERT(requestedSize > 0);
+ NSRect rectProposed = NSMakeRect(0, 0, requestedSize, requestedSize); //this is merely a hint!
+
+ CGImageRef imgRef = [nsImg CGImageForProposedRect:&rectProposed context:nil hints:nil];
+ ZEN_OSX_ASSERT(imgRef != NULL); //can this fail? not documented; ownership?? not documented!
+
+ const size_t width = CGImageGetWidth (imgRef);
+ const size_t height = CGImageGetHeight(imgRef);
+
+ ZEN_OSX_ASSERT(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;
+ }
+
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+ ZEN_OSX_ASSERT(colorSpace != NULL); //may fail
+ ZEN_ON_SCOPE_EXIT(CGColorSpaceRelease(colorSpace));
+
+ std::vector<unsigned char> buf(trgWidth* trgHeight * 4); //32-bit ARGB, little endian byte order -> already initialized with 0 = fully transparent
+
+ //supported color spaces: https://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_context/dq_context.html#//apple_ref/doc/uid/TP30001066-CH203-BCIBHHBB
+ CGContextRef ctxRef = CGBitmapContextCreate(&buf[0], //void *data,
+ trgWidth, //size_t width,
+ trgHeight, //size_t height,
+ 8, //size_t bitsPerComponent,
+ 4 * trgWidth, //size_t bytesPerRow,
+ colorSpace, //CGColorSpaceRef colorspace,
+ kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little); //CGBitmapInfo bitmapInfo
+ ZEN_OSX_ASSERT(ctxRef != NULL);
+ ZEN_ON_SCOPE_EXIT(CGContextRelease(ctxRef));
+
+ CGContextDrawImage(ctxRef, CGRectMake(0, 0, trgWidth, trgHeight), imgRef); //can this fail? not documented
+
+ //CGContextFlush(ctxRef); //"If you pass [...] a bitmap context, this function does nothing."
+
+ osx::ImageData imgOut(trgWidth, trgHeight);
+
+ auto it = buf.begin();
+ auto itOutRgb = imgOut.rgb.begin();
+ auto itOutAlpha = imgOut.alpha.begin();
+ for (int i = 0; i < trgWidth * trgHeight; ++i)
+ {
+ const unsigned char b = *it++;
+ const unsigned char g = *it++;
+ const unsigned char r = *it++;
+ const unsigned char a = *it++;
+
+ //unsigned arithmetics caveat!
+ auto demultiplex = [&](unsigned char c) { return static_cast<unsigned char>(numeric::confineCpy(a == 0 ? 0 : (c * 255 + a - 1) / a, 0, 255)); }; //=ceil(c * 255 / a)
+
+ *itOutRgb++ = demultiplex(r);
+ *itOutRgb++ = demultiplex(g);
+ *itOutRgb++ = demultiplex(b);
+ *itOutAlpha++ = a;
+ }
+
+ return imgOut;
+}
+}
+
+
+osx::ImageData osx::getThumbnail(const char* filename, int requestedSize) //throw OsxError
+{
+ @try
+ {
+ @autoreleasepool
+ {
+ NSString* nsFile = [NSString stringWithCString:filename encoding:NSUTF8StringEncoding];
+ ZEN_OSX_ASSERT(nsFile != nil); //throw OsxError; can this fail? not documented
+ //stringWithCString returns string which is already set to autorelease!
+
+ NSImage* nsImg = [[[NSImage alloc] initWithContentsOfFile:nsFile] autorelease];
+ ZEN_OSX_ASSERT(nsImg != nil); //may fail
+
+ return extractBytes(nsImg, requestedSize); //throw OsxError
+ }
+ }
+ @catch (NSException* e)
+ {
+ throwOsxError(e); //throw OsxError
+ }
+}
+
+
+osx::ImageData osx::getFileIcon(const char* filename, int requestedSize) //throw OsxError
+{
+ @try
+ {
+ @autoreleasepool
+ {
+ NSString* nsFile = [NSString stringWithCString:filename encoding:NSUTF8StringEncoding];
+ ZEN_OSX_ASSERT(nsFile != nil); //throw OsxError; can this fail? not documented
+ //stringWithCString returns string which is already set to autorelease!
+
+ NSImage* nsImg = [[NSWorkspace sharedWorkspace] iconForFile:nsFile];
+ ZEN_OSX_ASSERT(nsImg != nil); //can this fail? not documented
+
+ return extractBytes(nsImg, requestedSize); //throw OsxError
+ }
+ }
+ @catch (NSException* e)
+ {
+ throwOsxError(e); //throw OsxError
+ }
+}
+
+
+osx::ImageData osx::getDefaultFileIcon(int requestedSize) //throw OsxError
+{
+ @try
+ {
+ @autoreleasepool
+ {
+ NSImage* nsImg = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericDocumentIcon)];
+ //NSImage* nsImg = [[NSWorkspace sharedWorkspace] iconForFileType:@"dat"];
+ ZEN_OSX_ASSERT(nsImg != nil); //can this fail? not documented
+
+ return extractBytes(nsImg, requestedSize); //throw OsxError
+ }
+ }
+ @catch (NSException* e)
+ {
+ throwOsxError(e); //throw OsxError
+ }
+}
+
+
+osx::ImageData osx::getDefaultFolderIcon(int requestedSize) //throw OsxError
+{
+ @try
+ {
+ @autoreleasepool
+ {
+ NSImage* nsImg = [NSImage imageNamed:NSImageNameFolder];
+ //NSImage* nsImg = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericFolderIcon)];
+ ZEN_OSX_ASSERT(nsImg != nil); //may fail
+
+ return extractBytes(nsImg, requestedSize); //throw OsxError
+ }
+ }
+ @catch (NSException* e)
+ {
+ throwOsxError(e); //throw OsxError
+ }
+}
diff --git a/lib/parse_lng.h b/lib/parse_lng.h
index b5afe50c..b6af9b18 100644
--- a/lib/parse_lng.h
+++ b/lib/parse_lng.h
@@ -32,6 +32,7 @@ typedef std::map <SingularPluralPair, PluralForms> TranslationPluralMap; //(sing
struct TransHeader
{
+ TransHeader() : pluralCount(0) {}
std::string languageName; //display name: "English (UK)"
std::string translatorName; //"Zenju"
std::string localeName; //ISO 639 language code + ISO 3166 country code, e.g. "en_GB", or "en_US"
diff --git a/lib/process_xml.cpp b/lib/process_xml.cpp
index 0c5b5581..d41afc74 100644
--- a/lib/process_xml.cpp
+++ b/lib/process_xml.cpp
@@ -1047,21 +1047,6 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config)
inOpt["PromptSaveConfig" ](config.optDialogs.popupOnConfigChange);
inOpt["ConfirmSyncStart" ](config.optDialogs.confirmSyncStart);
- warn_static("remove after migration?")
- if (!inOpt)
- {
- inOpt = inShared["ShowOptionalDialogs"];
- inOpt["CheckForDependentFolders" ](config.optDialogs.warningDependentFolders);
- inOpt["CheckForMultipleWriteAccess" ](config.optDialogs.warningFolderPairRaceCondition);
- inOpt["CheckForSignificantDifference"](config.optDialogs.warningSignificantDifference);
- inOpt["CheckForFreeDiskSpace"](config.optDialogs.warningNotEnoughDiskSpace);
- inOpt["CheckForUnresolvedConflicts"](config.optDialogs.warningUnresolvedConflicts);
- inOpt["NotifyDatabaseError" ](config.optDialogs.warningDatabaseError);
- inOpt["CheckMissingRecycleBin"](config.optDialogs.warningRecyclerMissing);
- inOpt["PopupOnConfigChange" ](config.optDialogs.popupOnConfigChange);
- inOpt["SummaryBeforeSync" ](config.optDialogs.confirmSyncStart);
- }
-
//gui specific global settings (optional)
XmlIn inGui = in["Gui"];
XmlIn inWnd = inGui["MainDialog"];
@@ -1106,10 +1091,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config)
inWnd["Layout" ](config.gui.guiPerspectiveLast);
//load config file history
- warn_static("remove after migration?")
- if (inGui["LastConfigActive"]) inGui["LastConfigActive"](config.gui.lastUsedConfigFiles);//obsolete name
- else
- inGui["LastUsedConfig"](config.gui.lastUsedConfigFiles);
+ inGui["LastUsedConfig"](config.gui.lastUsedConfigFiles);
inGui["ConfigHistory"](config.gui.cfgFileHistory);
inGui["ConfigHistory"].attribute("MaxSize", config.gui.cfgFileHistMax);
@@ -1124,18 +1106,6 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config)
//external applications
inGui["ExternalApplications"](config.gui.externelApplications);
-
- warn_static("remove after migration?")
- //convert new internal macro naming convention: %name -> %item_path%; %dir -> %item_folder%
- for (auto it = config.gui.externelApplications.begin(); it != config.gui.externelApplications.end(); ++it)
- {
- replace(it->second, L"%nameCo", L"%item2_path%"); //unambiguous "Co" names first
- replace(it->second, L"%dirCo" , L"%item2_folder%");
- replace(it->second, L"%name" , L"%item_path%");
- replace(it->second, L"%dir" , L"%item_folder%");
- }
-
-
//last update check
inGui["LastUpdateCheck"](config.gui.lastUpdateCheck);
diff --git a/lib/resolve_path.cpp b/lib/resolve_path.cpp
index b735d1f9..bea62da3 100644
--- a/lib/resolve_path.cpp
+++ b/lib/resolve_path.cpp
@@ -7,10 +7,11 @@
#include <wx/utils.h> //wxGetEnv
#ifdef FFS_WIN
-#include <zen/win.h> //includes "windows.h"
-#include <Shlobj.h>
#include <zen/long_path_prefix.h>
#include <zen/file_handling.h>
+#include <zen/win.h> //includes "windows.h"
+#include <zen/dll.h>
+#include <Shlobj.h>
#ifdef _MSC_VER
#pragma comment(lib, "Mpr.lib")
#endif
@@ -106,6 +107,37 @@ private:
}
};
+ //================================================================================================
+ //SHGetKnownFolderPath: API available only with Windows Vista and later:
+#ifdef __MINGW32__ //MinGW is clueless about Vista...
+#define REFKNOWNFOLDERID const GUID&
+#define KF_FLAG_DONT_VERIFY 0x00004000
+ const GUID FOLDERID_Downloads = { 0x374de290, 0x123f, 0x4565, { 0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b} };
+ const GUID FOLDERID_PublicDownloads = { 0x3d644c9b, 0x1fb8, 0x4f30, { 0x9b, 0x45, 0xf6, 0x70, 0x23, 0x5f, 0x79, 0xc0} };
+ const GUID FOLDERID_QuickLaunch = { 0x52a4f021, 0x7b75, 0x48a9, { 0x9f, 0x6b, 0x4b, 0x87, 0xa2, 0x10, 0xbc, 0x8f} };
+#endif
+ typedef HRESULT (STDAPICALLTYPE* SHGetKnownFolderPathFunc)(REFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR* ppszPath);
+ const SysDllFun<SHGetKnownFolderPathFunc> shGetKnownFolderPath(L"Shell32.dll", "SHGetKnownFolderPath");
+
+ auto addFolderId = [&](REFKNOWNFOLDERID rfid, const Zstring& paramName)
+ {
+ if (shGetKnownFolderPath != nullptr) //[!] avoid bogus MSVC "performance warning"
+ {
+ PWSTR path = nullptr;
+ if (SUCCEEDED(shGetKnownFolderPath(rfid, //_In_ REFKNOWNFOLDERID rfid,
+ KF_FLAG_DONT_VERIFY, //_In_ DWORD dwFlags,
+ nullptr, //_In_opt_ HANDLE hToken,
+ &path))) //_Out_ PWSTR *ppszPath
+ {
+ ZEN_ON_SCOPE_EXIT(::CoTaskMemFree(path));
+
+ Zstring dirname = path;
+ if (!dirname.empty())
+ csidlToDir.insert(std::make_pair(paramName, dirname));
+ }
+ }
+ };
+
addCsidl(CSIDL_DESKTOPDIRECTORY, L"csidl_Desktop"); // C:\Users\<user>\Desktop
addCsidl(CSIDL_COMMON_DESKTOPDIRECTORY, L"csidl_PublicDesktop"); // C:\Users\All Users\Desktop
@@ -140,6 +172,12 @@ private:
addCsidl(CSIDL_TEMPLATES, L"csidl_Templates"); // C:\Users\<user>\AppData\Roaming\Microsoft\Windows\Templates
addCsidl(CSIDL_COMMON_TEMPLATES, L"csidl_PublicTemplates"); // C:\ProgramData\Microsoft\Windows\Templates
+ //Vista and later:
+ addFolderId(FOLDERID_Downloads, L"csidl_Downloads"); // C:\Users\<user>\Downloads
+ addFolderId(FOLDERID_PublicDownloads, L"csidl_PublicDownloads"); // C:\Users\Public\Downloads
+
+ addFolderId(FOLDERID_QuickLaunch, L"csidl_QuickLaunch"); // C:\Users\<user>\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch
+
/*
CSIDL_APPDATA covered by %AppData%
CSIDL_LOCAL_APPDATA covered by %LocalAppData% -> not on XP!
@@ -154,6 +192,8 @@ private:
CSIDL_PROGRAM_FILES_COMMONX86 covered by %CommonProgramFiles(x86)% -> not on XP!
CSIDL_ADMINTOOLS not relevant?
CSIDL_COMMON_ADMINTOOLS not relevant?
+
+ FOLDERID_Public covered by %Public%
*/
}
@@ -298,14 +338,14 @@ Zstring getPathByVolumenName(const Zstring& volumeName) //return empty string on
findFirstMatch.addJob([path, volumeName]() -> std::unique_ptr<Zstring>
{
- UINT type = ::GetDriveType(path.c_str()); //non-blocking call!
+ UINT type = ::GetDriveType(appendSeparator(path).c_str()); //non-blocking call!
if (type == DRIVE_REMOTE || type == DRIVE_CDROM)
return nullptr;
//next call seriously blocks for non-existing network drives!
std::vector<wchar_t> volName(MAX_PATH + 1); //docu says so
- if (::GetVolumeInformation(path.c_str(), //__in_opt LPCTSTR lpRootPathName,
+ if (::GetVolumeInformation(appendSeparator(path).c_str(), //__in_opt LPCTSTR lpRootPathName,
&volName[0], //__out LPTSTR lpVolumeNameBuffer,
static_cast<DWORD>(volName.size()), //__in DWORD nVolumeNameSize,
nullptr, //__out_opt LPDWORD lpVolumeSerialNumber,
@@ -329,18 +369,20 @@ Zstring getPathByVolumenName(const Zstring& volumeName) //return empty string on
//networks and cdrom excluded - this should not block
Zstring getVolumeName(const Zstring& volumePath) //return empty string on error
{
- UINT rv = ::GetDriveType(volumePath.c_str()); //non-blocking call!
- if (rv != DRIVE_REMOTE && rv != DRIVE_CDROM)
+ UINT rv = ::GetDriveType(appendSeparator(volumePath).c_str()); //non-blocking call!
+ if (rv != DRIVE_REMOTE &&
+ rv != DRIVE_CDROM)
{
- std::vector<wchar_t> buffer(MAX_PATH + 1);
- if (::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName,
- &buffer[0], //__out LPTSTR lpVolumeNameBuffer,
- static_cast<DWORD>(buffer.size()), //__in DWORD nVolumeNameSize,
- nullptr, //__out_opt LPDWORD lpVolumeSerialNumber,
- nullptr, //__out_opt LPDWORD lpMaximumComponentLength,
- nullptr, //__out_opt LPDWORD lpFileSystemFlags,
- nullptr, //__out LPTSTR lpFileSystemNameBuffer,
- 0)) //__in DWORD nFileSystemNameSize
+ const DWORD bufferSize = MAX_PATH + 1;
+ std::vector<wchar_t> buffer(bufferSize);
+ if (::GetVolumeInformation(appendSeparator(volumePath).c_str(), //__in_opt LPCTSTR lpRootPathName,
+ &buffer[0], //__out LPTSTR lpVolumeNameBuffer,
+ bufferSize, //__in DWORD nVolumeNameSize,
+ nullptr, //__out_opt LPDWORD lpVolumeSerialNumber,
+ nullptr, //__out_opt LPDWORD lpMaximumComponentLength,
+ nullptr, //__out_opt LPDWORD lpFileSystemFlags,
+ nullptr, //__out LPTSTR lpFileSystemNameBuffer,
+ 0)) //__in DWORD nFileSystemNameSize
return &buffer[0];
}
return Zstring();
@@ -420,7 +462,7 @@ void getDirectoryAliasesRecursive(const Zstring& dirname, std::set<Zstring, Less
}
#endif
- //3. environment variables: C:\Users\username -> %USERPROFILE%
+ //3. environment variables: C:\Users\<user> -> %USERPROFILE%
{
std::map<Zstring, Zstring> envToDir;
@@ -432,15 +474,15 @@ void getDirectoryAliasesRecursive(const Zstring& dirname, std::set<Zstring, Less
};
#ifdef FFS_WIN
addEnvVar(L"AllUsersProfile"); // C:\ProgramData
- addEnvVar(L"AppData"); // C:\Users\username\AppData\Roaming
- addEnvVar(L"LocalAppData"); // C:\Users\username\AppData\Local
+ addEnvVar(L"AppData"); // C:\Users\<user>\AppData\Roaming
+ addEnvVar(L"LocalAppData"); // C:\Users\<user>\AppData\Local
addEnvVar(L"ProgramData"); // C:\ProgramData
addEnvVar(L"ProgramFiles"); // C:\Program Files
addEnvVar(L"ProgramFiles(x86)");// C:\Program Files (x86)
addEnvVar(L"CommonProgramFiles"); // C:\Program Files\Common Files
addEnvVar(L"CommonProgramFiles(x86)"); // C:\Program Files (x86)\Common Files
addEnvVar(L"Public"); // C:\Users\Public
- addEnvVar(L"UserProfile"); // C:\Users\username
+ addEnvVar(L"UserProfile"); // C:\Users\<user>
addEnvVar(L"WinDir"); // C:\Windows
addEnvVar(L"Temp"); // C:\Windows\Temp
@@ -449,7 +491,7 @@ void getDirectoryAliasesRecursive(const Zstring& dirname, std::set<Zstring, Less
envToDir.insert(csidlMap.begin(), csidlMap.end());
#elif defined FFS_LINUX || defined FFS_MAC
- addEnvVar("HOME"); //Linux: /home/zenju Mac: /Users/zenju
+ addEnvVar("HOME"); //Linux: /home/<user> Mac: /Users/<user>
#endif
//substitute paths by symbolic names
auto pathStartsWith = [](const Zstring& path, const Zstring& prefix) -> bool
@@ -472,7 +514,7 @@ void getDirectoryAliasesRecursive(const Zstring& dirname, std::set<Zstring, Less
});
}
- //4. replace (all) macros: %USERPROFILE% -> C:\Users\username
+ //4. replace (all) macros: %USERPROFILE% -> C:\Users\<user>
{
Zstring testMacros = expandMacros(dirname);
if (testMacros != dirname)
diff --git a/lib/resources.cpp b/lib/resources.cpp
index 8a021475..e6691458 100644
--- a/lib/resources.cpp
+++ b/lib/resources.cpp
@@ -27,7 +27,7 @@ namespace
{
void loadAnimFromZip(wxZipInputStream& zipInput, wxAnimation& anim)
{
- //Workaround for wxWidgets:
+ //work around wxWidgets bug:
//construct seekable input stream (zip-input stream is non-seekable) for wxAnimation::Load()
//luckily this method call is very fast: below measurement precision!
std::vector<char> data;
@@ -53,7 +53,7 @@ GlobalResources::GlobalResources()
wxImage::AddHandler(new wxPNGHandler); //ownership passed
wxZipInputStream resourceFile(input, wxConvUTF8);
- //do NOT rely on wxConvLocal! ON failure shows unhelpful popup "Cannot convert from the charset 'Unknown encoding (-1)'!"
+ //do NOT rely on wxConvLocal! On failure shows unhelpful popup "Cannot convert from the charset 'Unknown encoding (-1)'!"
while (true)
{
@@ -77,16 +77,20 @@ GlobalResources::GlobalResources()
//for compatibility it seems we need to stick with a "real" icon
programIcon = wxIcon(L"A_PROGRAM_ICON");
-#elif defined FFS_LINUX || defined FFS_MAC
- //use big logo bitmap for better quality
- programIcon.CopyFromBitmap(getImageInt(L"FreeFileSync"));
- //attention: this is the reason we need a member getImage -> it must not implicitly create static object instance!!!
- //erroneously calling static object constructor twice will deadlock on Linux!!
+#elif defined FFS_LINUX
+ //attention: make sure to not implicitly call "instance()" again => deadlock on Linux
+ programIcon.CopyFromBitmap(getImage(L"FreeFileSync")); //use big logo bitmap for better quality
+
+#elif defined FFS_MAC
+ assert(getImage(L"FreeFileSync").GetWidth () == getImage(L"FreeFileSync").GetHeight() &&
+ getImage(L"FreeFileSync").GetWidth() % 128 == 0);
+ //wxWidgets' bitmap to icon conversion on OS X can only deal with very specific sizes
+ programIcon.CopyFromBitmap(getImage(L"FreeFileSync").ConvertToImage().Scale(128, 128, wxIMAGE_QUALITY_HIGH)); //"von hinten durch die Brust ins Auge"
#endif
}
-const wxBitmap& GlobalResources::getImageInt(const wxString& imageName) const
+const wxBitmap& GlobalResources::getImage(const wxString& imageName) const
{
auto it = bitmaps.find(!contains(imageName, L'.') ? //assume .png ending if nothing else specified
imageName + L".png" :
diff --git a/lib/resources.h b/lib/resources.h
index 9081ff4e..a8d9469c 100644
--- a/lib/resources.h
+++ b/lib/resources.h
@@ -18,7 +18,7 @@ class GlobalResources
public:
static const GlobalResources& instance();
- static const wxBitmap& getImage(const wxString& name) { return instance().getImageInt(name); }
+ const wxBitmap& getImage(const wxString& name) const;
//global image resource objects
wxAnimation aniWink;
@@ -30,9 +30,11 @@ private:
GlobalResources(const GlobalResources&);
GlobalResources& operator=(const GlobalResources&);
- const wxBitmap& getImageInt(const wxString& name) const;
-
std::map<wxString, wxBitmap> bitmaps;
};
+
+inline
+const wxBitmap& getResourceImage(const wxString& name) { return GlobalResources::instance().getImage(name); }
+
#endif // RESOURCES_H_INCLUDED
bgstack15