diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Batch.ico | bin | 103260 -> 103260 bytes | |||
-rw-r--r-- | lib/ShadowCopy/Shadow_Server2003.vcxproj | 14 | ||||
-rw-r--r-- | lib/ShadowCopy/Shadow_Windows7.vcxproj | 14 | ||||
-rw-r--r-- | lib/ShadowCopy/Shadow_XP.vcxproj | 14 | ||||
-rw-r--r-- | lib/ShadowCopy/shadow.cpp | 18 | ||||
-rw-r--r-- | lib/Thumbnail/Thumbnail.vcxproj | 12 | ||||
-rw-r--r-- | lib/Thumbnail/thumbnail.cpp | 514 | ||||
-rw-r--r-- | lib/Thumbnail/thumbnail.h | 45 | ||||
-rw-r--r-- | lib/dir_exist_async.h | 5 | ||||
-rw-r--r-- | lib/ffs_paths.cpp | 25 | ||||
-rw-r--r-- | lib/help_provider.h | 72 | ||||
-rw-r--r-- | lib/icon_buffer.cpp | 560 | ||||
-rw-r--r-- | lib/icon_buffer.h | 26 | ||||
-rw-r--r-- | lib/localization.cpp | 6 | ||||
-rw-r--r-- | lib/osx_file_icon.h | 36 | ||||
-rw-r--r-- | lib/osx_file_icon.mm | 179 | ||||
-rw-r--r-- | lib/parse_lng.h | 1 | ||||
-rw-r--r-- | lib/process_xml.cpp | 32 | ||||
-rw-r--r-- | lib/resolve_path.cpp | 84 | ||||
-rw-r--r-- | lib/resources.cpp | 20 | ||||
-rw-r--r-- | lib/resources.h | 8 |
21 files changed, 1154 insertions, 531 deletions
diff --git a/lib/Batch.ico b/lib/Batch.ico Binary files differindex d27f35d1..faa2db64 100644 --- a/lib/Batch.ico +++ b/lib/Batch.ico 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 |