From d4af25c52a28b93484ffb55e0a8027bc4ce7856f Mon Sep 17 00:00:00 2001 From: Daniel Wilhelm Date: Fri, 18 Apr 2014 17:21:59 +0200 Subject: 5.9 --- ui/IFileDialog_Vista/ifile_dialog.cpp | 18 +++- ui/IFileDialog_Vista/ifile_dialog.h | 12 ++- ui/batch_config.cpp | 1 + ui/batch_status_handler.cpp | 13 ++- ui/check_version.cpp | 177 +++++++++++++++++++++++++++----- ui/custom_grid.cpp | 63 +++++++----- ui/dir_name.cpp | 9 +- ui/folder_history_box.cpp | 14 ++- ui/gui_generated.cpp | 29 +++++- ui/gui_generated.h | 20 ++-- ui/gui_status_handler.cpp | 12 ++- ui/main_dlg.cpp | 188 ++++++++++++++++++++++------------ ui/main_dlg.h | 15 +-- ui/progress_indicator.cpp | 63 +++++++++--- ui/progress_indicator.h | 1 + ui/search.cpp | 1 + ui/sync_cfg.cpp | 2 +- 17 files changed, 472 insertions(+), 166 deletions(-) (limited to 'ui') diff --git a/ui/IFileDialog_Vista/ifile_dialog.cpp b/ui/IFileDialog_Vista/ifile_dialog.cpp index 8c3e694c..565dfa1a 100644 --- a/ui/IFileDialog_Vista/ifile_dialog.cpp +++ b/ui/IFileDialog_Vista/ifile_dialog.cpp @@ -19,6 +19,7 @@ namespace { bool showFolderPickerImpl(HWND ownerWindow, //throw ComError; return "false" if cancelled by user const wchar_t* defaultFolder, //optional! + const GUID* persistenceGuid, // std::wstring& selectedFolder) { ComPtr fileDlg; @@ -27,6 +28,9 @@ bool showFolderPickerImpl(HWND ownerWindow, //throw ComError; return "false" if CLSCTX_ALL, IID_PPV_ARGS(fileDlg.init()))); + if (persistenceGuid) + ZEN_CHECK_COM(fileDlg->SetClientGuid(*persistenceGuid)); + FILEOPENDIALOGOPTIONS dlgOptions = 0; ZEN_CHECK_COM(fileDlg->GetOptions(&dlgOptions)); //throw ComError ZEN_CHECK_COM(fileDlg->SetOptions(dlgOptions | FOS_PICKFOLDERS | FOS_NOVALIDATE | FOS_FORCEFILESYSTEM)); @@ -57,7 +61,7 @@ bool showFolderPickerImpl(HWND ownerWindow, //throw ComError; return "false" if return true; } -const wchar_t* allocString(const std::wstring& msg) //ownership passed +wchar_t* allocString(const std::wstring& msg) //ownership passed { auto tmp = new wchar_t [msg.size() + 1]; //std::bad_alloc ? ::wmemcpy(tmp, msg.c_str(), msg.size() + 1); //include 0-termination @@ -69,9 +73,10 @@ const wchar_t* allocString(const std::wstring& msg) //ownership passed void ifile::showFolderPicker(void* ownerWindow, const wchar_t* defaultFolder, - const wchar_t*& selectedFolder, + const GuidProxy* guid, + wchar_t*& selectedFolder, bool& cancelled, - const wchar_t*& errorMsg) + wchar_t*& errorMsg) { selectedFolder = nullptr; cancelled = false; @@ -79,8 +84,13 @@ void ifile::showFolderPicker(void* ownerWindow, try { + static_assert(sizeof(GuidProxy) == sizeof(GUID), ""); + GUID winGuid = {}; + if (guid) + ::memcpy(&winGuid, guid, sizeof(GUID)); + std::wstring folderPath; - if (showFolderPickerImpl(static_cast(ownerWindow), defaultFolder, folderPath)) //throw ComError + if (showFolderPickerImpl(static_cast(ownerWindow), defaultFolder, guid ? &winGuid : nullptr, folderPath)) //throw ComError selectedFolder = allocString(folderPath); else cancelled = true; diff --git a/ui/IFileDialog_Vista/ifile_dialog.h b/ui/IFileDialog_Vista/ifile_dialog.h index d0099dda..5b4dc532 100644 --- a/ui/IFileDialog_Vista/ifile_dialog.h +++ b/ui/IFileDialog_Vista/ifile_dialog.h @@ -25,12 +25,15 @@ namespace ifile //COM needs to be initialized before calling any of these functions! CoInitializeEx/CoUninitialize //Requires Windows Vista and later +typedef char GuidProxy[16]; //= Windows 128-bit GUID; we don't want to include "Guiddef.h" here! + DLL_FUNCTION_DECLARATION void showFolderPicker(void* ownerWindow, //in; ==HWND const wchar_t* defaultFolder, //in, optional! - const wchar_t*& selectedFolder, //out: call freeString() after use! + const GuidProxy* guid, //set nullptr by default: Windows stores dialog state (position, x, y coordinates, ect.) associated with the process executable name => use other GUID when needed + wchar_t*& selectedFolder, //out: call freeString() after use! bool& cancelled, //out - const wchar_t*& errorMsg); //out, optional: call freeString() after use! + wchar_t*& errorMsg); //out, optional: call freeString() after use! DLL_FUNCTION_DECLARATION void freeString(const wchar_t* str); @@ -40,9 +43,10 @@ void freeString(const wchar_t* str); ----------*/ typedef bool (*FunType_showFolderPicker)(void* ownerWindow, const wchar_t* defaultFolder, - const wchar_t*& selectedFolder, + const GuidProxy* guid, + wchar_t*& selectedFolder, bool& cancelled, - const wchar_t*& errorMsg); + wchar_t*& errorMsg); typedef void (*FunType_freeString)(const wchar_t* str); /*-------------- diff --git a/ui/batch_config.cpp b/ui/batch_config.cpp index f07dbc07..d5aa7bc2 100644 --- a/ui/batch_config.cpp +++ b/ui/batch_config.cpp @@ -14,6 +14,7 @@ #include #include #include +#include "../ui/exec_finished_box.h" #include "../lib/help_provider.h" #include "folder_pair.h" #include "msg_popup.h" diff --git a/ui/batch_status_handler.cpp b/ui/batch_status_handler.cpp index 594ffb7a..b33b0d80 100644 --- a/ui/batch_status_handler.cpp +++ b/ui/batch_status_handler.cpp @@ -135,7 +135,8 @@ BatchStatusHandler::BatchStatusHandler(bool showProgress, BatchStatusHandler::~BatchStatusHandler() { - const int totalErrors = errorLog.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR); //evaluate before finalizing log + const int totalErrors = errorLog.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR); //evaluate before finalizing log + const int totalWarnings = errorLog.getItemCount(TYPE_WARNING); //finalize error log std::wstring finalStatus; @@ -149,6 +150,12 @@ BatchStatusHandler::~BatchStatusHandler() { raiseReturnCode(returnCode_, FFS_RC_FINISHED_WITH_ERRORS); finalStatus = _("Synchronization completed with errors!"); + errorLog.logMsg(finalStatus, TYPE_ERROR); + } + else if (totalWarnings > 0) + { + raiseReturnCode(returnCode_, FFS_RC_FINISHED_WITH_WARNINGS); + finalStatus = _("Synchronization completed with warnings!"); errorLog.logMsg(finalStatus, TYPE_WARNING); } else @@ -190,7 +197,7 @@ BatchStatusHandler::~BatchStatusHandler() switchBatchToGui_.execute(); //open FreeFileSync GUI } catch (...) {} - syncStatusFrame.closeWindowDirectly(); //syncStatusFrame is main window => program will quit directly + syncStatusFrame.closeWindowDirectly(); //syncStatusFrame is not main window anymore } else { @@ -220,6 +227,8 @@ BatchStatusHandler::~BatchStatusHandler() syncStatusFrame.processHasFinished(SyncStatus::RESULT_ABORTED, errorLog); //enable okay and close events else if (totalErrors > 0) syncStatusFrame.processHasFinished(SyncStatus::RESULT_FINISHED_WITH_ERROR, errorLog); + else if (totalWarnings > 0) + syncStatusFrame.processHasFinished(SyncStatus::RESULT_FINISHED_WITH_WARNINGS, errorLog); else syncStatusFrame.processHasFinished(SyncStatus::RESULT_FINISHED_WITH_SUCCESS, errorLog); } diff --git a/ui/check_version.cpp b/ui/check_version.cpp index 5bf67b90..c9d2049d 100644 --- a/ui/check_version.cpp +++ b/ui/check_version.cpp @@ -16,50 +16,179 @@ #include "msg_popup.h" #include "../version/version.h" #include "../lib/ffs_paths.h" +#include + +#ifdef FFS_WIN +#include +#endif using namespace zen; namespace { +#ifdef FFS_WIN +class InternetConnectionError {}; + +class WinInetAccess //using IE proxy settings! :) +{ +public: + WinInetAccess(const wchar_t* url) //throw InternetConnectionError (if url cannot be reached; no need to also call readBytes()) + { + //::InternetAttemptConnect(0) -> not working as expected: succeeds even when there is no internet connection! + + hInternet = ::InternetOpen(L"FreeFileSync", //_In_ LPCTSTR lpszAgent, + INTERNET_OPEN_TYPE_PRECONFIG, //_In_ DWORD dwAccessType, + nullptr, //_In_ LPCTSTR lpszProxyName, + nullptr, //_In_ LPCTSTR lpszProxyBypass, + 0); //_In_ DWORD dwFlags + if (!hInternet) + throw InternetConnectionError(); + zen::ScopeGuard guardInternet = zen::makeGuard([&] { ::InternetCloseHandle(hInternet); }); + + hRequest = ::InternetOpenUrl(hInternet, //_In_ HINTERNET hInternet, + url, //_In_ LPCTSTR lpszUrl, + nullptr, //_In_ LPCTSTR lpszHeaders, + 0, //_In_ DWORD dwHeadersLength, + INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_UI, //_In_ DWORD dwFlags, + 0); //_In_ DWORD_PTR dwContext + if (!hRequest) //won't fail due to unreachable url here! There is no substitute for HTTP_QUERY_STATUS_CODE!!! + throw InternetConnectionError(); + zen::ScopeGuard guardRequest = zen::makeGuard([&] { ::InternetCloseHandle(hRequest); }); + + DWORD statusCode = 0; + DWORD bufferLength = sizeof(statusCode); + if (!::HttpQueryInfo(hRequest, //_In_ HINTERNET hRequest, + HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, //_In_ DWORD dwInfoLevel, + &statusCode, //_Inout_ LPVOID lpvBuffer, + &bufferLength, //_Inout_ LPDWORD lpdwBufferLength, + nullptr)) //_Inout_ LPDWORD lpdwIndex + throw InternetConnectionError(); + + if (statusCode != HTTP_STATUS_OK) + throw InternetConnectionError(); //e.g. 404 - HTTP_STATUS_NOT_FOUND + + guardRequest.dismiss(); + guardInternet.dismiss(); + } + + ~WinInetAccess() + { + ::InternetCloseHandle(hRequest); + ::InternetCloseHandle(hInternet); + } + + template + OutputIterator readBytes(OutputIterator result) //throw InternetConnectionError + { + //internet says "HttpQueryInfo() + HTTP_QUERY_CONTENT_LENGTH" not supported by all http servers... + const DWORD bufferSize = 64 * 1024; + std::vector buffer(bufferSize); + for (;;) + { + DWORD bytesRead = 0; + if (!::InternetReadFile(hRequest, //_In_ HINTERNET hFile, + &buffer[0], //_Out_ LPVOID lpBuffer, + bufferSize, //_In_ DWORD dwNumberOfBytesToRead, + &bytesRead)) //_Out_ LPDWORD lpdwNumberOfBytesRead + throw InternetConnectionError(); + if (bytesRead == 0) + return result; + + result = std::copy(buffer.begin(), buffer.begin() + bytesRead, result); + } + } + +private: + HINTERNET hInternet; + HINTERNET hRequest; +}; + + +inline +bool canAccessUrl(const wchar_t* url) //throw () +{ + try + { + (void)WinInetAccess(url); //throw InternetConnectionError + return true; + } + catch (const InternetConnectionError&) + { + return false; + } +} + + +template inline +OutputIterator readBytesUrl(const wchar_t* url, OutputIterator result) //throw InternetConnectionError +{ + return WinInetAccess(url).readBytes(result); //throw InternetConnectionError +} +#endif + + enum GetVerResult { GET_VER_SUCCESS, - GET_VER_NO_CONNECTION, //no internet connection? + GET_VER_NO_CONNECTION, //no internet connection or just Sourceforge down? GET_VER_PAGE_NOT_FOUND //version file seems to have moved! => trigger an update! }; GetVerResult getOnlineVersion(wxString& version) //empty string on error; { - wxWindowDisabler dummy; +#ifdef FFS_WIN + //internet access supporting proxy connections + std::vector output; + try + { + readBytesUrl(L"http://freefilesync.sourceforge.net/latest_version.txt", std::back_inserter(output)); //throw InternetConnectionError + } + catch (const InternetConnectionError&) + { + return canAccessUrl(L"http://sourceforge.net/") ? GET_VER_PAGE_NOT_FOUND : GET_VER_NO_CONNECTION; + } - wxHTTP webAccess; - webAccess.SetHeader(L"content-type", L"text/html; charset=utf-8"); - webAccess.SetTimeout(5); //5 seconds of timeout instead of 10 minutes(WTF are they thinking???)... + output.push_back('\0'); + version = utfCvrtTo(&output[0]); + return GET_VER_SUCCESS; - if (webAccess.Connect(L"freefilesync.sourceforge.net")) //only the server, no pages here yet... - { - //wxApp::IsMainLoopRunning(); // should return true +#else + wxWindowDisabler dummy; - std::unique_ptr httpStream(webAccess.GetInputStream(L"/latest_version.txt")); - //must be deleted BEFORE webAccess is closed + auto getStringFromUrl = [](const wxString& server, const wxString& page, int timeout, wxString* output) -> bool //true on successful connection + { + wxHTTP webAccess; + webAccess.SetHeader(L"content-type", L"text/html; charset=utf-8"); + webAccess.SetTimeout(timeout); //default: 10 minutes(WTF are they thinking???)... - if (httpStream && webAccess.GetError() == wxPROTO_NOERR) + if (webAccess.Connect(server)) //will *not* fail for non-reachable url here! { - wxString tmp; - wxStringOutputStream outStream(&tmp); - httpStream->Read(outStream); - version = tmp; - return GET_VER_SUCCESS; + //wxApp::IsMainLoopRunning(); // should return true + + std::unique_ptr httpStream(webAccess.GetInputStream(page)); + //must be deleted BEFORE webAccess is closed + + if (httpStream && webAccess.GetError() == wxPROTO_NOERR) + { + if (output) + { + output->clear(); + wxStringOutputStream outStream(output); + httpStream->Read(outStream); + } + return true; + } } - else - return GET_VER_PAGE_NOT_FOUND; - } - else //check if sourceforge in general is reachable - { - webAccess.SetTimeout(1); - return webAccess.Connect(L"sourceforge.net") ? GET_VER_PAGE_NOT_FOUND : GET_VER_NO_CONNECTION; - } + return false; + }; + + if (getStringFromUrl(L"freefilesync.sourceforge.net", L"/latest_version.txt", 5, &version)) + return GET_VER_SUCCESS; + + const bool canConnectToSf = getStringFromUrl(L"sourceforge.net", L"/", 1, nullptr); + return canConnectToSf ? GET_VER_PAGE_NOT_FOUND : GET_VER_NO_CONNECTION; +#endif } diff --git a/ui/custom_grid.cpp b/ui/custom_grid.cpp index 13964d9e..e7152905 100644 --- a/ui/custom_grid.cpp +++ b/ui/custom_grid.cpp @@ -224,7 +224,7 @@ protected: else { //alternate background color to improve readability (while lacking cell borders) - if (getRowDisplayType(row) == DISP_TYPE_NORMAL && row % 2 == 0) + if (getRowDisplayType(row) == DISP_TYPE_NORMAL && row % 2 == 1) { //accessibility, support high-contrast schemes => work with user-defined background color! const auto backCol = getBackGroundColor(row); @@ -1192,7 +1192,7 @@ public: GridDataLeft& provLeft, GridDataMiddle& provMiddle, GridDataRight& provRight) : - gridL_(gridL), gridC_(gridC), gridR_(gridR), + gridL_(gridL), gridC_(gridC), gridR_(gridR), scrollMaster(nullptr), provLeft_(provLeft), provMiddle_(provMiddle), provRight_(provRight) { gridL_.Connect(EVENT_GRID_COL_RESIZE, GridColumnResizeEventHandler(GridEventManager::onResizeColumnL), nullptr, this); @@ -1209,29 +1209,31 @@ public: gridC_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onCenterSelectEnd ), nullptr, this); //clear selection of other grid when selecting on - gridL_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onGridSelectionL), nullptr, this); - gridR_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onGridSelectionR), nullptr, this); + gridL_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onGridSelectionL), nullptr, this); + gridR_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onGridSelectionR), nullptr, this); //parallel grid scrolling: do NOT use DoPrepareDC() to align grids! GDI resource leak! Use regular paint event instead: - gridL_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridL), NULL, this); - gridC_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridC), NULL, this); - gridR_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridR), NULL, this); - - gridL_.Connect(wxEVT_SCROLLWIN_THUMBTRACK, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); - gridL_.Connect(wxEVT_SCROLLWIN_PAGEUP, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); - gridL_.Connect(wxEVT_SCROLLWIN_PAGEDOWN, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); - gridL_.Connect(wxEVT_SCROLLWIN_TOP, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); - gridL_.Connect(wxEVT_SCROLLWIN_BOTTOM, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); - gridL_.Connect(wxEVT_SCROLLWIN_LINEUP, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); - gridL_.Connect(wxEVT_SCROLLWIN_LINEDOWN, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); - - gridR_.Connect(wxEVT_SCROLLWIN_THUMBTRACK, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); - gridR_.Connect(wxEVT_SCROLLWIN_PAGEUP, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); - gridR_.Connect(wxEVT_SCROLLWIN_PAGEDOWN, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); - gridR_.Connect(wxEVT_SCROLLWIN_TOP, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); - gridR_.Connect(wxEVT_SCROLLWIN_BOTTOM, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); - gridR_.Connect(wxEVT_SCROLLWIN_LINEUP, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); - gridR_.Connect(wxEVT_SCROLLWIN_LINEDOWN, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); + gridL_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridL), nullptr, this); + gridC_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridC), nullptr, this); + gridR_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridR), nullptr, this); + + auto connectGridAccess = [&](Grid& grid, wxObjectEventFunction func) + { + grid.Connect(wxEVT_SCROLLWIN_TOP, func, nullptr, this); + grid.Connect(wxEVT_SCROLLWIN_BOTTOM, func, nullptr, this); + grid.Connect(wxEVT_SCROLLWIN_LINEUP, func, nullptr, this); + grid.Connect(wxEVT_SCROLLWIN_LINEDOWN, func, nullptr, this); + grid.Connect(wxEVT_SCROLLWIN_PAGEUP, func, nullptr, this); + grid.Connect(wxEVT_SCROLLWIN_PAGEDOWN, func, nullptr, this); + grid.Connect(wxEVT_SCROLLWIN_THUMBTRACK, func, nullptr, this); + + grid.getMainWin().Connect(wxEVT_SET_FOCUS, func, nullptr, this); + //on wxEVT_KILL_FOCUS, there's no need to reset "scrollMaster" + + }; + connectGridAccess(gridL_, wxEventHandler(GridEventManager::onGridAccessL)); + connectGridAccess(gridC_, wxEventHandler(GridEventManager::onGridAccessC)); + connectGridAccess(gridR_, wxEventHandler(GridEventManager::onGridAccessR)); Connect(EVENT_ALIGN_SCROLLBARS, wxEventHandler(GridEventManager::onAlignScrollBars), NULL, this); } @@ -1350,8 +1352,9 @@ private: trg.setColumnConfig(cfgTrg); } - void onGridAccessL(wxEvent& event) { gridL_.SetFocus(); event.Skip(); } - void onGridAccessR(wxEvent& event) { gridR_.SetFocus(); event.Skip(); } + void onGridAccessL(wxEvent& event) { scrollMaster = &gridL_; event.Skip(); } + void onGridAccessC(wxEvent& event) { scrollMaster = &gridC_; event.Skip(); } + void onGridAccessR(wxEvent& event) { scrollMaster = &gridR_; event.Skip(); } void onPaintGridL(wxEvent& event) { onPaintGrid(gridL_); event.Skip(); } void onPaintGridC(wxEvent& event) { onPaintGrid(gridC_); event.Skip(); } @@ -1367,9 +1370,9 @@ private: Grid* follow2 = nullptr; auto setGrids = [&](const Grid& l, Grid& f1, Grid& f2) { lead = &l; follow1 = &f1; follow2 = &f2; }; - if (wxWindow::FindFocus() == &gridC_.getMainWin()) + if (&gridC_ == scrollMaster) setGrids(gridC_, gridL_, gridR_); - else if (wxWindow::FindFocus() == &gridR_.getMainWin()) + else if (&gridR_ == scrollMaster) setGrids(gridR_, gridL_, gridC_); else //default: left panel setGrids(gridL_, gridC_, gridR_); @@ -1384,7 +1387,7 @@ private: int yOld = 0; target.GetViewStart(nullptr, &yOld); if (yOld != y) - target.Scroll(-1, y); + target.Scroll(-1, y); //empirical test Windows/Ubuntu: this call does NOT trigger a wxEVT_SCROLLWIN event, which would incorrectly set "scrollMaster" to "&target"! }; int y = 0; lead->GetViewStart(nullptr, &y); @@ -1423,6 +1426,10 @@ private: Grid& gridL_; Grid& gridC_; Grid& gridR_; + + const Grid* scrollMaster; //for address check only; this needn't be the grid having focus! + //e.g. mouse wheel events should set window under cursor as scrollMaster, but *not* change focus + GridDataLeft& provLeft_; GridDataMiddle& provMiddle_; GridDataRight& provRight_; diff --git a/ui/dir_name.cpp b/ui/dir_name.cpp index 406903e6..7d4f7a31 100644 --- a/ui/dir_name.cpp +++ b/ui/dir_name.cpp @@ -185,14 +185,19 @@ void DirectoryName::OnSelectDir(wxCommandEvent& event) const DllFun freeString (getDllName(), funName_freeString); if (showFolderPicker && freeString) { - const wchar_t* selectedFolder = nullptr; - const wchar_t* errorMsg = nullptr; + wchar_t* selectedFolder = nullptr; + wchar_t* errorMsg = nullptr; bool cancelled = false; ZEN_ON_SCOPE_EXIT(freeString(selectedFolder)); ZEN_ON_SCOPE_EXIT(freeString(errorMsg)); + const GuidProxy guid = { '\x0', '\x4a', '\xf9', '\x31', '\xb4', '\x92', '\x40', '\xa0', + '\x8d', '\xc2', '\xc', '\xa5', '\xef', '\x59', '\x6e', '\x3b' + }; //some random GUID => have Windows save IFileDialog state separately from other file/dir pickers! + showFolderPicker(static_cast(selectButton_.GetHWND()), //in; ==HWND defaultDirname.empty() ? nullptr : defaultDirname.c_str(), //in, optional! + &guid, selectedFolder, //out: call freeString() after use! cancelled, //out errorMsg); //out, optional: call freeString() after use! diff --git a/ui/folder_history_box.cpp b/ui/folder_history_box.cpp index 2832afed..cefc03c8 100644 --- a/ui/folder_history_box.cpp +++ b/ui/folder_history_box.cpp @@ -36,10 +36,16 @@ FolderHistoryBox::FolderHistoryBox(wxWindow* parent, /*##*/ SetMinSize(wxSize(150, -1)); //## workaround yet another wxWidgets bug: default minimum size is much too large for a wxComboBox //##################################### - Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(FolderHistoryBox::OnKeyEvent ), nullptr, this); - Connect(wxEVT_LEFT_DOWN, wxEventHandler(FolderHistoryBox::OnUpdateList), nullptr, this); + Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(FolderHistoryBox::OnKeyEvent ), nullptr, this); Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(FolderHistoryBox::OnSelection ), nullptr, this); - Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(FolderHistoryBox::OnMouseWheel), nullptr, this); + Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(FolderHistoryBox::OnMouseWheel), nullptr, this); +#ifdef FFS_WIN //on Win, this event only fires, when clicking on the small down arrow, NOT when clicking on the text field + //thanks to wxWidgets' non-portability it's exactly the converse on Linux! + Connect(wxEVT_LEFT_DOWN, wxEventHandler(FolderHistoryBox::OnUpdateList), nullptr, this); +#elif defined FFS_LINUX //update on each text change: maybe a little too often, but we have no choice as long as we don't have an event right before showing the drop-down list + Connect(wxEVT_COMMAND_TEXT_UPDATED, wxEventHandler(FolderHistoryBox::OnUpdateList), nullptr, this); +#endif + #if wxCHECK_VERSION(2, 9, 1) Connect(wxEVT_COMMAND_COMBOBOX_DROPDOWN, wxCommandEventHandler(FolderHistoryBox::OnShowDropDown), nullptr, this); @@ -73,10 +79,8 @@ void FolderHistoryBox::setValueAndUpdateList(const wxString& dirname) std::list dirList; //add some aliases to allow user changing to volume name and back, if possible -#ifdef FFS_WIN std::vector aliases = getDirectoryAliases(toZ(dirname)); dirList.insert(dirList.end(), aliases.begin(), aliases.end()); -#endif if (sharedHistory_.get()) { diff --git a/ui/gui_generated.cpp b/ui/gui_generated.cpp index 2e99377a..d132d748 100644 --- a/ui/gui_generated.cpp +++ b/ui/gui_generated.cpp @@ -1,10 +1,19 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Apr 10 2012) +// C++ code generated with wxFormBuilder (version Oct 8 2012) // http://www.wxformbuilder.org/ // // PLEASE DO "NOT" EDIT THIS FILE! /////////////////////////////////////////////////////////////////////////// +#include "../wx+/button.h" +#include "../wx+/graph.h" +#include "../wx+/grid.h" +#include "../wx+/toggle_button.h" +#include "exec_finished_box.h" +#include "folder_history_box.h" +#include "triple_splitter.h" +#include "wx_form_build_hide_warnings.h" + #include "gui_generated.h" /////////////////////////////////////////////////////////////////////////// @@ -259,6 +268,9 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const wxBoxSizer* bSizer1771; bSizer1771 = new wxBoxSizer( wxVERTICAL ); + + bSizer1771->Add( 0, 0, 1, wxEXPAND, 5 ); + m_bpButtonSwapSides = new wxBitmapButton( m_panelTopMiddle, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW ); m_bpButtonSwapSides->SetToolTip( _("Swap sides") ); @@ -280,6 +292,9 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const bSizer1771->Add( bSizer160, 0, wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizer1771->Add( 0, 0, 1, wxEXPAND, 5 ); + + m_panelTopMiddle->SetSizer( bSizer1771 ); m_panelTopMiddle->Layout(); bSizer1771->Fit( m_panelTopMiddle ); @@ -851,6 +866,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const m_bpButtonSave->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigSave ), NULL, this ); m_listBoxHistory->Connect( wxEVT_CHAR, wxKeyEventHandler( MainDialogGenerated::OnCfgHistoryKeyEvent ), NULL, this ); m_listBoxHistory->Connect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnLoadFromHistory ), NULL, this ); + m_listBoxHistory->Connect( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, wxCommandEventHandler( MainDialogGenerated::OnLoadFromHistoryDoubleClick ), NULL, this ); m_bpButtonFilter->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigureFilter ), NULL, this ); m_checkBoxShowExcluded->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnShowExcluded ), NULL, this ); m_bpButtonSyncCreateLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncCreateLeft ), NULL, this ); @@ -897,6 +913,7 @@ MainDialogGenerated::~MainDialogGenerated() m_bpButtonSave->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigSave ), NULL, this ); m_listBoxHistory->Disconnect( wxEVT_CHAR, wxKeyEventHandler( MainDialogGenerated::OnCfgHistoryKeyEvent ), NULL, this ); m_listBoxHistory->Disconnect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnLoadFromHistory ), NULL, this ); + m_listBoxHistory->Disconnect( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, wxCommandEventHandler( MainDialogGenerated::OnLoadFromHistoryDoubleClick ), NULL, this ); m_bpButtonFilter->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigureFilter ), NULL, this ); m_checkBoxShowExcluded->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnShowExcluded ), NULL, this ); m_bpButtonSyncCreateLeft->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSyncCreateLeft ), NULL, this ); @@ -1015,6 +1032,7 @@ CompareStatusGenerated::CompareStatusGenerated( wxWindow* parent, wxWindowID id, bSizer182 = new wxBoxSizer( wxVERTICAL ); m_textCtrlStatus = new wxTextCtrl( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, wxTE_READONLY ); + m_textCtrlStatus->SetMaxLength( 0 ); m_textCtrlStatus->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); bSizer182->Add( m_textCtrlStatus, 0, wxEXPAND, 5 ); @@ -2266,6 +2284,7 @@ SyncStatusDlgGenerated::SyncStatusDlgGenerated( wxWindow* parent, wxWindowID id, bSizer1811 = new wxBoxSizer( wxVERTICAL ); m_textCtrlStatus = new wxTextCtrl( m_panelProgress, wxID_ANY, _("dummy"), wxDefaultPosition, wxSize( -1,-1 ), wxTE_READONLY|wxNO_BORDER ); + m_textCtrlStatus->SetMaxLength( 0 ); m_textCtrlStatus->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); bSizer1811->Add( m_textCtrlStatus, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND|wxLEFT, 5 ); @@ -2508,6 +2527,7 @@ LogControlGenerated::LogControlGenerated( wxWindow* parent, wxWindowID id, const bSizer153->Add( m_staticline13, 0, wxEXPAND, 5 ); m_textCtrlInfo = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1,-1 ), wxTE_DONTWRAP|wxTE_MULTILINE|wxTE_READONLY|wxNO_BORDER ); + m_textCtrlInfo->SetMaxLength( 0 ); bSizer153->Add( m_textCtrlInfo, 1, wxEXPAND|wxALIGN_CENTER_VERTICAL, 5 ); @@ -2747,7 +2767,7 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer170->Add( 0, 0, 1, wxEXPAND, 5 ); - m_hyperlink1 = new wxHyperlinkCtrl( this, wxID_ANY, _("Homepage"), wxT("http://sourceforge.net/projects/freefilesync/"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); + m_hyperlink1 = new wxHyperlinkCtrl( this, wxID_ANY, _("Homepage"), wxT("http://freefilesync.sourceforge.net/"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); m_hyperlink1->SetFont( wxFont( 10, 70, 90, 92, true, wxEmptyString ) ); m_hyperlink1->SetToolTip( _("http://sourceforge.net/projects/freefilesync/") ); @@ -2855,6 +2875,7 @@ MessageDlgGenerated::MessageDlgGenerated( wxWindow* parent, wxWindowID id, const bSizer26->Add( m_bitmapMsgType, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 ); m_textCtrlMessage = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 400,130 ), wxTE_MULTILINE|wxTE_READONLY|wxNO_BORDER ); + m_textCtrlMessage->SetMaxLength( 0 ); bSizer26->Add( m_textCtrlMessage, 1, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 ); @@ -2963,6 +2984,7 @@ DeleteDlgGenerated::DeleteDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer24->Add( m_staticline91, 0, wxEXPAND|wxBOTTOM, 5 ); m_textCtrlFileList = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 550,200 ), wxTE_DONTWRAP|wxTE_MULTILINE|wxTE_READONLY|wxNO_BORDER ); + m_textCtrlFileList->SetMaxLength( 0 ); bSizer24->Add( m_textCtrlFileList, 1, wxEXPAND|wxLEFT, 5 ); m_staticline9 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); @@ -3107,6 +3129,7 @@ FilterDlgGenerated::FilterDlgGenerated( wxWindow* parent, wxWindowID id, const w sbSizer8->Add( m_bitmapInclude, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL|wxRIGHT, 5 ); m_textCtrlInclude = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1,-1 ), wxTE_MULTILINE ); + m_textCtrlInclude->SetMaxLength( 0 ); sbSizer8->Add( m_textCtrlInclude, 1, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL|wxEXPAND, 5 ); @@ -3119,6 +3142,7 @@ FilterDlgGenerated::FilterDlgGenerated( wxWindow* parent, wxWindowID id, const w sbSizer26->Add( m_bitmapExclude, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); m_textCtrlExclude = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1,-1 ), wxTE_MULTILINE ); + m_textCtrlExclude->SetMaxLength( 0 ); sbSizer26->Add( m_textCtrlExclude, 1, wxALIGN_CENTER_VERTICAL|wxEXPAND|wxALIGN_CENTER_HORIZONTAL, 5 ); @@ -3693,6 +3717,7 @@ SearchDialogGenerated::SearchDialogGenerated( wxWindow* parent, wxWindowID id, c bSizer162->Add( m_staticText101, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); m_textCtrlSearchTxt = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( 220,-1 ), 0 ); + m_textCtrlSearchTxt->SetMaxLength( 0 ); bSizer162->Add( m_textCtrlSearchTxt, 1, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); diff --git a/ui/gui_generated.h b/ui/gui_generated.h index fc5291e3..14e30f75 100644 --- a/ui/gui_generated.h +++ b/ui/gui_generated.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Apr 10 2012) +// C++ code generated with wxFormBuilder (version Oct 8 2012) // http://www.wxformbuilder.org/ // // PLEASE DO "NOT" EDIT THIS FILE! @@ -11,14 +11,15 @@ #include #include #include -#include "wx_form_build_hide_warnings.h" -#include "../wx+/button.h" -#include "folder_history_box.h" -#include "../wx+/grid.h" -#include "triple_splitter.h" -#include "../wx+/toggle_button.h" -#include "exec_finished_box.h" -#include "../wx+/graph.h" +class ExecFinishedBox; +class FolderHistoryBox; +class ToggleButton; +class wxStaticText; +namespace zen{ class BitmapButton; } +namespace zen{ class Graph2D; } +namespace zen{ class Grid; } +namespace zen{ class TripleSplitter; } + #include #include #include @@ -192,6 +193,7 @@ class MainDialogGenerated : public wxFrame virtual void OnSwapSides( wxCommandEvent& event ) { event.Skip(); } virtual void OnCfgHistoryKeyEvent( wxKeyEvent& event ) { event.Skip(); } virtual void OnLoadFromHistory( wxCommandEvent& event ) { event.Skip(); } + virtual void OnLoadFromHistoryDoubleClick( wxCommandEvent& event ) { event.Skip(); } virtual void OnConfigureFilter( wxCommandEvent& event ) { event.Skip(); } virtual void OnShowExcluded( wxCommandEvent& event ) { event.Skip(); } virtual void OnSyncCreateLeft( wxCommandEvent& event ) { event.Skip(); } diff --git a/ui/gui_status_handler.cpp b/ui/gui_status_handler.cpp index 63e394ff..120eab39 100644 --- a/ui/gui_status_handler.cpp +++ b/ui/gui_status_handler.cpp @@ -194,7 +194,8 @@ SyncStatusHandler::SyncStatusHandler(MainDialog* parentDlg, SyncStatusHandler::~SyncStatusHandler() { - const int totalErrors = errorLog.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR); //evaluate before finalizing log + const int totalErrors = errorLog.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR); //evaluate before finalizing log + const int totalWarnings = errorLog.getItemCount(TYPE_WARNING); //finalize error log //finalize error log @@ -207,7 +208,12 @@ SyncStatusHandler::~SyncStatusHandler() else if (totalErrors > 0) { finalStatus = _("Synchronization completed with errors!"); - errorLog.logMsg(finalStatus, TYPE_WARNING); + errorLog.logMsg(finalStatus, TYPE_ERROR); + } + else if (totalWarnings > 0) + { + finalStatus = _("Synchronization completed with warnings!"); + errorLog.logMsg(finalStatus, TYPE_WARNING); //give status code same warning priority as display category! } else { @@ -250,6 +256,8 @@ SyncStatusHandler::~SyncStatusHandler() syncStatusFrame.processHasFinished(SyncStatus::RESULT_ABORTED, errorLog); //enable okay and close events else if (totalErrors > 0) syncStatusFrame.processHasFinished(SyncStatus::RESULT_FINISHED_WITH_ERROR, errorLog); + else if (totalWarnings > 0) + syncStatusFrame.processHasFinished(SyncStatus::RESULT_FINISHED_WITH_WARNINGS, errorLog); else syncStatusFrame.processHasFinished(SyncStatus::RESULT_FINISHED_WITH_SUCCESS, errorLog); } diff --git a/ui/main_dlg.cpp b/ui/main_dlg.cpp index 2d52d45e..0004d2fc 100644 --- a/ui/main_dlg.cpp +++ b/ui/main_dlg.cpp @@ -55,6 +55,7 @@ #include #include #include "../lib/error_log.h" +#include "triple_splitter.h" using namespace zen; using namespace std::rel_ops; @@ -309,23 +310,23 @@ void setMenuItemImage(wxMenuItem*& menuItem, const wxBitmap& bmp) if (wxMenu* menu = menuItem->GetMenu()) { - int pos = menu->GetMenuItems().IndexOf(menuItem); - if (pos != wxNOT_FOUND) - { - /* - menu->Remove(item); ->this simple sequence crashes on Kubuntu x64, wxWidgets 2.9.2 - menu->Insert(index, item); - */ - const bool enabled = menuItem->IsEnabled(); - wxMenuItem* newItem = new wxMenuItem(menu, menuItem->GetId(), menuItem->GetItemLabel()); - newItem->SetBitmap(bmp); - - menu->Destroy(menuItem); //actual workaround - menuItem = menu->Insert(pos, newItem); //don't forget to update input item pointer! - - if (!enabled) - menuItem->Enable(false); //do not enable BEFORE appending item! wxWidgets screws up for yet another crappy reason - } + int pos = menu->GetMenuItems().IndexOf(menuItem); + if (pos != wxNOT_FOUND) + { + /* + menu->Remove(item); ->this simple sequence crashes on Kubuntu x64, wxWidgets 2.9.2 + menu->Insert(index, item); + */ + const bool enabled = menuItem->IsEnabled(); + wxMenuItem* newItem = new wxMenuItem(menu, menuItem->GetId(), menuItem->GetItemLabel()); + newItem->SetBitmap(bmp); + + menu->Destroy(menuItem); //actual workaround + menuItem = menu->Insert(pos, newItem); //don't forget to update input item pointer! + + if (!enabled) + menuItem->Enable(false); //do not enable BEFORE appending item! wxWidgets screws up for yet another crappy reason + } } } @@ -578,7 +579,7 @@ MainDialog::MainDialog(const xmlAccess::XmlGuiConfig& guiCfg, //init handling of first folder pair firstFolderPair.reset(new DirectoryPairFirst(*this)); - initViewFilterButtons(); + //initViewFilterButtons(); //init grid settings gridview::init(*m_gridMainL, *m_gridMainC, *m_gridMainR, gridDataView); @@ -783,6 +784,7 @@ void MainDialog::onQueryEndSession() catch (const xmlAccess::FfsXmlError&) {} } + void MainDialog::setGlobalCfgOnInit(const xmlAccess::XmlGlobalSettings& globalSettings) { globalCfg = globalSettings; @@ -930,7 +932,7 @@ void MainDialog::setSyncDirManually(const std::vector& select } -void MainDialog::setManualFilter(const std::vector& selection, bool setIncluded) +void MainDialog::setFilterManually(const std::vector& selection, bool setIncluded) { //if hidefiltered is active, there should be no filtered elements on screen => current element was filtered out assert(currentCfg.showFilteredElements || !setIncluded); @@ -940,7 +942,7 @@ void MainDialog::setManualFilter(const std::vector& selection std::for_each(selection.begin(), selection.end(), [&](FileSystemObject* fsObj) { zen::setActiveStatus(setIncluded, *fsObj); }); //works recursively for directories - updateGuiAfterFilterChange(400); //call this instead of updateGuiGrid() to add some delay if hideFiltered == true and to handle some graphical artifacts + updateGuiDelayedIf(!currentCfg.showFilteredElements); //show update GUI before removing rows } } @@ -1170,7 +1172,7 @@ void MainDialog::deleteSelectedFiles(const std::vector& selec if (!selectionLeft.empty() || !selectionRight.empty()) { wxWindow* oldFocus = wxWindow::FindFocus(); - ZEN_ON_SCOPE_EXIT( if (oldFocus) oldFocus->SetFocus(); ) + ZEN_ON_SCOPE_EXIT(if (oldFocus) oldFocus->SetFocus();) if (zen::showDeleteDialog(this, selectionLeft, @@ -1382,7 +1384,8 @@ void MainDialog::disableAllElements(bool enableAbort) //show abort button m_buttonAbort->Enable(); m_buttonAbort->Show(); - if (m_buttonAbort->IsShownOnScreen()) m_buttonAbort->SetFocus(); + if (m_buttonAbort->IsShownOnScreen()) + m_buttonAbort->SetFocus(); m_buttonCompare->Disable(); m_buttonCompare->Hide(); m_panelTopButtons->Layout(); @@ -1536,9 +1539,9 @@ void MainDialog::onTreeButtonEvent(wxKeyEvent& event) case WXK_SPACE: case WXK_NUMPAD_SPACE: { - const auto& selection = getTreeSelection(); + const std::vector& selection = getTreeSelection(); if (!selection.empty()) - setManualFilter(selection, !selection[0]->isActive()); + setFilterManually(selection, !selection[0]->isActive()); } return; @@ -1622,9 +1625,9 @@ void MainDialog::onGridButtonEvent(wxKeyEvent& event, Grid& grid, bool leftSide) case WXK_SPACE: case WXK_NUMPAD_SPACE: { - const auto& selection = getGridSelection(); + const std::vector& selection = getGridSelection(); if (!selection.empty()) - setManualFilter(selection, !selection[0]->isActive()); + setFilterManually(selection, !selection[0]->isActive()); } return; @@ -1718,14 +1721,16 @@ void MainDialog::OnGlobalKeyEvent(wxKeyEvent& event) //process key events withou if (!isPartOf(focus, m_gridMainL ) && // !isPartOf(focus, m_gridMainC ) && //don't propagate keyboard commands if grid is already in focus !isPartOf(focus, m_gridMainR ) && // + !isPartOf(focus, m_gridNavi ) && !isPartOf(focus, m_listBoxHistory) && //don't propagate if selecting config !isPartOf(focus, m_directoryLeft) && //don't propagate if changing directory field !isPartOf(focus, m_directoryRight) && - !isPartOf(focus, m_gridNavi ) && !isPartOf(focus, m_scrolledWindowFolderPairs)) - if (wxEvtHandler* evtHandler = m_gridMainL->GetEventHandler()) + if (wxEvtHandler* evtHandler = m_gridMainL->getMainWin().GetEventHandler()) { m_gridMainL->SetFocus(); + + event.SetEventType(wxEVT_KEY_DOWN); //the grid event handler doesn't expect wxEVT_CHAR_HOOK! evtHandler->ProcessEvent(event); //propagating event catched at wxTheApp to child leads to recursion, but we prevented it... event.Skip(false); //definitively handled now! return; @@ -1827,9 +1832,9 @@ void MainDialog::onNaviGridContext(GridClickEvent& event) if (!selection.empty()) { if (selection[0]->isActive()) - menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setManualFilter(selection, false); }, &GlobalResources::getImage(L"checkboxFalse")); + menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, false); }, &GlobalResources::getImage(L"checkboxFalse")); else - menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setManualFilter(selection, true); }, &GlobalResources::getImage(L"checkboxTrue")); + menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, true); }, &GlobalResources::getImage(L"checkboxTrue")); } else menu.addItem(_("Exclude temporarily") + L"\tSpace", [] {}, nullptr, false); @@ -1871,7 +1876,7 @@ void MainDialog::onMainGridContextC(GridClickEvent& event) menu.addItem(_("Exclude all"), [&] { zen::setActiveStatus(false, folderCmp); - updateGuiAfterFilterChange(400); //call this instead of updateGuiGrid() to add some delay if hideFiltered == true + updateGuiDelayedIf(!currentCfg.showFilteredElements); //show update GUI before removing rows }, nullptr, gridDataView->rowsTotal() > 0); menu.popup(*this); @@ -1919,9 +1924,9 @@ void MainDialog::onMainGridContextRim(bool leftSide) if (!selection.empty()) { if (selection[0]->isActive()) - menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setManualFilter(selection, false); }, &GlobalResources::getImage(L"checkboxFalse")); + menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, false); }, &GlobalResources::getImage(L"checkboxFalse")); else - menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setManualFilter(selection, true); }, &GlobalResources::getImage(L"checkboxTrue")); + menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, true); }, &GlobalResources::getImage(L"checkboxTrue")); } else menu.addItem(_("Exclude temporarily") + L"\tSpace", [] {}, nullptr, false); @@ -2163,7 +2168,8 @@ void MainDialog::onGridLabelContext(Grid& grid, ColumnTypeRim type, const std::v if (showSelectTimespanDlg(this, manualTimeSpanFrom, manualTimeSpanTo) == ReturnSmallDlg::BUTTON_OKAY) { applyTimeSpanFilter(folderCmp, manualTimeSpanFrom, manualTimeSpanTo); //overwrite current active/inactive settings - updateGuiAfterFilterChange(400); + //updateGuiDelayedIf(!currentCfg.showFilteredElements); //show update GUI before removing rows + updateGui(); } }; menu.addItem(_("Select time span..."), selectTimeSpan); @@ -2376,7 +2382,7 @@ void MainDialog::updateUnsavedCfgStatus() setImage(*m_bpButtonSave, allowSave ? GlobalResources::getImage(L"save") : makeBrightGrey(GlobalResources::getImage(L"save"))); m_bpButtonSave->Enable(allowSave); - m_menuItemSave->Enable(allowSave); //bitmap is automatically greyscaled on Win7 (introducing a crappy looking shift), but not on XP + m_menuItemSave->Enable(allowSave); //bitmap is automatically greyscaled on Win7 (introducing a crappy looking shift), but not on XP //set main dialog title wxString title; @@ -2551,31 +2557,50 @@ void MainDialog::OnLoadFromHistory(wxCommandEvent& event) }); if (!filenames.empty()) - { loadConfiguration(filenames); - //in case user cancelled saving old config, selection is wrong: so reapply it! - addFileToCfgHistory(activeConfigFiles); - } + //user changed m_listBoxHistory selection so it's this method's responsibility to synchronize with activeConfigFiles + //- if user cancelled saving old config + //- there's an error loading new config + //- filenames is empty and user tried to unselect the current config + addFileToCfgHistory(activeConfigFiles); } -void MainDialog::loadConfiguration(const wxString& filename) +void MainDialog::OnLoadFromHistoryDoubleClick(wxCommandEvent& event) { + wxArrayInt selections; + m_listBoxHistory->GetSelections(selections); + std::vector filenames; - filenames.push_back(filename); + std::for_each(selections.begin(), selections.end(), + [&](int pos) + { + if (auto histData = dynamic_cast(m_listBoxHistory->GetClientObject(pos))) + filenames.push_back(histData->cfgFile_); + }); - loadConfiguration(filenames); + if (!filenames.empty()) + if (loadConfiguration(filenames)) + { + //simulate button click on "compare" + wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED); + if (wxEvtHandler* evtHandler = m_buttonCompare->GetEventHandler()) + evtHandler->ProcessEvent(dummy2); //synchronous call + } + + //synchronize m_listBoxHistory and activeConfigFiles, see OnLoadFromHistory() + addFileToCfgHistory(activeConfigFiles); } -void MainDialog::loadConfiguration(const std::vector& filenames) +bool MainDialog::loadConfiguration(const std::vector& filenames) { if (filenames.empty()) - return; + return true; if (!saveOldConfig()) - return; + return false; //cancelled by user //load XML xmlAccess::XmlGuiConfig newGuiCfg; //structure to receive gui settings, already defaulted!! @@ -2586,6 +2611,7 @@ void MainDialog::loadConfiguration(const std::vector& filenames) setConfig(newGuiCfg, filenames); //flashStatusInformation(_("Configuration loaded!")); -> irrelvant!? + return true; } catch (const xmlAccess::FfsXmlError& error) { @@ -2597,6 +2623,7 @@ void MainDialog::loadConfiguration(const std::vector& filenames) } else wxMessageBox(error.toString(), _("Error"), wxOK | wxICON_ERROR, this); + return false; } } @@ -2670,7 +2697,7 @@ void MainDialog::onCheckRows(CheckRowsEvent& event) selectedRows.insert(i); std::vector objects = gridDataView->getAllFileRef(selectedRows); - setManualFilter(objects, event.setIncluded_); + setFilterManually(objects, event.setIncluded_); } } @@ -2720,7 +2747,7 @@ void MainDialog::setConfig(const xmlAccess::XmlGuiConfig& newGuiCfg, const std:: //evaluate new settings... //(re-)set view filter buttons - initViewFilterButtons(); + initViewFilterButtons(newGuiCfg.mainCfg); updateFilterButtons(); @@ -2803,18 +2830,18 @@ const wxString& MainDialog::lastRunConfigName() } -void MainDialog::updateGuiAfterFilterChange(int delay) +void MainDialog::updateGuiDelayedIf(bool condition) { - //signal UI that grids need to be refreshed on next Update() + const int delay = 400; - if (!currentCfg.showFilteredElements) + if (condition) { gridview::refresh(*m_gridMainL, *m_gridMainC, *m_gridMainR); m_gridMainL->Update(); m_gridMainC->Update(); m_gridMainR->Update(); - wxMilliSleep(delay); //some delay to show user the rows he has filtered out before they are removed + wxMilliSleep(delay); //some delay to show the changed GUI before removing rows from sight } updateGui(); @@ -2982,7 +3009,7 @@ wxBitmap buttonReleased(const std::string& name) } -void MainDialog::initViewFilterButtons() +void MainDialog::initViewFilterButtons(const MainConfiguration& mainCfg) { //compare result buttons m_bpButtonLeftOnly->init(buttonPressed("leftOnly"), @@ -3061,7 +3088,6 @@ void MainDialog::initViewFilterButtons() m_bpButtonRightOnly-> setActive(true); m_bpButtonLeftNewer-> setActive(true); m_bpButtonRightNewer->setActive(true); - m_bpButtonEqual-> setActive(false); m_bpButtonDifferent-> setActive(true); m_bpButtonConflict-> setActive(true); @@ -3072,7 +3098,46 @@ void MainDialog::initViewFilterButtons() m_bpButtonSyncDeleteRight-> setActive(true); m_bpButtonSyncDirOverwLeft-> setActive(true); m_bpButtonSyncDirOverwRight->setActive(true); - m_bpButtonSyncDirNone-> setActive(true); + + m_bpButtonEqual->setActive(false); + + //special case "m_bpButtonSyncDirNone": set always active, unless sync direction "none" is part of the rule set: + //e.g. for a "custom" config or "update" variant. Otherwise rows with sync direction "none" can only occur on grid if the user manually + //sets them, in which case these rows should not be hidden immediately, so m_bpButtonSyncDirNone must be active + const std::vector& cmpCfg = extractCompareCfg(mainCfg); + const bool syncDirNonePartOfConfig = std::any_of(cmpCfg.begin(), cmpCfg.end(), + [&](const FolderPairCfg& fpCfg) -> bool + { + //attention: following is quite an amount of implicit/redundant knowledge here... let's hope our model is fundamental enough to not change any time soon! + + if (fpCfg.directionCfg.var == DirectionConfig::AUTOMATIC) + return false; + + const DirectionSet dirSet = extractDirections(fpCfg.directionCfg); + + switch (fpCfg.compareVar) + { + case CMP_BY_TIME_SIZE: + return dirSet.exLeftSideOnly == SYNC_DIR_NONE || + dirSet.exRightSideOnly == SYNC_DIR_NONE || + dirSet.leftNewer == SYNC_DIR_NONE || + dirSet.rightNewer == SYNC_DIR_NONE; + //dirSet.different == SYNC_DIR_NONE || + //dirSet.conflict == SYNC_DIR_NONE; + + case CMP_BY_CONTENT: + return dirSet.exLeftSideOnly == SYNC_DIR_NONE || + dirSet.exRightSideOnly == SYNC_DIR_NONE || + //dirSet.leftNewer == SYNC_DIR_NONE || + //dirSet.rightNewer == SYNC_DIR_NONE || + dirSet.different == SYNC_DIR_NONE; + //dirSet.conflict == SYNC_DIR_NONE; + } + assert(false); + return false; + }); + + m_bpButtonSyncDirNone->setActive(!syncDirNonePartOfConfig); } @@ -3105,7 +3170,7 @@ void MainDialog::OnCompare(wxCommandEvent& event) wxBusyCursor dummy; //show hourglass cursor wxWindow* oldFocus = wxWindow::FindFocus(); - ZEN_ON_SCOPE_EXIT( if (oldFocus) oldFocus->SetFocus(); ); //e.g. keep focus on main grid after pressing F5 + ZEN_ON_SCOPE_EXIT(if (oldFocus) oldFocus->SetFocus();) //e.g. keep focus on main grid after pressing F5 int scrollPosX = 0; int scrollPosY = 0; @@ -3169,7 +3234,7 @@ void MainDialog::OnCompare(wxCommandEvent& event) //add to folder history after successful comparison only folderHistoryLeft ->addItem(toZ(m_directoryLeft->GetValue())); folderHistoryRight->addItem(toZ(m_directoryRight->GetValue())); - + //prepare status information if (allElementsEqual(folderCmp)) flashStatusInformation(_("All folders are in sync!")); @@ -3378,11 +3443,6 @@ void MainDialog::OnStartSync(wxCommandEvent& event) syncProcessCfg, folderCmp, statusHandler); - - //play (optional) sound notification after sync has completed (GUI and batch mode) - const wxString soundFile = toWx(zen::getResourceDir()) + L"Sync_Complete.wav"; - if (fileExists(toZ(soundFile))) - wxSound::Play(soundFile, wxSOUND_ASYNC); } catch (GuiAbortProcess&) { @@ -3674,7 +3734,9 @@ void MainDialog::updateGridViewData() void MainDialog::applyFilterConfig() { applyFiltering(folderCmp, getConfig().mainCfg); - updateGuiAfterFilterChange(400); + + updateGui(); + //updateGuiDelayedIf(!currentCfg.showFilteredElements); //show update GUI before removing rows } @@ -3771,7 +3833,7 @@ void MainDialog::updateGuiForFolderPair() m_bpButtonLocalFilter->Show(showLocalCfgFirstPair); setImage(*m_bpButtonSwapSides, GlobalResources::getImage(showLocalCfgFirstPair ? L"swapSlim" : L"swap")); m_panelTopMiddle->Layout(); //both required to update button size for calculations below!!! - m_panelDirectoryPairs->Layout(); // -> updates size of stretched m_panelTopLeft! + m_panelDirectoryPairs->Layout(); // -> updates size of stretched m_panelTopLeft! int addPairMinimalHeight = 0; int addPairOptimalHeight = 0; diff --git a/ui/main_dlg.h b/ui/main_dlg.h index 47630670..f5f07624 100644 --- a/ui/main_dlg.h +++ b/ui/main_dlg.h @@ -18,6 +18,7 @@ #include "custom_grid.h" #include "tree_view.h" #include +#include "folder_history_box.h" //class FolderHistory; class DirectoryPair; @@ -77,8 +78,7 @@ private: void setGlobalCfgOnInit(const xmlAccess::XmlGlobalSettings& globalSettings); //messes with Maximize(), window sizes, so call just once! xmlAccess::XmlGlobalSettings getGlobalCfgBeforeExit(); //destructive "get" thanks to "Iconize(false), Maximize(false)" - void loadConfiguration(const wxString& filename); - void loadConfiguration(const std::vector& filenames); + bool loadConfiguration(const std::vector& filenames); //return true if loaded successfully bool trySaveConfig(const wxString* fileName); //return true if saved successfully bool saveOldConfig(); //return false on user abort @@ -89,10 +89,10 @@ private: //used when saving configuration std::vector activeConfigFiles; //name of currently loaded config file (may be more than 1) - void initViewFilterButtons(); + void initViewFilterButtons(const zen::MainConfiguration& mainCfg); void updateFilterButtons(); - void addFileToCfgHistory(const std::vector& filenames); + void addFileToCfgHistory(const std::vector& filenames); //= update/insert + apply selection void addFolderPair(const std::vector& newPairs, bool addFront = false); void removeAddFolderPair(size_t pos); @@ -102,6 +102,8 @@ private: //main method for putting gridDataView on UI: updates data respecting current view settings void updateGui(); //kitchen-sink update + void updateGuiDelayedIf(bool condition); // 400 ms delay + void updateGridViewData(); // void updateStatistics(); // more fine-grained updaters void updateUnsavedCfgStatus(); // @@ -111,7 +113,7 @@ private: std::vector getTreeSelection() const; void setSyncDirManually(const std::vector& selection, zen::SyncDirection direction); - void setManualFilter(const std::vector& selection, bool setIncluded); + void setFilterManually(const std::vector& selection, bool setIncluded); void copySelectionToClipboard(); void deleteSelectedFiles(const std::vector& selectionLeft, const std::vector& selectionRight); @@ -194,6 +196,7 @@ private: void OnConfigSaveAs (wxCommandEvent& event); void OnConfigLoad (wxCommandEvent& event); void OnLoadFromHistory(wxCommandEvent& event); + void OnLoadFromHistoryDoubleClick(wxCommandEvent& event); void OnCfgHistoryKeyEvent(wxKeyEvent& event); void OnRegularUpdateCheck(wxIdleEvent& event); @@ -212,8 +215,6 @@ private: void OnStartSync (wxCommandEvent& event); void OnClose (wxCloseEvent& event); - void updateGuiAfterFilterChange(int delay); - void excludeExtension(const Zstring& extension); void excludeShortname(const zen::FileSystemObject& fsObj); void excludeItems(const std::vector& selection); diff --git a/ui/progress_indicator.cpp b/ui/progress_indicator.cpp index 096d7413..ebbc9edd 100644 --- a/ui/progress_indicator.cpp +++ b/ui/progress_indicator.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -16,7 +17,9 @@ #include #include #include +#include #include "gui_generated.h" +#include "../lib/ffs_paths.h" #include "../lib/resources.h" #include "../lib/perf_check.h" #include "tray_icon.h" @@ -361,20 +364,19 @@ private: includedTypes |= TYPE_INFO; //fast replacement for wxString modelling exponential growth - typedef Zbase zxString; - zxString logText; + MsgString logText; const auto& entries = log_.getEntries(); for (auto iter = entries.begin(); iter != entries.end(); ++iter) if (iter->type & includedTypes) { - logText += copyStringTo(formatMessage(*iter)); + logText += formatMessage(*iter); logText += L'\n'; } if (logText.empty()) //if no messages match selected view filter, at least show final status message if (!entries.empty()) - logText = copyStringTo(formatMessage(entries.back())); + logText = formatMessage(entries.back()); wxWindowUpdateLocker dummy(m_textCtrlInfo); m_textCtrlInfo->ChangeValue(copyStringTo(logText)); @@ -810,6 +812,7 @@ std::wstring getDialogStatusText(const Statistics* syncStat, bool paused, SyncSt case SyncStatus::RESULT_ABORTED: return _("Aborted"); case SyncStatus::RESULT_FINISHED_WITH_ERROR: + case SyncStatus::RESULT_FINISHED_WITH_WARNINGS: case SyncStatus::RESULT_FINISHED_WITH_SUCCESS: return _("Completed"); } @@ -1007,15 +1010,13 @@ std::wstring SyncStatus::SyncStatusImpl::getExecWhenFinishedCommand() const void SyncStatus::SyncStatusImpl::updateDialogStatus() //depends on "syncStat_, paused_, finalResult" { - m_staticTextStatus->SetLabel(getDialogStatusText(syncStat_, paused_, finalResult)); + const wxString dlgStatusTxt = getDialogStatusText(syncStat_, paused_, finalResult); + m_staticTextStatus->SetLabel(dlgStatusTxt); + + //status bitmap if (syncStat_) //sync running { - if (paused_) - m_buttonPause->SetLabel(_("Continue")); - else - m_buttonPause->SetLabel(_("Pause")); - if (paused_) m_bitmapStatus->SetBitmap(GlobalResources::getImage(L"statusPause")); else @@ -1036,20 +1037,30 @@ void SyncStatus::SyncStatusImpl::updateDialogStatus() //depends on "syncStat_, p m_bitmapStatus->SetBitmap(GlobalResources::getImage(L"statusSyncing")); break; } + + m_bitmapStatus->SetToolTip(dlgStatusTxt); } else //sync finished switch (finalResult) { case RESULT_ABORTED: - m_bitmapStatus->SetBitmap(GlobalResources::getImage(L"statusError")); + m_bitmapStatus->SetBitmap(GlobalResources::getImage(L"statusAborted")); + m_bitmapStatus->SetToolTip(_("Synchronization aborted!")); break; case RESULT_FINISHED_WITH_ERROR: - m_bitmapStatus->SetBitmap(GlobalResources::getImage(L"statusWarning")); + m_bitmapStatus->SetBitmap(GlobalResources::getImage(L"statusFinishedErrors")); + m_bitmapStatus->SetToolTip(_("Synchronization completed with errors!")); + break; + + case RESULT_FINISHED_WITH_WARNINGS: + m_bitmapStatus->SetBitmap(GlobalResources::getImage(L"statusFinishedWarnings")); + m_bitmapStatus->SetToolTip(_("Synchronization completed with warnings!")); break; case RESULT_FINISHED_WITH_SUCCESS: - m_bitmapStatus->SetBitmap(GlobalResources::getImage(L"statusSuccess")); + m_bitmapStatus->SetBitmap(GlobalResources::getImage(L"statusFinishedSuccess")); + m_bitmapStatus->SetToolTip(_("Synchronization completed successfully!")); break; } @@ -1082,12 +1093,22 @@ void SyncStatus::SyncStatusImpl::updateDialogStatus() //depends on "syncStat_, p taskbar_->setStatus(Taskbar::STATUS_ERROR); break; + case RESULT_FINISHED_WITH_WARNINGS: case RESULT_FINISHED_WITH_SUCCESS: taskbar_->setStatus(Taskbar::STATUS_NORMAL); break; } } + //pause button + if (syncStat_) //sync running + { + if (paused_) + m_buttonPause->SetLabel(_("Continue")); + else + m_buttonPause->SetLabel(_("Pause")); + } + m_panelHeader->Layout(); Layout(); } @@ -1228,6 +1249,22 @@ void SyncStatus::SyncStatusImpl::processHasFinished(SyncResult resultId, const E m_panelFooter->Layout(); Layout(); + //play (optional) sound notification after sync has completed -> only play when waiting on results dialog, seems to be pointless otherwise! + switch (finalResult) + { + case SyncStatus::RESULT_ABORTED: + break; + case SyncStatus::RESULT_FINISHED_WITH_ERROR: + case SyncStatus::RESULT_FINISHED_WITH_WARNINGS: + case SyncStatus::RESULT_FINISHED_WITH_SUCCESS: + { + const Zstring soundFile = getResourceDir() + Zstr("Sync_Complete.wav"); + if (fileExists(soundFile)) + wxSound::Play(utfCvrtTo(soundFile), wxSOUND_ASYNC); //warning: this may fail and show a wxWidgets error message! => must not play when running FFS as a service! + } + break; + } + //Raise(); -> don't! user may be watching a movie in the meantime ;) } diff --git a/ui/progress_indicator.h b/ui/progress_indicator.h index 5628694f..b3d7ff2f 100644 --- a/ui/progress_indicator.h +++ b/ui/progress_indicator.h @@ -61,6 +61,7 @@ public: { RESULT_ABORTED, RESULT_FINISHED_WITH_ERROR, + RESULT_FINISHED_WITH_WARNINGS, RESULT_FINISHED_WITH_SUCCESS }; //essential to call one of these two methods in StatusUpdater derived class destructor at the LATEST(!) diff --git a/ui/search.cpp b/ui/search.cpp index fd3a11c5..be384f74 100644 --- a/ui/search.cpp +++ b/ui/search.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include using namespace zen; diff --git a/ui/sync_cfg.cpp b/ui/sync_cfg.cpp index d159503d..ef9d2a68 100644 --- a/ui/sync_cfg.cpp +++ b/ui/sync_cfg.cpp @@ -181,7 +181,7 @@ void updateConfigIcons(const DirectionConfig& directionCfg, buttonConflict->SetToolTip(getSyncOpDescription(SO_OVERWRITE_LEFT)); break; case SYNC_DIR_NONE: - buttonConflict->SetBitmapLabel(mirrorIfRtl(GlobalResources::getImage(L"conflict"))); + buttonConflict->SetBitmapLabel(mirrorIfRtl(GlobalResources::getImage(L"conflict"))); //silent dependency to algorithm.cpp::Redetermine!!! buttonConflict->SetToolTip(_("Leave as unresolved conflict")); break; } -- cgit