diff options
Diffstat (limited to 'ui')
-rw-r--r-- | ui/check_version.cpp | 22 | ||||
-rw-r--r-- | ui/check_version.h | 4 | ||||
-rw-r--r-- | ui/column_attr.h | 12 | ||||
-rw-r--r-- | ui/custom_grid.cpp | 61 | ||||
-rw-r--r-- | ui/custom_grid.h | 4 | ||||
-rw-r--r-- | ui/folder_pair.h | 127 | ||||
-rw-r--r-- | ui/gui_generated.cpp | 77 | ||||
-rw-r--r-- | ui/gui_generated.h | 16 | ||||
-rw-r--r-- | ui/main_dlg.cpp | 632 | ||||
-rw-r--r-- | ui/main_dlg.h | 60 | ||||
-rw-r--r-- | ui/msg_popup.cpp | 2 | ||||
-rw-r--r-- | ui/progress_indicator.cpp | 20 | ||||
-rw-r--r-- | ui/small_dlgs.cpp | 58 | ||||
-rw-r--r-- | ui/switch_to_gui.h | 4 | ||||
-rw-r--r-- | ui/sync_cfg.cpp | 6 | ||||
-rw-r--r-- | ui/tree_view.cpp | 143 | ||||
-rw-r--r-- | ui/tree_view.h | 35 |
17 files changed, 720 insertions, 563 deletions
diff --git a/ui/check_version.cpp b/ui/check_version.cpp index f01239da..b736b361 100644 --- a/ui/check_version.cpp +++ b/ui/check_version.cpp @@ -5,22 +5,25 @@ // ************************************************************************** #include "check_version.h" -#include <memory> +//#include <memory> #include <zen/string_tools.h> #include <zen/i18n.h> #include <zen/utf.h> #include <wx/msgdlg.h> -#include <wx/protocol/http.h> -#include <wx/sstream.h> -#include <wx/utils.h> #include <wx/timer.h> +#include <wx/utils.h> #include "msg_popup.h" #include "../version/version.h" -//#include "../lib/ffs_paths.h" +////#include "../lib/ffs_paths.h" #include <zen/scope_guard.h> #ifdef FFS_WIN +#include <zen/win.h> //tame wininet include #include <wininet.h> + +#elif defined FFS_LINUX || defined FFS_MAC +#include <wx/protocol/http.h> +#include <wx/sstream.h> #endif using namespace zen; @@ -158,7 +161,7 @@ GetVerResult getOnlineVersion(wxString& version) //empty string on error; { wxHTTP webAccess; webAccess.SetHeader(L"content-type", L"text/html; charset=utf-8"); - webAccess.SetTimeout(timeout); //default: 10 minutes(WTF are they thinking???)... + webAccess.SetTimeout(timeout); //default: 10 minutes(WTF are these wxWidgets people thinking???)... if (webAccess.Connect(server)) //will *not* fail for non-reachable url here! { @@ -245,7 +248,7 @@ void zen::checkForUpdateNow(wxWindow* parent) } -void zen::checkForUpdatePeriodically(wxWindow* parent, long& lastUpdateCheck) +void zen::checkForUpdatePeriodically(wxWindow* parent, long& lastUpdateCheck, const std::function<void()>& onBeforeInternetAccess) { if (lastUpdateCheck != -1) { @@ -268,9 +271,10 @@ void zen::checkForUpdatePeriodically(wxWindow* parent, long& lastUpdateCheck) // break; // } //} - //else - if (wxGetLocalTime() >= lastUpdateCheck + 7 * 24 * 3600) //check weekly + //else + if (wxGetLocalTime() >= lastUpdateCheck + 7 * 24 * 3600) //check weekly { + onBeforeInternetAccess(); //notify client before (potentially) blocking some time wxString onlineVersion; switch (getOnlineVersion(onlineVersion)) { diff --git a/ui/check_version.h b/ui/check_version.h index a61cc98c..d2e7220f 100644 --- a/ui/check_version.h +++ b/ui/check_version.h @@ -7,14 +7,14 @@ #ifndef UPDATEVERSION_H_INCLUDED #define UPDATEVERSION_H_INCLUDED +#include <functional> #include <wx/window.h> namespace zen { void checkForUpdateNow(wxWindow* parent); - -void checkForUpdatePeriodically(wxWindow* parent, long& lastUpdateCheck); +void checkForUpdatePeriodically(wxWindow* parent, long& lastUpdateCheck, const std::function<void()>& onBeforeInternetAccess); //-1: check never } #endif // UPDATEVERSION_H_INCLUDED diff --git a/ui/column_attr.h b/ui/column_attr.h index ab196cb1..5f0d79aa 100644 --- a/ui/column_attr.h +++ b/ui/column_attr.h @@ -76,7 +76,8 @@ enum ColumnTypeMiddle enum ColumnTypeNavi { COL_TYPE_NAVI_BYTES, - COL_TYPE_NAVI_DIRECTORY + COL_TYPE_NAVI_DIRECTORY, + COL_TYPE_NAVI_ITEM_COUNT }; @@ -93,15 +94,16 @@ struct ColumnAttributeNavi const bool defaultValueShowPercentage = true; -const ColumnTypeNavi defaultValueLastSortColumn = COL_TYPE_NAVI_DIRECTORY; //remember sort on navigation panel -const bool defaultValueLastSortAscending = true; // +const ColumnTypeNavi defaultValueLastSortColumn = COL_TYPE_NAVI_BYTES; //remember sort on navigation panel +const bool defaultValueLastSortAscending = false; // inline std::vector<ColumnAttributeNavi> getDefaultColumnAttributesNavi() { std::vector<ColumnAttributeNavi> attr; - attr.push_back(ColumnAttributeNavi(COL_TYPE_NAVI_DIRECTORY, -60, 1, true)); //stretch to full width and substract sum of fixed size widths - attr.push_back(ColumnAttributeNavi(COL_TYPE_NAVI_BYTES, 60, 0, true)); //GTK needs a few pixels width more + attr.push_back(ColumnAttributeNavi(COL_TYPE_NAVI_DIRECTORY, -120, 1, true)); //stretch to full width and substract sum of fixed size widths + attr.push_back(ColumnAttributeNavi(COL_TYPE_NAVI_ITEM_COUNT, 60, 0, true)); + attr.push_back(ColumnAttributeNavi(COL_TYPE_NAVI_BYTES, 60, 0, true)); //GTK needs a few pixels width more return attr; } } diff --git a/ui/custom_grid.cpp b/ui/custom_grid.cpp index 006ad834..35d3248d 100644 --- a/ui/custom_grid.cpp +++ b/ui/custom_grid.cpp @@ -695,11 +695,11 @@ class GridDataLeft : public GridDataRim<LEFT_SIDE> public: GridDataLeft(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid) : GridDataRim<LEFT_SIDE>(gridDataView, grid) {} - void setNavigationMarker(std::vector<const HierarchyObject*>&& markedFiles, - std::vector<const HierarchyObject*>&& markedContainer) + void setNavigationMarker(hash_set<const FileSystemObject*>&& markedFilesAndLinks, + hash_set<const HierarchyObject*>&& markedContainer) { - markedFiles_ .swap(markedFiles); - markedContainer_.swap(markedContainer); + markedFilesAndLinks_.swap(markedFilesAndLinks); + markedContainer_ .swap(markedContainer); } private: @@ -714,33 +714,26 @@ private: { if (const FileSystemObject* fsObj = getRawData(row)) { - if (dynamic_cast<const FileMapping*>(fsObj) || dynamic_cast<const SymLinkMapping*>(fsObj)) - { - for (auto it = markedFiles_.begin(); it != markedFiles_.end(); ++it) - if (*it == &(fsObj->parent())) //mark files/links wich have the given parent - return true; - } - else if (auto dirObj = dynamic_cast<const DirMapping*>(fsObj)) + if (markedFilesAndLinks_.find(fsObj) != markedFilesAndLinks_.end()) //mark files/links directly + return true; + + if (auto dirObj = dynamic_cast<const DirMapping*>(fsObj)) { - for (auto it = markedContainer_.begin(); it != markedContainer_.end(); ++it) - if (*it == dirObj) //mark directories which *are* the given HierarchyObject* - return true; + if (markedContainer_.find(dirObj) != markedContainer_.end()) //mark directories which *are* the given HierarchyObject* + return true; } - for (auto it = markedContainer_.begin(); it != markedContainer_.end(); ++it) + //mark all objects which have the HierarchyObject as *any* matching ancestor + const HierarchyObject* parent = &(fsObj->parent()); + for (;;) { - //mark all objects which have the HierarchyObject as *any* matching ancestor - const HierarchyObject* parent = &(fsObj->parent()); - for (;;) - { - if (*it == parent) - return true; + if (markedContainer_.find(parent) != markedContainer_.end()) + return true; - if (auto dirObj = dynamic_cast<const DirMapping*>(parent)) - parent = &(dirObj->parent()); - else - break; - } + if (auto dirObj = dynamic_cast<const DirMapping*>(parent)) + parent = &(dirObj->parent()); + else + break; } } return false; @@ -758,8 +751,8 @@ private: } } - std::vector<const HierarchyObject*> markedFiles_; //mark files/symlinks directly within a container - std::vector<const HierarchyObject*> markedContainer_; //mark full container including all child-objects + hash_set<const FileSystemObject*> markedFilesAndLinks_; //mark files/symlinks directly within a container + hash_set<const HierarchyObject*> markedContainer_; //mark full container including all child-objects //DO NOT DEREFERENCE!!!! NOT GUARANTEED TO BE VALID!!! }; @@ -837,7 +830,8 @@ public: } //update highlight and tooltip: on OS X no mouse movement event is generated after a mouse button click (unlike on Windows) - onMouseMovement(refGrid().getMainWin().ScreenToClient(wxGetMousePosition())); + wxPoint clientPos = refGrid().getMainWin().ScreenToClient(wxGetMousePosition()); + onMouseMovement(clientPos); } void onMouseMovement(const wxPoint& clientPos) @@ -859,7 +853,8 @@ public: refreshCell(refGrid(), highlight->row_, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE)); //show custom tooltip - showToolTip(row, refGrid().getMainWin().ClientToScreen(clientPos)); + if (refGrid().getMainWin().GetClientRect().Contains(clientPos)) //cursor might have moved outside visible client area + showToolTip(row, refGrid().getMainWin().ClientToScreen(clientPos)); } else onMouseLeave(); @@ -1623,11 +1618,11 @@ void gridview::refresh(Grid& gridLeft, Grid& gridCenter, Grid& gridRight) void gridview::setNavigationMarker(Grid& gridLeft, - std::vector<const HierarchyObject*>&& markedFiles, - std::vector<const HierarchyObject*>&& markedContainer) + hash_set<const FileSystemObject*>&& markedFilesAndLinks, + hash_set<const HierarchyObject*>&& markedContainer) { if (auto* provLeft = dynamic_cast<GridDataLeft*>(gridLeft.getDataProvider())) - provLeft->setNavigationMarker(std::move(markedFiles), std::move(markedContainer)); + provLeft->setNavigationMarker(std::move(markedFilesAndLinks), std::move(markedContainer)); else assert(false); gridLeft.Refresh(); diff --git a/ui/custom_grid.h b/ui/custom_grid.h index 6381c8c0..c2e653ef 100644 --- a/ui/custom_grid.h +++ b/ui/custom_grid.h @@ -31,8 +31,8 @@ void refresh(Grid& gridLeft, Grid& gridCenter, Grid& gridRight); //mark rows selected in navigation/compressed tree and navigate to leading object void setNavigationMarker(Grid& gridLeft, - std::vector<const HierarchyObject*>&& markedFiles, //mark files/symlinks directly within a container - std::vector<const HierarchyObject*>&& markedContainer); //mark full container including child-objects + hash_set<const FileSystemObject*>&& markedFilesAndLinks,//mark files/symlinks directly within a container + hash_set<const HierarchyObject*>&& markedContainer); //mark full container including child-objects } wxBitmap getSyncOpImage(SyncOperation syncOp); diff --git a/ui/folder_pair.h b/ui/folder_pair.h index 5a629ff5..801cd606 100644 --- a/ui/folder_pair.h +++ b/ui/folder_pair.h @@ -4,8 +4,8 @@ // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** -#ifndef FOLDERPAIR_H_INCLUDED -#define FOLDERPAIR_H_INCLUDED +#ifndef FOLDERPAIR_H_89341750847252345 +#define FOLDERPAIR_H_89341750847252345 #include <wx/event.h> #include <wx/menu.h> @@ -30,10 +30,6 @@ public: typedef std::shared_ptr<const CompConfig> AltCompCfgPtr; typedef std::shared_ptr<const SyncConfig> AltSyncCfgPtr; - AltCompCfgPtr getAltCompConfig() const { return altCompConfig; } - AltSyncCfgPtr getAltSyncConfig() const { return altSyncConfig; } - FilterConfig getAltFilterConfig() const { return localFilter; } - void setConfig(AltCompCfgPtr compConfig, AltSyncCfgPtr syncCfg, const FilterConfig& filter) { altCompConfig = compConfig; @@ -42,6 +38,27 @@ public: refreshButtons(); } + AltCompCfgPtr getAltCompConfig() const { return altCompConfig; } + AltSyncCfgPtr getAltSyncConfig() const { return altSyncConfig; } + FilterConfig getAltFilterConfig() const { return localFilter; } + + + FolderPairPanelBasic(GuiPanel& basicPanel) : //takes reference on basic panel to be enhanced + basicPanel_(basicPanel) + { + //register events for removal of alternate configuration + basicPanel_.m_bpButtonAltCompCfg ->Connect(wxEVT_RIGHT_DOWN, wxCommandEventHandler(FolderPairPanelBasic::OnAltCompCfgContext ), nullptr, this); + basicPanel_.m_bpButtonAltSyncCfg ->Connect(wxEVT_RIGHT_DOWN, wxCommandEventHandler(FolderPairPanelBasic::OnAltSyncCfgContext ), nullptr, this); + basicPanel_.m_bpButtonLocalFilter->Connect(wxEVT_RIGHT_DOWN, wxCommandEventHandler(FolderPairPanelBasic::OnLocalFilterCfgContext), nullptr, this); + + basicPanel_.m_bpButtonAltCompCfg-> Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderPairPanelBasic::OnAltCompCfg ), nullptr, this); + basicPanel_.m_bpButtonAltSyncCfg-> Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderPairPanelBasic::OnAltSyncCfg ), nullptr, this); + basicPanel_.m_bpButtonLocalFilter->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderPairPanelBasic::OnLocalFilterCfg), nullptr, this); + + basicPanel_.m_bpButtonRemovePair->SetBitmapLabel(getResourceImage(L"item_delete")); + } + +private: void refreshButtons() { if (altCompConfig.get()) @@ -79,69 +96,72 @@ public: } } -protected: - FolderPairPanelBasic(GuiPanel& basicPanel) : //takes reference on basic panel to be enhanced - basicPanel_(basicPanel) - { - //register events for removal of alternate configuration - basicPanel_.m_bpButtonAltCompCfg ->Connect(wxEVT_RIGHT_DOWN, wxCommandEventHandler(FolderPairPanelBasic::OnAltCompCfgContext ), nullptr, this); - basicPanel_.m_bpButtonAltSyncCfg ->Connect(wxEVT_RIGHT_DOWN, wxCommandEventHandler(FolderPairPanelBasic::OnAltSyncCfgContext ), nullptr, this); - basicPanel_.m_bpButtonLocalFilter->Connect(wxEVT_RIGHT_DOWN, wxCommandEventHandler(FolderPairPanelBasic::OnLocalFilterCfgContext), nullptr, this); - - basicPanel_.m_bpButtonAltCompCfg-> Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderPairPanelBasic::OnAltCompCfg ), nullptr, this); - basicPanel_.m_bpButtonAltSyncCfg-> Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderPairPanelBasic::OnAltSyncCfg ), nullptr, this); - basicPanel_.m_bpButtonLocalFilter->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(FolderPairPanelBasic::OnLocalFilterCfg), nullptr, this); - - basicPanel_.m_bpButtonRemovePair->SetBitmapLabel(getResourceImage(L"item_delete")); - } - - virtual void removeAltCompCfg() - { - altCompConfig.reset(); - refreshButtons(); - } - - virtual void removeAltSyncCfg() - { - altSyncConfig.reset(); - refreshButtons(); - } - - virtual void removeLocalFilterCfg() - { - localFilter = FilterConfig(); - refreshButtons(); - } - -private: void OnAltCompCfgContext(wxCommandEvent& event) { + auto removeAltCompCfg = [&] + { + this->altCompConfig.reset(); //"this->" galore: workaround GCC compiler bugs + this->refreshButtons(); + this->onAltCompCfgChange(); + }; + ContextMenu menu; - menu.addItem(_("Remove alternate settings"), [this] { this->removeAltCompCfg(); }, nullptr, altCompConfig.get() != nullptr); + menu.addItem(_("Remove alternate settings"), removeAltCompCfg, nullptr, altCompConfig.get() != nullptr); menu.popup(basicPanel_); } void OnAltSyncCfgContext(wxCommandEvent& event) { + auto removeAltSyncCfg = [&] + { + this->altSyncConfig.reset(); + this->refreshButtons(); + this->onAltSyncCfgChange(); + }; + ContextMenu menu; - menu.addItem(_("Remove alternate settings"), [this] { this->removeAltSyncCfg(); }, nullptr, altSyncConfig.get() != nullptr); + menu.addItem(_("Remove alternate settings"), removeAltSyncCfg, nullptr, altSyncConfig.get() != nullptr); menu.popup(basicPanel_); } void OnLocalFilterCfgContext(wxCommandEvent& event) { + auto removeLocalFilterCfg = [&] + { + this->localFilter = FilterConfig(); + this->refreshButtons(); + this->onLocalFilterCfgChange(); + }; + + std::unique_ptr<FilterConfig>& filterCfgOnClipboard = getFilterCfgOnClipboardRef(); + + auto copyFilter = [&] { filterCfgOnClipboard = make_unique<FilterConfig>(this->localFilter); }; + auto pasteFilter = [&] + { + if (filterCfgOnClipboard) + { + this->localFilter = *filterCfgOnClipboard; + this->refreshButtons(); + this->onLocalFilterCfgChange(); + } + }; + ContextMenu menu; - menu.addItem(_("Clear filter settings"), [this] { this->removeLocalFilterCfg(); }, nullptr, !isNullFilter(localFilter)); + menu.addItem(_("Clear filter settings"), removeLocalFilterCfg, nullptr, !isNullFilter(localFilter)); + menu.addSeparator(); + menu.addItem( _("Copy"), copyFilter, nullptr, !isNullFilter(localFilter)); + menu.addItem( _("Paste"), pasteFilter, nullptr, filterCfgOnClipboard.get() != nullptr); menu.popup(basicPanel_); } virtual MainConfiguration getMainConfig() const = 0; virtual wxWindow* getParentWindow() = 0; + virtual std::unique_ptr<FilterConfig>& getFilterCfgOnClipboardRef() = 0; - virtual void OnAltCompCfgChange() = 0; - virtual void OnAltSyncCfgChange() = 0; - virtual void OnLocalFilterCfgChange() {}; + virtual void onAltCompCfgChange() = 0; + virtual void onAltSyncCfgChange() = 0; + virtual void onLocalFilterCfgChange() = 0; void OnAltCompCfg(wxCommandEvent& event) { @@ -153,8 +173,7 @@ private: { altCompConfig = std::make_shared<CompConfig>(cmpCfg); refreshButtons(); - - OnAltCompCfgChange(); + onAltCompCfgChange(); } } @@ -173,8 +192,7 @@ private: { altSyncConfig = std::make_shared<SyncConfig>(syncCfg); refreshButtons(); - - OnAltSyncCfgChange(); + onAltSyncCfgChange(); } } @@ -188,8 +206,7 @@ private: { localFilter = localFiltTmp; refreshButtons(); - - OnLocalFilterCfgChange(); + onLocalFilterCfgChange(); } } @@ -203,6 +220,4 @@ private: } -#endif // FOLDERPAIR_H_INCLUDED - - +#endif //FOLDERPAIR_H_89341750847252345 diff --git a/ui/gui_generated.cpp b/ui/gui_generated.cpp index 6ce104e7..db1f03da 100644 --- a/ui/gui_generated.cpp +++ b/ui/gui_generated.cpp @@ -89,8 +89,15 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const m_menuItemManual = new wxMenuItem( m_menuHelp, wxID_HELP, wxString( _("&Content") ) + wxT('\t') + wxT("F1"), wxEmptyString, wxITEM_NORMAL ); m_menuHelp->Append( m_menuItemManual ); - m_menuItemCheckVer = new wxMenuItem( m_menuHelp, wxID_ANY, wxString( _("&Check for new version") ) , wxEmptyString, wxITEM_NORMAL ); - m_menuHelp->Append( m_menuItemCheckVer ); + m_menuCheckVersion = new wxMenu(); + m_menuItemCheckVersionNow = new wxMenuItem( m_menuCheckVersion, wxID_ANY, wxString( _("&Check now") ) , wxEmptyString, wxITEM_NORMAL ); + m_menuCheckVersion->Append( m_menuItemCheckVersionNow ); + + m_menuItemCheckVersionAuto = new wxMenuItem( m_menuCheckVersion, wxID_ANY, wxString( _("Check &automatically once a week") ) , wxEmptyString, wxITEM_CHECK ); + m_menuCheckVersion->Append( m_menuItemCheckVersionAuto ); + m_menuItemCheckVersionAuto->Check( true ); + + m_menuHelp->Append( -1, _("Check for new version"), m_menuCheckVersion ); m_menuHelp->AppendSeparator(); @@ -854,7 +861,8 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const this->Connect( m_menuItemGlobSett->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuGlobalSettings ) ); this->Connect( m_menuItem5->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuExportFileList ) ); this->Connect( m_menuItemManual->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnShowHelp ) ); - this->Connect( m_menuItemCheckVer->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuCheckVersion ) ); + this->Connect( m_menuItemCheckVersionNow->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuCheckVersion ) ); + this->Connect( m_menuItemCheckVersionAuto->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuCheckVersionAutomatically ) ); this->Connect( m_menuItemAbout->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuAbout ) ); m_buttonCompare->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnCompare ), NULL, this ); m_bpButtonCmpConfig->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnCmpSettings ), NULL, this ); @@ -921,6 +929,7 @@ MainDialogGenerated::~MainDialogGenerated() this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuExportFileList ) ); this->Disconnect( wxID_HELP, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnShowHelp ) ); this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuCheckVersion ) ); + this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuCheckVersionAutomatically ) ); this->Disconnect( wxID_ABOUT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuAbout ) ); m_buttonCompare->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnCompare ), NULL, this ); m_bpButtonCmpConfig->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnCmpSettings ), NULL, this ); @@ -1378,13 +1387,13 @@ SyncProgressDlgGenerated::SyncProgressDlgGenerated( wxWindow* parent, wxWindowID m_buttonClose->SetDefault(); m_buttonClose->Enable( false ); - bSizer28->Add( m_buttonClose, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 ); + bSizer28->Add( m_buttonClose, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); m_buttonPause = new wxButton( this, wxID_ANY, _("&Pause"), wxDefaultPosition, wxSize( -1,30 ), 0 ); - bSizer28->Add( m_buttonPause, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 ); + bSizer28->Add( m_buttonPause, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); m_buttonAbort = new wxButton( this, wxID_CANCEL, _("Cancel"), wxDefaultPosition, wxSize( -1,30 ), 0 ); - bSizer28->Add( m_buttonAbort, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + bSizer28->Add( m_buttonAbort, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxRIGHT, 5 ); bSizerRoot->Add( bSizer28, 0, wxALIGN_RIGHT, 5 ); @@ -1769,7 +1778,7 @@ SyncCfgDlgGenerated::SyncCfgDlgGenerated( wxWindow* parent, wxWindowID id, const fgSizer1->Add( m_toggleBtnAutomatic, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 ); - m_staticTextAutomatic = new wxStaticText( m_panel37, wxID_ANY, _("Identify and propagate changes on both sides using a database. Deletions, renaming and conflicts are detected automatically."), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticTextAutomatic = new wxStaticText( m_panel37, wxID_ANY, _("Identify and propagate changes on both sides. Deletions, moves and conflicts are detected automatically using a database."), wxDefaultPosition, wxDefaultSize, 0 ); m_staticTextAutomatic->Wrap( 450 ); fgSizer1->Add( m_staticTextAutomatic, 0, wxALIGN_CENTER_VERTICAL, 5 ); @@ -1910,7 +1919,7 @@ SyncCfgDlgGenerated::SyncCfgDlgGenerated( wxWindow* parent, wxWindowID id, const bSizer180->Add( m_toggleBtnRecycler, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5 ); m_toggleBtnVersioning = new wxToggleButton( m_panel37, wxID_ANY, _("Versioning"), wxDefaultPosition, wxDefaultSize, 0 ); - m_toggleBtnVersioning->SetToolTip( _("Move time-stamped files into specified folder") ); + m_toggleBtnVersioning->SetToolTip( _("Move files to user-defined folder") ); bSizer180->Add( m_toggleBtnVersioning, 0, wxALIGN_CENTER_VERTICAL, 5 ); @@ -1966,38 +1975,45 @@ SyncCfgDlgGenerated::SyncCfgDlgGenerated( wxWindow* parent, wxWindowID id, const bSizerConfig = new wxBoxSizer( wxVERTICAL ); - m_staticText90 = new wxStaticText( m_panel37, wxID_ANY, _("Configuration"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText90->Wrap( -1 ); - bSizerConfig->Add( m_staticText90, 0, wxBOTTOM, 5 ); + wxBoxSizer* bSizer18011; + bSizer18011 = new wxBoxSizer( wxHORIZONTAL ); + + m_staticTextHeaderCategory1 = new wxStaticText( m_panel37, wxID_ANY, _("Category"), wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT ); + m_staticTextHeaderCategory1->Wrap( -1 ); + bSizer18011->Add( m_staticTextHeaderCategory1, 1, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + + + bSizer18011->Add( 5, 0, 0, 0, 5 ); + + m_staticTextHeaderAction1 = new wxStaticText( m_panel37, wxID_ANY, _("Action"), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT ); + m_staticTextHeaderAction1->Wrap( -1 ); + bSizer18011->Add( m_staticTextHeaderAction1, 1, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + + + bSizerConfig->Add( bSizer18011, 0, wxEXPAND, 5 ); bSizerConfig->Add( 0, 5, 0, 0, 5 ); m_bitmapDatabase = new wxStaticBitmap( m_panel37, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 ); - bSizerConfig->Add( m_bitmapDatabase, 0, wxALIGN_CENTER_HORIZONTAL, 10 ); + bSizerConfig->Add( m_bitmapDatabase, 0, wxALIGN_CENTER_HORIZONTAL|wxTOP, 10 ); - sbSizerSyncDirections = new wxBoxSizer( wxVERTICAL ); + wxBoxSizer* sbSizerKeepWidthStable; + sbSizerKeepWidthStable = new wxBoxSizer( wxHORIZONTAL ); - wxBoxSizer* bSizer1801; - bSizer1801 = new wxBoxSizer( wxHORIZONTAL ); - m_staticTextHeaderCategory = new wxStaticText( m_panel37, wxID_ANY, _("Category"), wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT ); - m_staticTextHeaderCategory->Wrap( -1 ); - m_staticTextHeaderCategory->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, false, wxEmptyString ) ); + sbSizerKeepWidthStable->Add( 45, 0, 0, 0, 5 ); - bSizer1801->Add( m_staticTextHeaderCategory, 1, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); + sbSizerKeepWidthStable->Add( 5, 0, 0, 0, 5 ); - bSizer1801->Add( 5, 0, 0, 0, 5 ); - m_staticTextHeaderAction = new wxStaticText( m_panel37, wxID_ANY, _("Action"), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT ); - m_staticTextHeaderAction->Wrap( -1 ); - m_staticTextHeaderAction->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 90, 92, false, wxEmptyString ) ); + sbSizerKeepWidthStable->Add( 46, 0, 0, 0, 5 ); - bSizer1801->Add( m_staticTextHeaderAction, 1, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 5 ); + bSizerConfig->Add( sbSizerKeepWidthStable, 0, 0, 5 ); - sbSizerSyncDirections->Add( bSizer1801, 0, wxEXPAND, 5 ); + sbSizerSyncDirections = new wxBoxSizer( wxVERTICAL ); bSizerLeftOnly = new wxBoxSizer( wxHORIZONTAL ); @@ -2099,9 +2115,6 @@ SyncCfgDlgGenerated::SyncCfgDlgGenerated( wxWindow* parent, wxWindowID id, const bSizerConfig->Add( sbSizerSyncDirections, 0, wxEXPAND, 5 ); - bSizerConfig->Add( 0, 0, 1, wxEXPAND, 5 ); - - bSizer181->Add( bSizerConfig, 0, wxALL|wxEXPAND, 5 ); @@ -2913,8 +2926,8 @@ FilterDlgGenerated::FilterDlgGenerated( wxWindow* parent, wxWindowID id, const w wxBoxSizer* bSizer22; bSizer22 = new wxBoxSizer( wxHORIZONTAL ); - m_button9 = new wxButton( this, wxID_DEFAULT, _("&Default"), wxDefaultPosition, wxSize( -1,30 ), 0 ); - bSizer22->Add( m_button9, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 ); + m_buttonClear = new wxButton( this, wxID_DEFAULT, _("&Clear"), wxDefaultPosition, wxSize( -1,30 ), 0 ); + bSizer22->Add( m_buttonClear, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 ); bSizer22->Add( 0, 0, 1, wxALIGN_CENTER_VERTICAL, 5 ); @@ -2946,7 +2959,7 @@ FilterDlgGenerated::FilterDlgGenerated( wxWindow* parent, wxWindowID id, const w m_choiceUnitTimespan->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( FilterDlgGenerated::OnUpdateChoice ), NULL, this ); m_choiceUnitMinSize->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( FilterDlgGenerated::OnUpdateChoice ), NULL, this ); m_choiceUnitMaxSize->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( FilterDlgGenerated::OnUpdateChoice ), NULL, this ); - m_button9->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FilterDlgGenerated::OnDefault ), NULL, this ); + m_buttonClear->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FilterDlgGenerated::OnClear ), NULL, this ); m_buttonOk->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FilterDlgGenerated::OnApply ), NULL, this ); m_button17->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FilterDlgGenerated::OnCancel ), NULL, this ); } @@ -2961,7 +2974,7 @@ FilterDlgGenerated::~FilterDlgGenerated() m_choiceUnitTimespan->Disconnect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( FilterDlgGenerated::OnUpdateChoice ), NULL, this ); m_choiceUnitMinSize->Disconnect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( FilterDlgGenerated::OnUpdateChoice ), NULL, this ); m_choiceUnitMaxSize->Disconnect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( FilterDlgGenerated::OnUpdateChoice ), NULL, this ); - m_button9->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FilterDlgGenerated::OnDefault ), NULL, this ); + m_buttonClear->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FilterDlgGenerated::OnClear ), NULL, this ); m_buttonOk->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FilterDlgGenerated::OnApply ), NULL, this ); m_button17->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FilterDlgGenerated::OnCancel ), NULL, this ); diff --git a/ui/gui_generated.h b/ui/gui_generated.h index 0972f194..1f034dc2 100644 --- a/ui/gui_generated.h +++ b/ui/gui_generated.h @@ -82,7 +82,9 @@ protected: wxMenuItem* m_menuItemGlobSett; wxMenu* m_menuHelp; wxMenuItem* m_menuItemManual; - wxMenuItem* m_menuItemCheckVer; + wxMenu* m_menuCheckVersion; + wxMenuItem* m_menuItemCheckVersionNow; + wxMenuItem* m_menuItemCheckVersionAuto; wxMenuItem* m_menuItemAbout; wxBoxSizer* bSizerPanelHolder; wxPanel* m_panelTopButtons; @@ -188,6 +190,7 @@ protected: virtual void OnMenuExportFileList( wxCommandEvent& event ) { event.Skip(); } virtual void OnShowHelp( wxCommandEvent& event ) { event.Skip(); } virtual void OnMenuCheckVersion( wxCommandEvent& event ) { event.Skip(); } + virtual void OnMenuCheckVersionAutomatically( wxCommandEvent& event ) { event.Skip(); } virtual void OnMenuAbout( wxCommandEvent& event ) { event.Skip(); } virtual void OnCmpSettings( wxCommandEvent& event ) { event.Skip(); } virtual void OnCompSettingsContext( wxMouseEvent& event ) { event.Skip(); } @@ -482,11 +485,10 @@ protected: wxButton* m_buttonSelectDirVersioning; wxStaticLine* m_staticline31; wxBoxSizer* bSizerConfig; - wxStaticText* m_staticText90; + wxStaticText* m_staticTextHeaderCategory1; + wxStaticText* m_staticTextHeaderAction1; wxStaticBitmap* m_bitmapDatabase; wxBoxSizer* sbSizerSyncDirections; - wxStaticText* m_staticTextHeaderCategory; - wxStaticText* m_staticTextHeaderAction; wxBoxSizer* bSizerLeftOnly; wxStaticBitmap* m_bitmapLeftOnly; wxBitmapButton* m_bpButtonLeftOnly; @@ -730,7 +732,7 @@ protected: wxSpinCtrl* m_spinCtrlMaxSize; wxChoice* m_choiceUnitMaxSize; wxStaticLine* m_staticline16; - wxButton* m_button9; + wxButton* m_buttonClear; wxButton* m_buttonOk; wxButton* m_button17; @@ -739,14 +741,14 @@ protected: virtual void OnHelp( wxCommandEvent& event ) { event.Skip(); } virtual void OnUpdateNameFilter( wxCommandEvent& event ) { event.Skip(); } virtual void OnUpdateChoice( wxCommandEvent& event ) { event.Skip(); } - virtual void OnDefault( wxCommandEvent& event ) { event.Skip(); } + virtual void OnClear( wxCommandEvent& event ) { event.Skip(); } virtual void OnApply( wxCommandEvent& event ) { event.Skip(); } virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } public: - FilterDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Configure filter"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); + FilterDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Configure filter"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxMINIMIZE_BOX|wxRESIZE_BORDER ); ~FilterDlgGenerated(); }; diff --git a/ui/main_dlg.cpp b/ui/main_dlg.cpp index 26036817..cb05e086 100644 --- a/ui/main_dlg.cpp +++ b/ui/main_dlg.cpp @@ -13,7 +13,7 @@ #include <zen/format_unit.h> #include <zen/file_handling.h> #include <zen/serialize.h> -#include <zen/file_id.h> +//#include <zen/file_id.h> #include <zen/thread.h> #include <wx+/context_menu.h> #include <wx+/string_conv.h> @@ -54,9 +54,9 @@ namespace { struct wxClientHistoryData: public wxClientData //we need a wxClientData derived class to tell wxWidgets to take object ownership! { - wxClientHistoryData(const wxString& cfgFile, int lastUseIndex) : cfgFile_(cfgFile), lastUseIndex_(lastUseIndex) {} + wxClientHistoryData(const Zstring& cfgFile, int lastUseIndex) : cfgFile_(cfgFile), lastUseIndex_(lastUseIndex) {} - wxString cfgFile_; + Zstring cfgFile_; int lastUseIndex_; //support sorting history by last usage, the higher the index the more recent the usage }; @@ -99,7 +99,7 @@ public: case xmlAccess::MERGE_BATCH: case xmlAccess::MERGE_GUI: case xmlAccess::MERGE_GUI_BATCH: - mainDlg_.loadConfiguration(droppedFiles); + mainDlg_.loadConfiguration(toZ(droppedFiles)); return false; case xmlAccess::MERGE_OTHER: @@ -142,34 +142,13 @@ public: mainDlg(mainDialog) {} private: - virtual wxWindow* getParentWindow() - { - return &mainDlg; - } - virtual MainConfiguration getMainConfig() const { return mainDlg.getConfig().mainCfg; } - virtual void OnAltCompCfgChange() { mainDlg.applyCompareConfig(); } - virtual void OnAltSyncCfgChange() { mainDlg.applySyncConfig (); } - - virtual void removeAltCompCfg() - { - FolderPairPanelBasic<GuiPanel>::removeAltCompCfg(); - mainDlg.applyCompareConfig(); - } - - virtual void removeAltSyncCfg() - { - FolderPairPanelBasic<GuiPanel>::removeAltSyncCfg(); - mainDlg.applySyncConfig(); - } + virtual wxWindow* getParentWindow() { return &mainDlg; } + virtual std::unique_ptr<FilterConfig>& getFilterCfgOnClipboardRef() { return mainDlg.filterCfgOnClipboard; } - virtual void OnLocalFilterCfgChange() { mainDlg.applyFilterConfig(); } //re-apply filter - - virtual void removeLocalFilterCfg() - { - FolderPairPanelBasic<GuiPanel>::removeLocalFilterCfg(); - mainDlg.applyFilterConfig(); //update filter - } + virtual void onAltCompCfgChange () { mainDlg.applyCompareConfig(); } + virtual void onAltSyncCfgChange () { mainDlg.applySyncConfig(); } + virtual void onLocalFilterCfgChange() { mainDlg.applyFilterConfig(); } //re-apply filter MainDialog& mainDlg; }; @@ -193,18 +172,18 @@ public: dirNameRight.Connect(EVENT_ON_DIR_MANUAL_CORRECTION, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); } - void setValues(const wxString& leftDir, - const wxString& rightDir, + void setValues(const Zstring& leftDir, + const Zstring& rightDir, AltCompCfgPtr cmpCfg, AltSyncCfgPtr syncCfg, const FilterConfig& filter) { setConfig(cmpCfg, syncCfg, filter); - dirNameLeft.setName(leftDir); - dirNameRight.setName(rightDir); + dirNameLeft .setName(toWx(leftDir)); + dirNameRight.setName(toWx(rightDir)); } - wxString getLeftDir () const { return dirNameLeft .getName(); } - wxString getRightDir() const { return dirNameRight.getName(); } + Zstring getLeftDir () const { return toZ(dirNameLeft .getName()); } + Zstring getRightDir() const { return toZ(dirNameRight.getName()); } private: //support for drag and drop @@ -240,18 +219,18 @@ public: dirNameRight.Connect(EVENT_ON_DIR_MANUAL_CORRECTION, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog); } - void setValues(const wxString& leftDir, - const wxString& rightDir, + void setValues(const Zstring& leftDir, + const Zstring& rightDir, AltCompCfgPtr cmpCfg, AltSyncCfgPtr syncCfg, const FilterConfig& filter) { setConfig(cmpCfg, syncCfg, filter); - dirNameLeft.setName(leftDir); - dirNameRight.setName(rightDir); + dirNameLeft .setName(toWx(leftDir)); + dirNameRight.setName(toWx(rightDir)); } - wxString getLeftDir () const { return dirNameLeft .getName(); } - wxString getRightDir() const { return dirNameRight.getName(); } + Zstring getLeftDir () const { return toZ(dirNameLeft .getName()); } + Zstring getRightDir() const { return toZ(dirNameRight.getName()); } private: //support for drag and drop @@ -328,12 +307,13 @@ xmlAccess::XmlGlobalSettings retrieveGlobalCfgFromDisk() //blocks on GUI on erro XmlGlobalSettings globalCfg; try { - if (fileExists(toZ(getGlobalConfigFile()))) + if (fileExists(getGlobalConfigFile())) readConfig(globalCfg); //throw FfsXmlError //else: globalCfg already has default values } catch (const FfsXmlError& e) { + assert(false); if (e.getSeverity() != FfsXmlError::WARNING) //ignore parsing errors: should be migration problems only *cross-fingers* wxMessageBox(e.toString(), _("Error"), wxOK | wxICON_ERROR); } @@ -342,12 +322,12 @@ xmlAccess::XmlGlobalSettings retrieveGlobalCfgFromDisk() //blocks on GUI on erro } -void MainDialog::create(const std::vector<wxString>& cfgFileNames) +void MainDialog::create(const std::vector<Zstring>& cfgFileNames) { using namespace xmlAccess; const XmlGlobalSettings globalSettings = retrieveGlobalCfgFromDisk(); - std::vector<wxString> filenames; + std::vector<Zstring> filenames; if (!cfgFileNames.empty()) //1. this one has priority filenames = cfgFileNames; else //FFS default startup: use last used selection @@ -359,11 +339,9 @@ void MainDialog::create(const std::vector<wxString>& cfgFileNames) RunUntilFirstHit<NullType> findFirstMissing; - std::for_each(filenames.begin(), filenames.end(), - [&](const wxString& filename) + std::for_each(filenames.begin(), filenames.end(), [&](const Zstring& filename) { - const Zstring filenameFmt = toZ(filename); //convert to Zstring first: we don't want to pass wxString by value and risk MT issues! - findFirstMissing.addJob([=] { return filenameFmt.empty() /*ever empty??*/ || !fileExists(filenameFmt) ? zen::make_unique<NullType>() : nullptr; }); + findFirstMissing.addJob([=] { return filename.empty() /*ever empty??*/ || !fileExists(filename) ? zen::make_unique<NullType>() : nullptr; }); }); //potentially slow network access: give all checks 500ms to finish const bool allFilesExist = findFirstMissing.timedWait(boost::posix_time::milliseconds(500)) && //false: time elapsed @@ -374,7 +352,7 @@ void MainDialog::create(const std::vector<wxString>& cfgFileNames) if (filenames.empty()) { - if (zen::fileExists(zen::toZ(lastRunConfigName()))) //3. try to load auto-save config + if (zen::fileExists(lastRunConfigName())) //3. try to load auto-save config filenames.push_back(lastRunConfigName()); } } @@ -382,10 +360,19 @@ void MainDialog::create(const std::vector<wxString>& cfgFileNames) XmlGuiConfig guiCfg; //structure to receive gui settings with default values bool loadCfgSuccess = false; - if (!filenames.empty()) + if (filenames.empty()) + { + //add default exclusion filter: this is only ever relevant when creating new configurations! + //a default XmlGuiConfig does not need these user-specific exclusions! + Zstring& excludeFilter = guiCfg.mainCfg.globalFilter.excludeFilter; + if (!excludeFilter.empty() && !endsWith(excludeFilter, Zstr("\n"))) + excludeFilter += Zstr("\n"); + excludeFilter += globalSettings.gui.defaultExclusionFilter; + } + else try { - readAnyConfig(toZ(filenames), guiCfg); //throw FfsXmlError + readAnyConfig(filenames, guiCfg); //throw FfsXmlError loadCfgSuccess = true; } catch (const FfsXmlError& error) @@ -396,6 +383,7 @@ void MainDialog::create(const std::vector<wxString>& cfgFileNames) else wxMessageBox(error.toString(), _("Error"), wxOK | wxICON_ERROR); } + const bool startComparisonImmediately = !cfgFileNames.empty() && loadCfgSuccess; //------------------------------------------------------------------------------------------ @@ -407,12 +395,12 @@ void MainDialog::create(const std::vector<wxString>& cfgFileNames) void MainDialog::create(const xmlAccess::XmlGuiConfig& guiCfg, bool startComparison) { - create_impl(guiCfg, std::vector<wxString>(), retrieveGlobalCfgFromDisk(), startComparison); + create_impl(guiCfg, std::vector<Zstring>(), retrieveGlobalCfgFromDisk(), startComparison); } void MainDialog::create(const xmlAccess::XmlGuiConfig& guiCfg, - const std::vector<wxString>& referenceFiles, + const std::vector<Zstring>& referenceFiles, const xmlAccess::XmlGlobalSettings& globalSettings, bool startComparison) { @@ -421,7 +409,7 @@ void MainDialog::create(const xmlAccess::XmlGuiConfig& guiCfg, void MainDialog::create_impl(const xmlAccess::XmlGuiConfig& guiCfg, - const std::vector<wxString>& referenceFiles, + const std::vector<Zstring>& referenceFiles, const xmlAccess::XmlGlobalSettings& globalSettings, bool startComparison) { @@ -442,7 +430,7 @@ void MainDialog::create_impl(const xmlAccess::XmlGuiConfig& guiCfg, MainDialog::MainDialog(const xmlAccess::XmlGuiConfig& guiCfg, - const std::vector<wxString>& referenceFiles, + const std::vector<Zstring>& referenceFiles, const xmlAccess::XmlGlobalSettings& globalSettings, bool startComparison) : MainDialogGenerated(nullptr), @@ -479,34 +467,34 @@ MainDialog::MainDialog(const xmlAccess::XmlGuiConfig& guiCfg, //caption required for all panes that can be manipulated by the users => used by context menu auiMgr.AddPane(m_panelTopButtons, - wxAuiPaneInfo().Name(wxT("Panel1")).Layer(4).Top().Caption(_("Main bar")).CaptionVisible(false).PaneBorder(false).Gripper().MinSize(-1, m_panelTopButtons->GetSize().GetHeight())); + wxAuiPaneInfo().Name(L"Panel1").Layer(4).Top().Caption(_("Main bar")).CaptionVisible(false).PaneBorder(false).Gripper().MinSize(-1, m_panelTopButtons->GetSize().GetHeight())); //note: min height is calculated incorrectly by wxAuiManager if panes with and without caption are in the same row => use smaller min-size compareStatus = make_unique<CompareProgressDialog>(*this); //integrate the compare status panel (in hidden state) auiMgr.AddPane(compareStatus->getAsWindow(), - wxAuiPaneInfo().Name(wxT("Panel9")).Layer(4).Top().Row(1).CaptionVisible(false).PaneBorder(false).Hide()); //name "CmpStatus" used by context menu + wxAuiPaneInfo().Name(L"Panel9").Layer(4).Top().Row(1).CaptionVisible(false).PaneBorder(false).Hide()); //name "CmpStatus" used by context menu auiMgr.AddPane(m_panelDirectoryPairs, - wxAuiPaneInfo().Name(wxT("Panel2")).Layer(2).Top().Row(2).Caption(_("Folder pairs")).CaptionVisible(false).PaneBorder(false).Gripper()); + wxAuiPaneInfo().Name(L"Panel2").Layer(2).Top().Row(2).Caption(_("Folder pairs")).CaptionVisible(false).PaneBorder(false).Gripper()); auiMgr.AddPane(m_panelCenter, - wxAuiPaneInfo().Name(wxT("Panel3")).CenterPane().PaneBorder(false)); + wxAuiPaneInfo().Name(L"Panel3").CenterPane().PaneBorder(false)); auiMgr.AddPane(m_gridNavi, - wxAuiPaneInfo().Name(L"Panel10").Left().Layer(3).Caption(_("Overview")).MinSize(230, m_gridNavi->GetSize().GetHeight())); //MinSize(): just default size, see comment below + wxAuiPaneInfo().Name(L"Panel10").Left().Layer(3).Caption(_("Overview")).MinSize(300, m_gridNavi->GetSize().GetHeight())); //MinSize(): just default size, see comment below auiMgr.AddPane(m_panelConfig, - wxAuiPaneInfo().Name(wxT("Panel4")).Layer(4).Bottom().Row(1).Position(0).Caption(_("Configuration")).MinSize(m_listBoxHistory->GetSize().GetWidth(), m_panelConfig->GetSize().GetHeight())); + wxAuiPaneInfo().Name(L"Panel4").Layer(4).Bottom().Row(1).Position(0).Caption(_("Configuration")).MinSize(m_listBoxHistory->GetSize().GetWidth(), m_panelConfig->GetSize().GetHeight())); auiMgr.AddPane(m_panelFilter, - wxAuiPaneInfo().Name(wxT("Panel5")).Layer(4).Bottom().Row(1).Position(1).Caption(_("Filter files")).MinSize(m_bpButtonFilter->GetSize().GetWidth(), m_panelFilter->GetSize().GetHeight())); + wxAuiPaneInfo().Name(L"Panel5").Layer(4).Bottom().Row(1).Position(1).Caption(_("Filter files")).MinSize(m_bpButtonFilter->GetSize().GetWidth(), m_panelFilter->GetSize().GetHeight())); auiMgr.AddPane(m_panelViewFilter, - wxAuiPaneInfo().Name(wxT("Panel6")).Layer(4).Bottom().Row(1).Position(2).Caption(_("Select view")).MinSize(m_bpButtonShowDoNothing->GetSize().GetWidth(), m_panelViewFilter->GetSize().GetHeight())); + wxAuiPaneInfo().Name(L"Panel6").Layer(4).Bottom().Row(1).Position(2).Caption(_("Select view")).MinSize(m_bpButtonShowDoNothing->GetSize().GetWidth(), m_panelViewFilter->GetSize().GetHeight())); auiMgr.AddPane(m_panelStatistics, - wxAuiPaneInfo().Name(wxT("Panel7")).Layer(4).Bottom().Row(1).Position(3).Caption(_("Statistics")).MinSize(m_bitmapData->GetSize().GetWidth() + m_staticTextData->GetSize().GetWidth(), m_panelStatistics->GetSize().GetHeight())); + wxAuiPaneInfo().Name(L"Panel7").Layer(4).Bottom().Row(1).Position(3).Caption(_("Statistics")).MinSize(m_bitmapData->GetSize().GetWidth() + m_staticTextData->GetSize().GetWidth(), m_panelStatistics->GetSize().GetHeight())); auiMgr.Update(); @@ -632,7 +620,16 @@ MainDialog::MainDialog(const xmlAccess::XmlGuiConfig& guiCfg, setMenuItemImage(m_menuItemAbout, getResourceImage(L"aboutSmall")); if (!manualProgramUpdateRequired()) - m_menuItemCheckVer->Enable(false); + { + m_menuItemCheckVersionNow ->Enable(false); + m_menuItemCheckVersionAuto->Enable(false); + + //wxFormbuilder doesn't give us a wxMenuItem for m_menuCheckVersion, so we need this abomination: + wxMenuItemList& items = m_menuHelp->GetMenuItems(); + for (auto it = items.begin(); it != items.end(); ++it) + if ((*it)->GetSubMenu() == m_menuCheckVersion) + (*it)->Enable(false); + } //create language selection menu std::for_each(zen::ExistingTranslations::get().begin(), ExistingTranslations::get().end(), @@ -664,7 +661,7 @@ MainDialog::MainDialog(const xmlAccess::XmlGuiConfig& guiCfg, setupFileDrop(*m_gridNavi); m_gridNavi->Connect(EVENT_DROP_FILE, FileDropEventHandler(MainDialog::onNaviPanelFilesDropped), nullptr, this); - Connect(wxEVT_IDLE, wxEventHandler(MainDialog::OnIdleEvent), nullptr, this); + timerForAsyncTasks.Connect(wxEVT_TIMER, wxEventHandler(MainDialog::onProcessAsyncTasks), nullptr, this); //Connect(wxEVT_SIZE, wxSizeEventHandler(MainDialog::OnResize), nullptr, this); //Connect(wxEVT_MOVE, wxSizeEventHandler(MainDialog::OnResize), nullptr, this); @@ -760,7 +757,7 @@ MainDialog::~MainDialog() try //save "LastRun.ffs_gui" { - xmlAccess::writeConfig(getConfig(), toZ(lastRunConfigName())); //throw FfsXmlError + xmlAccess::writeConfig(getConfig(), lastRunConfigName()); //throw FfsXmlError } //don't annoy users on read-only drives: it's enough to show a single error message when saving global config catch (const xmlAccess::FfsXmlError&) {} @@ -781,7 +778,7 @@ void MainDialog::onQueryEndSession() try { xmlAccess::writeConfig(getGlobalCfgBeforeExit()); } catch (const xmlAccess::FfsXmlError&) {} //we try our best do to something useful in this extreme situation - no reason to notify or even log errors here! - try { xmlAccess::writeConfig(getConfig(), toZ(lastRunConfigName())); } + try { xmlAccess::writeConfig(getConfig(), lastRunConfigName()); } catch (const xmlAccess::FfsXmlError&) {} } @@ -818,11 +815,12 @@ void MainDialog::setGlobalCfgOnInit(const xmlAccess::XmlGlobalSettings& globalSe //-------------------------------------------------------------------------------- //load list of last used configuration files - std::vector<wxString> cfgFileNames = globalSettings.gui.cfgFileHistory; + std::vector<Zstring> cfgFileNames = globalSettings.gui.cfgFileHistory; std::reverse(cfgFileNames.begin(), cfgFileNames.end()); //list is stored with last used files first in xml, however addFileToCfgHistory() needs them last!!! cfgFileNames.push_back(lastRunConfigName()); //make sure <Last session> is always part of history list (if existing) addFileToCfgHistory(cfgFileNames); + removeObsoleteCfgHistoryItems(cfgFileNames); //remove non-existent items (we need this only on startup) //-------------------------------------------------------------------------------- //load list of last used folders @@ -849,6 +847,8 @@ void MainDialog::setGlobalCfgOnInit(const xmlAccess::XmlGlobalSettings& globalSe //if MainDialog::onQueryEndSession() is called while comparison is active, this panel is saved and restored as "visible" auiMgr.GetPane(compareStatus->getAsWindow()).Hide(); + m_menuItemCheckVersionAuto->Check(globalCfg.gui.lastUpdateCheck != -1); + auiMgr.Update(); } @@ -876,14 +876,14 @@ xmlAccess::XmlGlobalSettings MainDialog::getGlobalCfgBeforeExit() //-------------------------------------------------------------------------------- //write list of last used configuration files - std::map<int, wxString> historyDetail; //(cfg-file/last use index) + std::map<int, Zstring> historyDetail; //(cfg-file/last use index) for (unsigned int i = 0; i < m_listBoxHistory->GetCount(); ++i) if (auto clientString = dynamic_cast<const wxClientHistoryData*>(m_listBoxHistory->GetClientObject(i))) historyDetail.insert(std::make_pair(clientString->lastUseIndex_, clientString->cfgFile_)); //sort by last use; put most recent items *first* (looks better in xml than the reverse) - std::vector<wxString> history; - std::transform(historyDetail.rbegin(), historyDetail.rend(), std::back_inserter(history), [](const std::pair<int, wxString>& item) { return item.second; }); + std::vector<Zstring> history; + std::transform(historyDetail.rbegin(), historyDetail.rend(), std::back_inserter(history), [](const std::pair<int, Zstring>& item) { return item.second; }); if (history.size() > globalSettings.gui.cfgFileHistMax) //erase oldest elements history.resize(globalSettings.gui.cfgFileHistMax); @@ -952,7 +952,7 @@ namespace typedef Zbase<wchar_t> zxString; //guaranteed exponential growth } -void MainDialog::copySelectionToClipboard() +void MainDialog::copySelectionToClipboard(const std::vector<const Grid*>& gridRefs) { try { @@ -983,8 +983,8 @@ void MainDialog::copySelectionToClipboard() } }; - addSelection(*m_gridMainL); - addSelection(*m_gridMainR); + for (auto it = gridRefs.begin(); it != gridRefs.end(); ++it) + addSelection(**it); //finally write to clipboard if (!clipboardString.empty()) @@ -996,7 +996,7 @@ void MainDialog::copySelectionToClipboard() } catch (const std::bad_alloc& e) { - wxMessageBox(_("Out of memory!") + L" " + utfCvrtTo<std::wstring>(e.what()), _("Error"), wxOK | wxICON_ERROR); + wxMessageBox(_("Out of memory!") + L" " + utfCvrtTo<std::wstring>(e.what()), _("Error"), wxOK | wxICON_ERROR, this); } } @@ -1023,9 +1023,9 @@ std::vector<FileSystemObject*> MainDialog::getGridSelection(bool fromLeft, bool std::vector<FileSystemObject*> MainDialog::getTreeSelection() const { - const std::vector<size_t>& sel = m_gridNavi->getSelectedRows(); - std::vector<FileSystemObject*> output; + + const std::vector<size_t>& sel = m_gridNavi->getSelectedRows(); std::for_each(sel.begin(), sel.end(), [&](size_t row) { @@ -1042,12 +1042,7 @@ std::vector<FileSystemObject*> MainDialog::getTreeSelection() const else if (auto dir = dynamic_cast<const TreeView::DirNode*>(node.get())) output.push_back(&(dir->dirObj_)); else if (auto file = dynamic_cast<const TreeView::FilesNode*>(node.get())) - { - //does a "little more" than what is shown on main grid: we return ALL files - HierarchyObject& parent = file->firstFile_.parent(); - std::transform(parent.refSubFiles().begin(), parent.refSubFiles().end(), std::back_inserter(output), [](FileSystemObject& fsObj) { return &fsObj; }); - std::transform(parent.refSubLinks().begin(), parent.refSubLinks().end(), std::back_inserter(output), [](FileSystemObject& fsObj) { return &fsObj; }); - } + output.insert(output.end(), file->filesAndLinks_.begin(), file->filesAndLinks_.end()); } }); return output; @@ -1266,8 +1261,8 @@ void MainDialog::openExternalApplication(const wxString& commandline, const std: Zstring fallbackDir; if (selectionTmp.empty()) fallbackDir = leftSide ? - getFormattedDirectoryName(toZ(firstFolderPair->getLeftDir())) : - getFormattedDirectoryName(toZ(firstFolderPair->getRightDir())); + getFormattedDirectoryName(firstFolderPair->getLeftDir()) : + getFormattedDirectoryName(firstFolderPair->getRightDir()); else fallbackDir = leftSide ? @@ -1350,12 +1345,11 @@ void MainDialog::setStatusBarFileStatistics(size_t filesOnLeftView, setText(*m_staticTextStatusRightFiles, replaceCpy(_P("1 file", "%x files", filesOnRightView), L"%x", toGuiString(filesOnRightView), false)); setText(*m_staticTextStatusRightBytes, filesizeToShortString(to<Int64>(filesizeRightView))); - //fill middle text (considering flashStatusInformation()) - if (!oldStatusMsg) + if (oldStatusMsgs.empty()) setText(*m_staticTextStatusMiddle, statusMiddleNew); else - *oldStatusMsg = statusMiddleNew; + oldStatusMsgs.front() = statusMiddleNew; m_panelStatusBar->Layout(); } @@ -1379,36 +1373,42 @@ void MainDialog::setStatusBarFullText(const wxString& msg) void MainDialog::flashStatusInformation(const wxString& text) { - if (!oldStatusMsg) - oldStatusMsg = make_unique<wxString>(m_staticTextStatusMiddle->GetLabel()); + oldStatusMsgs.push_back(m_staticTextStatusMiddle->GetLabel()); - lastStatusChange = wxGetLocalTimeMillis(); m_staticTextStatusMiddle->SetLabel(text); m_staticTextStatusMiddle->SetForegroundColour(wxColour(31, 57, 226)); //highlight color: blue m_panelStatusBar->Layout(); - //if (needLayoutUpdate) auiMgr.Update(); -> not needed here, this is called anyway in updateGui() + + asyncTasks.add2([] { boost::this_thread::sleep(boost::posix_time::millisec(2500)); }, + [this] { this->restoreStatusInformation(); }); + startProcessingAsyncTasks(); } -void MainDialog::OnIdleEvent(wxEvent& event) +void MainDialog::restoreStatusInformation() { - //small routine to restore status information after some time - if (oldStatusMsg) //check if there is some work to do + if (!oldStatusMsgs.empty()) { - wxMilliClock_t currentTime = wxGetLocalTimeMillis(); - if (numeric::dist(currentTime, lastStatusChange) > 2500) //restore after two seconds - { - lastStatusChange = currentTime; + wxString oldMsg = oldStatusMsgs.back(); + oldStatusMsgs.pop_back(); - m_staticTextStatusMiddle->SetLabel(*oldStatusMsg); + if (oldStatusMsgs.empty()) //restore original status text + { + m_staticTextStatusMiddle->SetLabel(oldMsg); m_staticTextStatusMiddle->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //reset color m_panelStatusBar->Layout(); - oldStatusMsg.reset(); } } +} - event.Skip(); + +void MainDialog::onProcessAsyncTasks(wxEvent& event) +{ + //schedule and run long-running tasks asynchronously + asyncTasks.evalResults(); //process results on GUI queue + if (asyncTasks.empty()) + timerForAsyncTasks.Stop(); } @@ -1569,7 +1569,17 @@ void MainDialog::onTreeButtonEvent(wxKeyEvent& event) } if (event.ControlDown()) - ; + switch (keyCode) + { + case 'C': + case WXK_INSERT: //CTRL + C || CTRL + INS + { + std::vector<const Grid*> gridRefs; + gridRefs.push_back(m_gridNavi); + copySelectionToClipboard(gridRefs); + } + return; + } else if (event.AltDown()) switch (keyCode) { @@ -1646,8 +1656,13 @@ void MainDialog::onGridButtonEvent(wxKeyEvent& event, Grid& grid, bool leftSide) { case 'C': case WXK_INSERT: //CTRL + C || CTRL + INS - copySelectionToClipboard(); - return; // -> swallow event! don't allow default grid commands! + { + std::vector<const Grid*> gridRefs; + gridRefs.push_back(m_gridMainL); + gridRefs.push_back(m_gridMainR); + copySelectionToClipboard(gridRefs); + } + return; // -> swallow event! don't allow default grid commands! } else if (event.AltDown()) @@ -1813,7 +1828,11 @@ void MainDialog::onNaviSelection(GridRangeSelectEvent& event) leadRow = gridDataView->findRowFirstChild(&(dir->dirObj_)); } else if (const TreeView::FilesNode* files = dynamic_cast<const TreeView::FilesNode*>(node.get())) - leadRow = gridDataView->findRowDirect(files->firstFile_.getId()); + { + assert(!files->filesAndLinks_.empty()); + if (!files->filesAndLinks_.empty()) + leadRow = gridDataView->findRowDirect(files->filesAndLinks_[0]->getId()); + } } if (leadRow >= 0) @@ -1829,8 +1848,8 @@ void MainDialog::onNaviSelection(GridRangeSelectEvent& event) } //get selection on navigation tree and set corresponding markers on main grid - std::vector<const HierarchyObject*> markedFiles; //mark files/symlinks directly within a container - std::vector<const HierarchyObject*> markedContainer; //mark full container including child-objects + hash_set<const FileSystemObject*> markedFilesAndLinks; //mark files/symlinks directly + hash_set<const HierarchyObject*> markedContainer; //mark full container including child-objects const std::vector<size_t>& selection = m_gridNavi->getSelectedRows(); std::for_each(selection.begin(), selection.end(), @@ -1839,15 +1858,15 @@ void MainDialog::onNaviSelection(GridRangeSelectEvent& event) if (std::unique_ptr<TreeView::Node> node = treeDataView->getLine(row)) { if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get())) - markedContainer.push_back(&(root->baseMap_)); + markedContainer.insert(&(root->baseMap_)); else if (const TreeView::DirNode* dir = dynamic_cast<const TreeView::DirNode*>(node.get())) - markedContainer.push_back(&(dir->dirObj_)); + markedContainer.insert(&(dir->dirObj_)); else if (const TreeView::FilesNode* files = dynamic_cast<const TreeView::FilesNode*>(node.get())) - markedFiles.push_back(&(files->firstFile_.parent())); + markedFilesAndLinks.insert(files->filesAndLinks_.begin(), files->filesAndLinks_.end()); } }); - gridview::setNavigationMarker(*m_gridMainL, std::move(markedFiles), std::move(markedContainer)); + gridview::setNavigationMarker(*m_gridMainL, std::move(markedFilesAndLinks), std::move(markedContainer)); event.Skip(); } @@ -2068,11 +2087,11 @@ void MainDialog::excludeExtension(const Zstring& extension) //add to filter config Zstring& excludeFilter = currentCfg.mainCfg.globalFilter.excludeFilter; - if (!excludeFilter.empty() && !endsWith(excludeFilter, Zstr(";"))) + if (!excludeFilter.empty() && !endsWith(excludeFilter, Zstr(";")) && !endsWith(excludeFilter, Zstr("\n"))) excludeFilter += Zstr("\n"); excludeFilter += newExclude + Zstr(";"); //';' is appended to 'mark' that next exclude extension entry won't write to new line - updateFilterButtons(); + updateGlobalFilterButton(); //do not fully apply filter, just exclude new items std::for_each(begin(folderCmp), end(folderCmp), [&](BaseDirMapping& baseMap) { addHardFiltering(baseMap, newExclude); }); @@ -2093,7 +2112,7 @@ void MainDialog::excludeShortname(const FileSystemObject& fsObj) excludeFilter += Zstr("\n"); excludeFilter += newExclude; - updateFilterButtons(); + updateGlobalFilterButton(); //do not fully apply filter, just exclude new items std::for_each(begin(folderCmp), end(folderCmp), [&](BaseDirMapping& baseMap) { addHardFiltering(baseMap, newExclude); }); @@ -2127,7 +2146,7 @@ void MainDialog::excludeItems(const std::vector<FileSystemObject*>& selection) excludeFilter += Zstr("\n"); excludeFilter += newExclude; - updateFilterButtons(); + updateGlobalFilterButton(); //do not fully apply filter, just exclude new items std::for_each(begin(folderCmp), end(folderCmp), [&](BaseDirMapping& baseMap) { addHardFiltering(baseMap, newExclude); }); @@ -2305,9 +2324,9 @@ void MainDialog::OnSyncSettingsContext(wxMouseEvent& event) const auto currentVar = getConfig().mainCfg.syncCfg.directionCfg.var; menu.addRadio(_("<- Two way ->"), [&] { setVariant(DirectionConfig::AUTOMATIC); }, currentVar == DirectionConfig::AUTOMATIC); - menu.addRadio(_("Mirror ->>") , [&] { setVariant(DirectionConfig::MIRROR); }, currentVar == DirectionConfig::MIRROR); - menu.addRadio(_("Update ->") , [&] { setVariant(DirectionConfig::UPDATE); }, currentVar == DirectionConfig::UPDATE); - menu.addRadio(_("Custom") , [&] { setVariant(DirectionConfig::CUSTOM); }, currentVar == DirectionConfig::CUSTOM); + menu.addRadio(_("Mirror ->>") , [&] { setVariant(DirectionConfig::MIRROR); }, currentVar == DirectionConfig::MIRROR); + menu.addRadio(_("Update ->") , [&] { setVariant(DirectionConfig::UPDATE); }, currentVar == DirectionConfig::UPDATE); + menu.addRadio(_("Custom") , [&] { setVariant(DirectionConfig::CUSTOM); }, currentVar == DirectionConfig::CUSTOM); menu.popup(*this); } @@ -2315,7 +2334,7 @@ void MainDialog::OnSyncSettingsContext(wxMouseEvent& event) void MainDialog::onNaviPanelFilesDropped(FileDropEvent& event) { - loadConfiguration(event.getFiles()); + loadConfiguration(toZ(event.getFiles())); event.Skip(); } @@ -2335,31 +2354,17 @@ void MainDialog::onDirManualCorrection(wxCommandEvent& event) } -wxString getFormattedHistoryElement(const wxString& filename) +wxString getFormattedHistoryElement(const Zstring& filename) { - wxString output = afterLast(filename, utfCvrtTo<wxString>(FILE_NAME_SEPARATOR)); - if (endsWith(output, L".ffs_gui")) - output = beforeLast(output, L'.'); - return output; + Zstring output = afterLast(filename, FILE_NAME_SEPARATOR); + if (endsWith(output, Zstr(".ffs_gui"))) + output = beforeLast(output, Zstr('.')); + return utfCvrtTo<wxString>(output); } -void MainDialog::addFileToCfgHistory(const std::vector<wxString>& filenames) +void MainDialog::addFileToCfgHistory(const std::vector<Zstring>& filenames) { - //check existence of all config files in parallel! - std::list<boost::unique_future<bool>> fileEx; - std::for_each(filenames.begin(), filenames.end(), - [&](const wxString& filename) - { - const Zstring file = toZ(filename); //convert to Zstring first: we don't want to pass wxString by value and risk MT issues! - fileEx.push_back(zen::async2<bool>([=]() { return zen::fileExists(file); })); - }); - - //potentially slow network access: give all checks 500ms to finish - wait_for_all_timed(fileEx.begin(), fileEx.end(), boost::posix_time::milliseconds(500)); - - //------------------------------------------------------------------------------------------ - //determine highest "last use" index number of m_listBoxHistory int lastUseIndexMax = 0; for (int i = 0; i < static_cast<int>(m_listBoxHistory->GetCount()); ++i) @@ -2369,39 +2374,36 @@ void MainDialog::addFileToCfgHistory(const std::vector<wxString>& filenames) std::deque<bool> selections(m_listBoxHistory->GetCount()); //items to select after update of history list - auto futIter = fileEx.begin(); - for (auto it = filenames.begin(); it != filenames.end(); ++it, ++futIter) - if (!futIter->is_ready() || futIter->get()) //only existing files should be included in the list (and also those with no result yet) - { - const wxString& filename = *it; + for (auto it = filenames.begin(); it != filenames.end(); ++it) + { + const Zstring& filename = *it; - warn_static("perf!!!!? samePhysicalFile : andere setllen?") + //Do we need to additionally check for aliases of the same physical files here? (and aliases for lastRunConfigName?) - auto findItem = [&]() -> int - { - const int itemCount = static_cast<int>(m_listBoxHistory->GetCount()); - for (int i = 0; i < itemCount; ++i) - if (auto histData = dynamic_cast<const wxClientHistoryData*>(m_listBoxHistory->GetClientObject(i))) - if (samePhysicalFile(toZ(filename), toZ(histData->cfgFile_))) - return i; - return -1; - }; - - const int itemPos = findItem(); - if (itemPos >= 0) //update - { - if (auto histData = dynamic_cast<wxClientHistoryData*>(m_listBoxHistory->GetClientObject(itemPos))) - histData->lastUseIndex_ = ++lastUseIndexMax; - selections[itemPos] = true; - } - else //insert - { - const wxString label = samePhysicalFile(toZ(lastRunConfigName()), toZ(filename)) ? //give default config file a different name - _("<Last session>") : getFormattedHistoryElement(filename); - const int newPos = m_listBoxHistory->Append(label, new wxClientHistoryData(filename, ++lastUseIndexMax)); //*insert* into sorted list - selections.insert(selections.begin() + newPos, true); - } + const int itemPos = [&]() -> int + { + const int itemCount = static_cast<int>(m_listBoxHistory->GetCount()); + for (int i = 0; i < itemCount; ++i) + if (auto histData = dynamic_cast<const wxClientHistoryData*>(m_listBoxHistory->GetClientObject(i))) + if (EqualFilename()(filename, histData->cfgFile_)) + return i; + return -1; + }(); + + if (itemPos >= 0) //update + { + if (auto histData = dynamic_cast<wxClientHistoryData*>(m_listBoxHistory->GetClientObject(itemPos))) + histData->lastUseIndex_ = ++lastUseIndexMax; + selections[itemPos] = true; } + else //insert + { + const wxString label = EqualFilename()(filename, lastRunConfigName()) ? //give default config file a different name + _("<Last session>") : getFormattedHistoryElement(filename); + const int newPos = m_listBoxHistory->Append(label, new wxClientHistoryData(filename, ++lastUseIndexMax)); //*insert* into sorted list + selections.insert(selections.begin() + newPos, true); + } + } assert(selections.size() == m_listBoxHistory->GetCount()); @@ -2418,9 +2420,61 @@ void MainDialog::addFileToCfgHistory(const std::vector<wxString>& filenames) } +void MainDialog::removeObsoleteCfgHistoryItems(const std::vector<Zstring>& filenames) +{ + //don't use wxString: NOT thread-safe! (e.g. non-atomic ref-count) + + auto getMissingFilesAsync = [filenames]() -> std::vector<Zstring> + { + //boost::this_thread::sleep(boost::posix_time::millisec(5000)); + + //check existence of all config files in parallel! + std::list<boost::unique_future<bool>> fileEx; + + for (auto it = filenames.begin(); it != filenames.end(); ++it) //avoid VC11 compiler issues with std::for_each + { + const Zstring filename = *it; //don't reference iterator in lambda! + fileEx.push_back(zen::async2<bool>([=] { return fileExists(filename); })); + } + + //potentially slow network access => limit maximum wait time! + wait_for_all_timed(fileEx.begin(), fileEx.end(), boost::posix_time::milliseconds(1000)); + + std::vector<Zstring> missingFiles; + + auto itFut = fileEx.begin(); + for (auto it = filenames.begin(); it != filenames.end(); ++it, ++itFut) + if (itFut->is_ready() && !itFut->get()) //remove only files that are confirmed to be non-existent + missingFiles.push_back(*it); + + return missingFiles; + }; + + asyncTasks.add(getMissingFilesAsync, + [this](const std::vector<Zstring>& files) { removeCfgHistoryItems(files); }); + startProcessingAsyncTasks(); +} + + +void MainDialog::removeCfgHistoryItems(const std::vector<Zstring>& filenames) +{ + std::for_each(filenames.begin(), filenames.end(), [&](const Zstring& filename) + { + const int histSize = m_listBoxHistory->GetCount(); + for (int i = 0; i < histSize; ++i) + if (auto histData = dynamic_cast<wxClientHistoryData*>(m_listBoxHistory->GetClientObject(i))) + if (EqualFilename()(filename, histData->cfgFile_)) + { + m_listBoxHistory->Delete(i); + break; + } + }); +} + + void MainDialog::updateUnsavedCfgStatus() { - const wxString activeCfgFilename = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : wxString(); + const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : Zstring(); const bool haveUnsavedCfg = lastConfigurationSaved != getConfig(); @@ -2446,15 +2500,15 @@ void MainDialog::updateUnsavedCfgStatus() title += L'*'; if (!activeCfgFilename.empty()) - title += activeCfgFilename; + title += toWx(activeCfgFilename); else if (activeConfigFiles.size() > 1) { #ifdef _MSC_VER #pragma warning(disable:4428) // VC wrongly issues warning C4428: universal-character-name encountered in source #endif const wchar_t* EM_DASH = L" \u2014 "; - title += xmlAccess::extractJobName(toZ(activeConfigFiles[0])); - std::for_each(activeConfigFiles.begin() + 1, activeConfigFiles.end(), [&](const wxString& filename) { title += EM_DASH + xmlAccess::extractJobName(toZ(filename)); }); + title += xmlAccess::extractJobName(activeConfigFiles[0]); + std::for_each(activeConfigFiles.begin() + 1, activeConfigFiles.end(), [&](const Zstring& filename) { title += EM_DASH + xmlAccess::extractJobName(filename); }); } else title += L"FreeFileSync - " + _("Folder Comparison and Synchronization"); @@ -2465,7 +2519,7 @@ void MainDialog::updateUnsavedCfgStatus() void MainDialog::OnConfigSave(wxCommandEvent& event) { - const wxString activeCfgFilename = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : wxString(); + const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : Zstring(); //if we work on a single named configuration document: save directly if changed //else: always show file dialog @@ -2473,7 +2527,7 @@ void MainDialog::OnConfigSave(wxCommandEvent& event) { using namespace xmlAccess; - switch (getXmlType(utfCvrtTo<Zstring>(activeCfgFilename))) //throw() + switch (getXmlType(activeCfgFilename)) //throw() { case XML_TYPE_GUI: trySaveConfig(&activeCfgFilename); @@ -2504,18 +2558,18 @@ void MainDialog::OnSaveAsBatchJob(wxCommandEvent& event) } -bool MainDialog::trySaveConfig(const wxString* fileNameGui) //return true if saved successfully +bool MainDialog::trySaveConfig(const Zstring* fileNameGui) //return true if saved successfully { - wxString targetFilename; + Zstring targetFilename; if (fileNameGui) { targetFilename = *fileNameGui; - assert(endsWith(targetFilename, L".ffs_gui")); + assert(endsWith(targetFilename, Zstr(".ffs_gui"))); } else { - wxString defaultFileName = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : L"SyncSettings.ffs_gui"; + wxString defaultFileName = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? toWx(activeConfigFiles[0]) : L"SyncSettings.ffs_gui"; //attention: activeConfigFiles may be an imported *.ffs_batch file! We don't want to overwrite it with a GUI config! if (endsWith(defaultFileName, L".ffs_batch")) replace(defaultFileName, L".ffs_batch", L".ffs_gui", false); @@ -2528,14 +2582,14 @@ bool MainDialog::trySaveConfig(const wxString* fileNameGui) //return true if sav wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (filePicker.ShowModal() != wxID_OK) return false; - targetFilename = filePicker.GetPath(); + targetFilename = toZ(filePicker.GetPath()); } const xmlAccess::XmlGuiConfig guiCfg = getConfig(); try { - xmlAccess::writeConfig(guiCfg, toZ(targetFilename)); //throw FfsXmlError + xmlAccess::writeConfig(guiCfg, targetFilename); //throw FfsXmlError setLastUsedConfig(targetFilename, guiCfg); flashStatusInformation(_("Configuration saved!")); @@ -2549,25 +2603,25 @@ bool MainDialog::trySaveConfig(const wxString* fileNameGui) //return true if sav } -bool MainDialog::trySaveBatchConfig(const wxString* fileNameBatch) +bool MainDialog::trySaveBatchConfig(const Zstring* fileNameBatch) { //essentially behave like trySaveConfig(): the collateral damage of not saving GUI-only settings "hideExcludedItems, showSyncAction" is negliable const xmlAccess::XmlGuiConfig guiCfg = getConfig(); - wxString targetFilename; + Zstring targetFilename; xmlAccess::XmlBatchConfig batchCfg; if (fileNameBatch) { targetFilename = *fileNameBatch; - batchCfg = convertGuiToBatchPreservingExistingBatch(guiCfg, toZ(*fileNameBatch)); - assert(endsWith(targetFilename, L".ffs_batch")); + batchCfg = convertGuiToBatchPreservingExistingBatch(guiCfg, *fileNameBatch); + assert(endsWith(targetFilename, Zstr(".ffs_batch"))); } else { - const wxString activeCfgFilename = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : wxString(); - batchCfg = convertGuiToBatchPreservingExistingBatch(guiCfg, toZ(activeCfgFilename)); + const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : Zstring(); + batchCfg = convertGuiToBatchPreservingExistingBatch(guiCfg, activeCfgFilename); //let user change batch config: this should change batch-exclusive settings only, else the "setLastUsedConfig" below would be somewhat of a lie if (!customizeBatchConfig(this, @@ -2576,7 +2630,7 @@ bool MainDialog::trySaveBatchConfig(const wxString* fileNameBatch) globalCfg.gui.onCompletionHistoryMax)) return false; - wxString defaultFileName = !activeCfgFilename.empty() ? activeCfgFilename : L"BatchRun.ffs_batch"; + wxString defaultFileName = !activeCfgFilename.empty() ? toWx(activeCfgFilename) : L"BatchRun.ffs_batch"; //attention: activeConfigFiles may be a *.ffs_gui file! We don't want to overwrite it with a BATCH config! if (endsWith(defaultFileName, L".ffs_gui")) replace(defaultFileName, L".ffs_gui", L".ffs_batch"); @@ -2589,12 +2643,12 @@ bool MainDialog::trySaveBatchConfig(const wxString* fileNameBatch) wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (filePicker.ShowModal() != wxID_OK) return false; - targetFilename = filePicker.GetPath(); + targetFilename = toZ(filePicker.GetPath()); } try { - xmlAccess::writeConfig(batchCfg, toZ(targetFilename)); //throw FfsXmlError + xmlAccess::writeConfig(batchCfg, targetFilename); //throw FfsXmlError setLastUsedConfig(targetFilename, guiCfg); //[!] behave as if we had saved guiCfg flashStatusInformation(_("Configuration saved!")); @@ -2612,7 +2666,7 @@ bool MainDialog::saveOldConfig() //return false on user abort { if (lastConfigurationSaved != getConfig()) { - const wxString activeCfgFilename = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : wxString(); + const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : Zstring(); //notify user about changed settings if (globalCfg.optDialogs.popupOnConfigChange) @@ -2623,8 +2677,8 @@ bool MainDialog::saveOldConfig() //return false on user abort switch (showQuestionDlg(this, ReturnQuestionDlg::BUTTON_YES | ReturnQuestionDlg::BUTTON_NO | ReturnQuestionDlg::BUTTON_CANCEL, - replaceCpy(_("Do you want to save changes to %x?"), L"%x", fmtFileName(afterLast(utfCvrtTo<Zstring>(activeCfgFilename), FILE_NAME_SEPARATOR))), - QuestConfig().setCaption(activeCfgFilename). + replaceCpy(_("Do you want to save changes to %x?"), L"%x", fmtFileName(afterLast(activeCfgFilename, FILE_NAME_SEPARATOR))), + QuestConfig().setCaption(toWx(activeCfgFilename)). setLabelYes(_("&Save")). setLabelNo(_("Do&n't save")). showCheckBox(neverSave, _("Never save changes")))) @@ -2632,7 +2686,7 @@ bool MainDialog::saveOldConfig() //return false on user abort case ReturnQuestionDlg::BUTTON_YES: using namespace xmlAccess; - switch (getXmlType(utfCvrtTo<Zstring>(activeCfgFilename))) //throw() + switch (getXmlType(activeCfgFilename)) //throw() { case XML_TYPE_GUI: return trySaveConfig(&activeCfgFilename); @@ -2654,7 +2708,7 @@ bool MainDialog::saveOldConfig() //return false on user abort } //discard current reference file(s), this ensures next app start will load <last session> instead of the original non-modified config selection - setLastUsedConfig(std::vector<wxString>(), lastConfigurationSaved); + setLastUsedConfig(std::vector<Zstring>(), lastConfigurationSaved); //this seems to make theoretical sense also: the job of this function is to make sure current (volatile) config and reference file name are in sync // => if user does not save cfg, it is not attached to a physical file names anymore! } @@ -2664,11 +2718,11 @@ bool MainDialog::saveOldConfig() //return false on user abort void MainDialog::OnConfigLoad(wxCommandEvent& event) { - const wxString activeCfgFilename = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : wxString(); + const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : Zstring(); wxFileDialog filePicker(this, wxEmptyString, - beforeLast(activeCfgFilename, utfCvrtTo<wxString>(FILE_NAME_SEPARATOR)), //set default dir: empty string if "activeConfigFiles" is empty or has no path separator + toWx(beforeLast(activeCfgFilename, FILE_NAME_SEPARATOR)), //set default dir: empty string if "activeConfigFiles" is empty or has no path separator wxEmptyString, wxString(L"FreeFileSync (*.ffs_gui;*.ffs_batch)|*.ffs_gui;*.ffs_batch") + L"|" +_("All files") + L" (*.*)|*", wxFD_OPEN | wxFD_MULTIPLE); @@ -2677,9 +2731,9 @@ void MainDialog::OnConfigLoad(wxCommandEvent& event) { wxArrayString tmp; filePicker.GetPaths(tmp); - std::vector<wxString> fileNames(tmp.begin(), tmp.end()); + std::vector<wxString> filenames(tmp.begin(), tmp.end()); - loadConfiguration(fileNames); + loadConfiguration(toZ(filenames)); } } @@ -2689,7 +2743,16 @@ void MainDialog::OnConfigNew(wxCommandEvent& event) if (!saveOldConfig()) //notify user about changed settings return; - setConfig(xmlAccess::XmlGuiConfig(), std::vector<wxString>()); + xmlAccess::XmlGuiConfig newConfig; + + //add default exclusion filter: this is only ever relevant when creating new configurations! + //a default XmlGuiConfig does not need these user-specific exclusions! + Zstring& excludeFilter = newConfig.mainCfg.globalFilter.excludeFilter; + if (!excludeFilter.empty() && !endsWith(excludeFilter, Zstr("\n"))) + excludeFilter += Zstr("\n"); + excludeFilter += globalCfg.gui.defaultExclusionFilter; + + setConfig(newConfig, std::vector<Zstring>()); } @@ -2698,7 +2761,7 @@ void MainDialog::OnLoadFromHistory(wxCommandEvent& event) wxArrayInt selections; m_listBoxHistory->GetSelections(selections); - std::vector<wxString> filenames; + std::vector<Zstring> filenames; std::for_each(selections.begin(), selections.end(), [&](int pos) { @@ -2709,7 +2772,7 @@ void MainDialog::OnLoadFromHistory(wxCommandEvent& event) if (!filenames.empty()) loadConfiguration(filenames); - //user changed m_listBoxHistory selection so it's this method's responsibility to synchronize with 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 @@ -2722,9 +2785,8 @@ void MainDialog::OnLoadFromHistoryDoubleClick(wxCommandEvent& event) wxArrayInt selections; m_listBoxHistory->GetSelections(selections); - std::vector<wxString> filenames; - std::for_each(selections.begin(), selections.end(), - [&](int pos) + std::vector<Zstring> filenames; + std::for_each(selections.begin(), selections.end(), [&](int pos) { if (auto histData = dynamic_cast<const wxClientHistoryData*>(m_listBoxHistory->GetClientObject(pos))) filenames.push_back(histData->cfgFile_); @@ -2744,7 +2806,7 @@ void MainDialog::OnLoadFromHistoryDoubleClick(wxCommandEvent& event) } -bool MainDialog::loadConfiguration(const std::vector<wxString>& filenames) +bool MainDialog::loadConfiguration(const std::vector<Zstring>& filenames) { if (filenames.empty()) return true; @@ -2757,7 +2819,7 @@ bool MainDialog::loadConfiguration(const std::vector<wxString>& filenames) try { //allow reading batch configurations also - xmlAccess::readAnyConfig(toZ(filenames), newGuiCfg); //throw FfsXmlError + xmlAccess::readAnyConfig(filenames, newGuiCfg); //throw FfsXmlError setConfig(newGuiCfg, filenames); //flashStatusInformation(_("Configuration loaded!")); -> irrelevant!? @@ -2783,14 +2845,8 @@ void MainDialog::deleteSelectedCfgHistoryItems() m_listBoxHistory->GetSelections(tmp); std::set<int> selections(tmp.begin(), tmp.end()); //sort ascending! - - int shift = 0; - std::for_each(selections.begin(), selections.end(), - [&](int pos) - { - m_listBoxHistory->Delete(pos + shift); - --shift; - }); + //delete starting with high positions: + std::for_each(selections.rbegin(), selections.rend(), [&](int pos) { m_listBoxHistory->Delete(pos); }); //set active selection on next element to allow "batch-deletion" by holding down DEL key if (!selections.empty() && m_listBoxHistory->GetCount() > 0) @@ -2876,15 +2932,15 @@ void MainDialog::onSetSyncDirection(SyncDirectionEvent& event) } -void MainDialog::setLastUsedConfig(const wxString& filename, const xmlAccess::XmlGuiConfig& guiConfig) +void MainDialog::setLastUsedConfig(const Zstring& filename, const xmlAccess::XmlGuiConfig& guiConfig) { - std::vector<wxString> filenames; + std::vector<Zstring> filenames; filenames.push_back(filename); setLastUsedConfig(filenames, guiConfig); } -void MainDialog::setLastUsedConfig(const std::vector<wxString>& filenames, +void MainDialog::setLastUsedConfig(const std::vector<Zstring>& filenames, const xmlAccess::XmlGuiConfig& guiConfig) { activeConfigFiles = filenames; @@ -2896,7 +2952,7 @@ void MainDialog::setLastUsedConfig(const std::vector<wxString>& filenames, } -void MainDialog::setConfig(const xmlAccess::XmlGuiConfig& newGuiCfg, const std::vector<wxString>& referenceFiles) +void MainDialog::setConfig(const xmlAccess::XmlGuiConfig& newGuiCfg, const std::vector<Zstring>& referenceFiles) { currentCfg = newGuiCfg; @@ -2905,11 +2961,11 @@ void MainDialog::setConfig(const xmlAccess::XmlGuiConfig& newGuiCfg, const std:: //(re-)set view filter buttons setViewFilterDefault(); - updateFilterButtons(); + updateGlobalFilterButton(); //set first folder pair - firstFolderPair->setValues(toWx(currentCfg.mainCfg.firstPair.leftDirectory), - toWx(currentCfg.mainCfg.firstPair.rightDirectory), + firstFolderPair->setValues(currentCfg.mainCfg.firstPair.leftDirectory, + currentCfg.mainCfg.firstPair.rightDirectory, currentCfg.mainCfg.firstPair.altCmpConfig, currentCfg.mainCfg.firstPair.altSyncConfig, currentCfg.mainCfg.firstPair.localFilter); @@ -2946,8 +3002,8 @@ void MainDialog::setConfig(const xmlAccess::XmlGuiConfig& newGuiCfg, const std:: inline FolderPairEnh getEnhancedPair(const DirectoryPair* panel) { - return FolderPairEnh(toZ(panel->getLeftDir()), - toZ(panel->getRightDir()), + return FolderPairEnh(panel->getLeftDir(), + panel->getRightDir(), panel->getAltCompConfig(), panel->getAltSyncConfig(), panel->getAltFilterConfig()); @@ -2961,8 +3017,8 @@ xmlAccess::XmlGuiConfig MainDialog::getConfig() const //load settings whose ownership lies not in currentCfg: //first folder pair - guiCfg.mainCfg.firstPair = FolderPairEnh(toZ(firstFolderPair->getLeftDir()), - toZ(firstFolderPair->getRightDir()), + guiCfg.mainCfg.firstPair = FolderPairEnh(firstFolderPair->getLeftDir(), + firstFolderPair->getRightDir(), firstFolderPair->getAltCompConfig(), firstFolderPair->getAltSyncConfig(), firstFolderPair->getAltFilterConfig()); @@ -2979,9 +3035,9 @@ xmlAccess::XmlGuiConfig MainDialog::getConfig() const } -const wxString& MainDialog::lastRunConfigName() +const Zstring& MainDialog::lastRunConfigName() { - static wxString instance = toWx(zen::getConfigDir()) + L"LastRun.ffs_gui"; + static Zstring instance = zen::getConfigDir() + Zstr("LastRun.ffs_gui"); return instance; } @@ -3021,7 +3077,7 @@ void MainDialog::OnConfigureFilter(wxCommandEvent& event) true, //is main filter dialog currentCfg.mainCfg.globalFilter) == ReturnSmallDlg::BUTTON_OKAY) { - updateFilterButtons(); //refresh global filter icon + updateGlobalFilterButton(); //refresh global filter icon applyFilterConfig(); //re-apply filter } @@ -3031,17 +3087,28 @@ void MainDialog::OnConfigureFilter(wxCommandEvent& event) void MainDialog::OnGlobalFilterContext(wxMouseEvent& event) { - ContextMenu menu; - auto clearFilter = [&] { currentCfg.mainCfg.globalFilter = FilterConfig(); - - updateFilterButtons(); //refresh global filter icon + updateGlobalFilterButton(); //refresh global filter icon applyFilterConfig(); //re-apply filter }; - menu.addItem( _("Clear filter settings"), clearFilter, nullptr, !isNullFilter(currentCfg.mainCfg.globalFilter)); + auto copyFilter = [&] { filterCfgOnClipboard = make_unique<FilterConfig>(currentCfg.mainCfg.globalFilter); }; + auto pasteFilter = [&] + { + if (filterCfgOnClipboard) + { + currentCfg.mainCfg.globalFilter = *filterCfgOnClipboard; + updateGlobalFilterButton(); //refresh global filter icon + applyFilterConfig(); //re-apply filter + } + }; + ContextMenu menu; + menu.addItem( _("Clear filter settings"), clearFilter, nullptr, !isNullFilter(currentCfg.mainCfg.globalFilter)); + menu.addSeparator(); + menu.addItem( _("Copy"), copyFilter, nullptr, !isNullFilter(currentCfg.mainCfg.globalFilter)); + menu.addItem( _("Paste"), pasteFilter, nullptr, filterCfgOnClipboard.get() != nullptr); menu.popup(*this); } @@ -3063,8 +3130,7 @@ wxBitmap buttonPressed(const std::string& name) { wxBitmap background = getResourceImage(L"buttonPressed"); return mirrorIfRtl( - layOver( - getResourceImage(utfCvrtTo<wxString>(name)), background)); + layOver(getResourceImage(utfCvrtTo<wxString>(name)), background)); } @@ -3084,73 +3150,59 @@ void MainDialog::initViewFilterButtons() //compare result buttons m_bpButtonShowLeftOnly->init(buttonPressed("leftOnly"), buttonReleased("leftOnly"), - _("Hide files that exist on left side only"), _("Show files that exist on left side only")); m_bpButtonShowRightOnly->init(buttonPressed("rightOnly"), buttonReleased("rightOnly"), - _("Hide files that exist on right side only"), _("Show files that exist on right side only")); m_bpButtonShowLeftNewer->init(buttonPressed("leftNewer"), buttonReleased("leftNewer"), - _("Hide files that are newer on left"), _("Show files that are newer on left")); m_bpButtonShowRightNewer->init(buttonPressed("rightNewer"), buttonReleased("rightNewer"), - _("Hide files that are newer on right"), _("Show files that are newer on right")); m_bpButtonShowEqual->init(buttonPressed("equal"), buttonReleased("equal"), - _("Hide files that are equal"), _("Show files that are equal")); m_bpButtonShowDifferent->init(buttonPressed("different"), buttonReleased("different"), - _("Hide files that are different"), _("Show files that are different")); m_bpButtonShowConflict->init(buttonPressed("conflict"), buttonReleased("conflict"), - _("Hide conflicts"), _("Show conflicts")); //sync preview buttons m_bpButtonShowCreateLeft->init(buttonPressed("createLeft"), buttonReleased("createLeft"), - _("Hide files that will be created on the left side"), _("Show files that will be created on the left side")); m_bpButtonShowCreateRight->init(buttonPressed("createRight"), buttonReleased("createRight"), - _("Hide files that will be created on the right side"), _("Show files that will be created on the right side")); m_bpButtonShowDeleteLeft->init(buttonPressed("deleteLeft"), buttonReleased("deleteLeft"), - _("Hide files that will be deleted on the left side"), _("Show files that will be deleted on the left side")); m_bpButtonShowDeleteRight->init(buttonPressed("deleteRight"), buttonReleased("deleteRight"), - _("Hide files that will be deleted on the right side"), _("Show files that will be deleted on the right side")); m_bpButtonShowUpdateLeft->init(buttonPressed("updateLeft"), buttonReleased("updateLeft"), - _("Hide files that will be overwritten on left side"), _("Show files that will be overwritten on left side")); m_bpButtonShowUpdateRight->init(buttonPressed("updateRight"), buttonReleased("updateRight"), - _("Hide files that will be overwritten on right side"), _("Show files that will be overwritten on right side")); m_bpButtonShowDoNothing->init(buttonPressed("none"), buttonReleased("none"), - _("Hide files that won't be copied"), _("Show files that won't be copied")); } @@ -3212,7 +3264,7 @@ void MainDialog::OnViewButtonRightClick(wxMouseEvent& event) } -void MainDialog::updateFilterButtons() +void MainDialog::updateGlobalFilterButton() { //global filter: test for Null-filter if (!isNullFilter(currentCfg.mainCfg.globalFilter)) @@ -3225,12 +3277,6 @@ void MainDialog::updateFilterButtons() setImage(*m_bpButtonFilter, greyScale(getResourceImage(L"filter"))); m_bpButtonFilter->SetToolTip(_("No filter selected")); } - - //update local filter buttons - firstFolderPair->refreshButtons(); - - std::for_each(additionalFolderPairs.begin(), additionalFolderPairs.end(), - [&](DirectoryPair* dirPair) { dirPair->refreshButtons(); }); } @@ -3261,24 +3307,15 @@ void MainDialog::OnCompare(wxCommandEvent& event) const std::vector<zen::FolderPairCfg> cmpConfig = zen::extractCompareCfg(getConfig().mainCfg); //GUI mode: place directory locks on directories isolated(!) during both comparison and synchronization - std::unique_ptr<LockHolder> dummy2; - if (globalCfg.createLockFile) - { - std::vector<Zstring> dirnames; - std::for_each(cmpConfig.begin(), cmpConfig.end(), - [&](const FolderPairCfg& fpCfg) - { - dirnames.push_back(fpCfg.leftDirectoryFmt); - dirnames.push_back(fpCfg.rightDirectoryFmt); - }); - dummy2 = make_unique<LockHolder>(dirnames, statusHandler, true); //allow pw prompt - } + std::unique_ptr<LockHolder> dirLocks; //COMPARE DIRECTORIES compare(globalCfg.fileTimeTolerance, globalCfg.optDialogs, true, //allow pw prompt globalCfg.runWithBackgroundPriority, + globalCfg.createLockFile, + dirLocks, cmpConfig, folderCmp, statusHandler); //throw GuiAbortProcess @@ -3300,12 +3337,12 @@ void MainDialog::OnCompare(wxCommandEvent& event) m_gridNavi->clearSelection(); //play (optional) sound notification after sync has completed (GUI and batch mode) - const wxString soundFile = toWx(zen::getResourceDir()) + L"Compare_Complete.wav"; - if (fileExists(toZ(soundFile))) - wxSound::Play(soundFile, wxSOUND_ASYNC); + const Zstring soundFile = zen::getResourceDir() + Zstr("Compare_Complete.wav"); + if (fileExists(soundFile)) + wxSound::Play(toWx(soundFile), wxSOUND_ASYNC); //add to folder history after successful comparison only - folderHistoryLeft ->addItem(toZ(m_directoryLeft->GetValue())); + folderHistoryLeft ->addItem(toZ(m_directoryLeft ->GetValue())); folderHistoryRight->addItem(toZ(m_directoryRight->GetValue())); //prepare status information @@ -3474,7 +3511,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event) try { //PERF_START; - const wxString activeCfgFilename = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : wxString(); + const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : Zstring(); const auto& guiCfg = getConfig(); @@ -3482,21 +3519,23 @@ void MainDialog::OnStartSync(wxCommandEvent& event) SyncStatusHandler statusHandler(this, //throw GuiAbortProcess globalCfg.lastSyncsLogFileSizeMax, currentCfg.handleError, - xmlAccess::extractJobName(utfCvrtTo<Zstring>(activeCfgFilename)), + xmlAccess::extractJobName(activeCfgFilename), guiCfg.mainCfg.onCompletion, globalCfg.gui.onCompletionHistory); //GUI mode: place directory locks on directories isolated(!) during both comparison and synchronization - std::unique_ptr<LockHolder> dummy2; + std::unique_ptr<LockHolder> dirLocks; if (globalCfg.createLockFile) { - std::vector<Zstring> dirnames; + std::set<Zstring, LessFilename> dirnamesExisting; for (auto it = begin(folderCmp); it != end(folderCmp); ++it) { - dirnames.push_back(it->getBaseDirPf<LEFT_SIDE >()); - dirnames.push_back(it->getBaseDirPf<RIGHT_SIDE>()); + if (it->isExisting<LEFT_SIDE>()) //do NOT check directory existence again! + dirnamesExisting.insert(it->getBaseDirPf<LEFT_SIDE >()); + if (it->isExisting<RIGHT_SIDE>()) + dirnamesExisting.insert(it->getBaseDirPf<RIGHT_SIDE>()); } - dummy2 = make_unique<LockHolder>(dirnames, statusHandler, true); //allow pw prompt + dirLocks = make_unique<LockHolder>(dirnamesExisting, globalCfg.optDialogs.warningDirectoryLockFailed, statusHandler); } //START SYNCHRONIZATION @@ -3818,8 +3857,8 @@ void MainDialog::OnAddFolderPair(wxCommandEvent& event) //clear first pair const FolderPairEnh cfgEmpty; - firstFolderPair->setValues(toWx(cfgEmpty.leftDirectory), - toWx(cfgEmpty.rightDirectory), + firstFolderPair->setValues(cfgEmpty.leftDirectory, + cfgEmpty.rightDirectory, cfgEmpty.altCmpConfig, cfgEmpty.altSyncConfig, cfgEmpty.localFilter); @@ -3839,8 +3878,8 @@ void MainDialog::OnRemoveTopFolderPair(wxCommandEvent& event) const FolderPairEnh cfgSecond = getEnhancedPair(additionalFolderPairs[0]); //reset first pair - firstFolderPair->setValues(toWx(cfgSecond.leftDirectory), - toWx(cfgSecond.rightDirectory), + firstFolderPair->setValues(cfgSecond.leftDirectory, + cfgSecond.rightDirectory, cfgSecond.altCmpConfig, cfgSecond.altSyncConfig, cfgSecond.localFilter); @@ -3897,7 +3936,8 @@ void MainDialog::updateGuiForFolderPair() addPairOptimalHeight = std::max(addPairOptimalHeight, addPairMinimalHeight); //implicitly handle corrupted values for "maxFolderPairsVisible" } - const int firstPairHeight = m_panelDirectoryPairs->ClientToWindowSize(m_panelTopLeft->GetSize()).GetHeight(); //include m_panelDirectoryPairs window borders! + const int firstPairHeight = std::max(m_panelDirectoryPairs->ClientToWindowSize(m_panelTopLeft ->GetSize()).GetHeight(), //include m_panelDirectoryPairs window borders! + m_panelDirectoryPairs->ClientToWindowSize(m_panelTopMiddle->GetSize()).GetHeight()); // //######################################################################################################################## //wxAUI hack: set minimum height to desired value, then call wxAuiPaneInfo::Fixed() to apply it @@ -3963,8 +4003,8 @@ void MainDialog::addFolderPair(const std::vector<FolderPairEnh>& newPairs, bool updateGuiForFolderPair(); for (auto it = newPairs.begin(); it != newPairs.end(); ++it)//set alternate configuration - newEntries[it - newPairs.begin()]->setValues(toWx(it->leftDirectory), - toWx(it->rightDirectory), + newEntries[it - newPairs.begin()]->setValues(it->leftDirectory, + it->rightDirectory, it->altCmpConfig, it->altSyncConfig, it->localFilter); @@ -4198,20 +4238,26 @@ void MainDialog::OnMenuCheckVersion(wxCommandEvent& event) } +void MainDialog::OnMenuCheckVersionAutomatically(wxCommandEvent& event) +{ + globalCfg.gui.lastUpdateCheck = globalCfg.gui.lastUpdateCheck == -1 ? 0 : -1; + m_menuItemCheckVersionAuto->Check(globalCfg.gui.lastUpdateCheck != -1); + zen::checkForUpdatePeriodically(this, globalCfg.gui.lastUpdateCheck, [&] { flashStatusInformation(_("Searching for program updates...")); }); +} + + void MainDialog::OnRegularUpdateCheck(wxIdleEvent& event) { //execute just once per startup! Disconnect(wxEVT_IDLE, wxIdleEventHandler(MainDialog::OnRegularUpdateCheck), nullptr, this); if (manualProgramUpdateRequired()) - zen::checkForUpdatePeriodically(this, globalCfg.gui.lastUpdateCheck); + zen::checkForUpdatePeriodically(this, globalCfg.gui.lastUpdateCheck, [&] { flashStatusInformation(_("Searching for program updates...")); }); } void MainDialog::OnLayoutWindowAsync(wxIdleEvent& event) { - warn_static("harmonize with async version check?") - //execute just once per startup! Disconnect(wxEVT_IDLE, wxIdleEventHandler(MainDialog::OnLayoutWindowAsync), nullptr, this); diff --git a/ui/main_dlg.h b/ui/main_dlg.h index e1b2d44f..70f78e93 100644 --- a/ui/main_dlg.h +++ b/ui/main_dlg.h @@ -7,18 +7,19 @@ #ifndef MAINDIALOG_H #define MAINDIALOG_H -#include <memory> #include <map> #include <set> -#include <wx/aui/aui.h> -#include <zen/int64.h> #include <stack> +#include <memory> +#include <zen/int64.h> +#include <zen/async_task.h> +#include <wx+/file_drop.h> +#include <wx/aui/aui.h> #include "gui_generated.h" -#include "../lib/process_xml.h" #include "custom_grid.h" #include "tree_view.h" -#include <wx+/file_drop.h> #include "folder_history_box.h" +#include "../lib/process_xml.h" //class FolderHistory; class DirectoryPair; @@ -30,14 +31,14 @@ class MainDialog : public MainDialogGenerated { public: //default behavior, application start - static void create(const std::vector<wxString>& cfgFileNames); //cfgFileNames empty: restore last config; non-empty load/merge given set of config files + static void create(const std::vector<Zstring>& cfgFileNames); //cfgFileNames empty: restore last config; non-empty load/merge given set of config files //load dynamically assembled config static void create(const xmlAccess::XmlGuiConfig& guiCfg, bool startComparison); //when switching language or switching from batch run to GUI on warnings static void create(const xmlAccess::XmlGuiConfig& guiCfg, - const std::vector<wxString>& referenceFiles, + const std::vector<Zstring>& referenceFiles, const xmlAccess::XmlGlobalSettings& globalSettings, //take over ownership => save on exit bool startComparison); @@ -48,12 +49,12 @@ public: private: static void create_impl(const xmlAccess::XmlGuiConfig& guiCfg, - const std::vector<wxString>& referenceFiles, + const std::vector<Zstring>& referenceFiles, const xmlAccess::XmlGlobalSettings& globalSettings, bool startComparison); MainDialog(const xmlAccess::XmlGuiConfig& guiCfg, - const std::vector<wxString>& referenceFiles, + const std::vector<Zstring>& referenceFiles, const xmlAccess::XmlGlobalSettings& globalSettings, //take over ownership => save on exit bool startComparison); ~MainDialog(); @@ -69,33 +70,35 @@ private: friend class PanelMoveWindow; //configuration load/save - void setLastUsedConfig(const wxString& filename, const xmlAccess::XmlGuiConfig& guiConfig); - void setLastUsedConfig(const std::vector<wxString>& filenames, const xmlAccess::XmlGuiConfig& guiConfig); + void setLastUsedConfig(const Zstring& filename, const xmlAccess::XmlGuiConfig& guiConfig); + void setLastUsedConfig(const std::vector<Zstring>& filenames, const xmlAccess::XmlGuiConfig& guiConfig); xmlAccess::XmlGuiConfig getConfig() const; - void setConfig(const xmlAccess::XmlGuiConfig& newGuiCfg, const std::vector<wxString>& referenceFiles); + void setConfig(const xmlAccess::XmlGuiConfig& newGuiCfg, const std::vector<Zstring>& referenceFiles); 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)" - bool loadConfiguration(const std::vector<wxString>& filenames); //return true if loaded successfully + bool loadConfiguration(const std::vector<Zstring>& filenames); //return true if loaded successfully - bool trySaveConfig (const wxString* fileNameGui); //return true if saved successfully - bool trySaveBatchConfig(const wxString* fileNameBatch); // + bool trySaveConfig (const Zstring* fileNameGui); //return true if saved successfully + bool trySaveBatchConfig(const Zstring* fileNameBatch); // bool saveOldConfig(); //return false on user abort - static const wxString& lastRunConfigName(); + static const Zstring& lastRunConfigName(); xmlAccess::XmlGuiConfig lastConfigurationSaved; //support for: "Save changed configuration?" dialog //used when saving configuration - std::vector<wxString> activeConfigFiles; //name of currently loaded config file (may be more than 1) + std::vector<Zstring> activeConfigFiles; //name of currently loaded config file (may be more than 1) - void updateFilterButtons(); //file exclusion + void updateGlobalFilterButton(); void initViewFilterButtons(); void setViewFilterDefault(); - void addFileToCfgHistory(const std::vector<wxString>& filenames); //= update/insert + apply selection + void addFileToCfgHistory(const std::vector<Zstring>& filenames); //= update/insert + apply selection + void removeObsoleteCfgHistoryItems(const std::vector<Zstring>& filenames); + void removeCfgHistoryItems(const std::vector<Zstring>& filenames); void addFolderPair(const std::vector<zen::FolderPairEnh>& newPairs, bool addFront = false); void removeAddFolderPair(size_t pos); @@ -117,19 +120,22 @@ private: void setSyncDirManually(const std::vector<zen::FileSystemObject*>& selection, zen::SyncDirection direction); void setFilterManually(const std::vector<zen::FileSystemObject*>& selection, bool setIncluded); - void copySelectionToClipboard(); + void copySelectionToClipboard(const std::vector<const zen::Grid*>& gridRefs); void deleteSelectedFiles(const std::vector<zen::FileSystemObject*>& selectionLeft, const std::vector<zen::FileSystemObject*>& selectionRight); void openExternalApplication(const wxString& commandline, const std::vector<zen::FileSystemObject*>& selection, bool leftSide); //selection may be empty - //work to be done in idle time - void OnIdleEvent(wxEvent& event); + //don't use wxWidgets idle handling => repeated idle requests/consumption hogs 100% cpu! + void startProcessingAsyncTasks() { timerForAsyncTasks.Start(50); } //timer interval in [ms] + void onProcessAsyncTasks(wxEvent& event); //status bar supports one of the following two states at a time: void setStatusBarFullText(const wxString& msg); void setStatusBarFileStatistics(size_t filesOnLeftView, size_t foldersOnLeftView, size_t filesOnRightView, size_t foldersOnRightView, zen::UInt64 filesizeLeftView, zen::UInt64 filesizeRightView); + void flashStatusInformation(const wxString& msg); //temporarily show different status (only valid for setStatusBarFullText) + void restoreStatusInformation(); //called automatically after a few seconds //events void onGridButtonEventL(wxKeyEvent& event); @@ -225,6 +231,7 @@ private: virtual void OnMenuGlobalSettings(wxCommandEvent& event); virtual void OnMenuExportFileList(wxCommandEvent& event); virtual void OnMenuCheckVersion (wxCommandEvent& event); + virtual void OnMenuCheckVersionAutomatically(wxCommandEvent& event); virtual void OnMenuAbout (wxCommandEvent& event); virtual void OnShowHelp (wxCommandEvent& event); virtual void OnMenuQuit (wxCommandEvent& event) { Close(); } @@ -263,8 +270,7 @@ private: //*********************************************** //status information - wxLongLong lastStatusChange; - std::unique_ptr<wxString> oldStatusMsg; + std::list<wxString> oldStatusMsgs; //the first one is the original/non-flash status message //compare status panel (hidden on start, shown when comparing) std::unique_ptr<CompareProgressDialog> compareStatus; //always bound @@ -285,6 +291,12 @@ private: std::shared_ptr<FolderHistory> folderHistoryLeft; //shared by all wxComboBox dropdown controls std::shared_ptr<FolderHistory> folderHistoryRight; //always bound! + + //schedule and run long-running tasks asynchronously, but process results on GUI queue + zen::AsyncTasks asyncTasks; + wxTimer timerForAsyncTasks; //don't use wxWidgets idle handling => repeated idle requests/consumption hogs 100% cpu! + + std::unique_ptr<zen::FilterConfig> filterCfgOnClipboard; //copy/paste of filter config }; #endif // MAINDIALOG_H diff --git a/ui/msg_popup.cpp b/ui/msg_popup.cpp index fb33dfb0..940163af 100644 --- a/ui/msg_popup.cpp +++ b/ui/msg_popup.cpp @@ -156,7 +156,7 @@ WarningDlg::WarningDlg(wxWindow* parent, int activeButtons, const wxString& mes SetTitle(_("Warning")); m_bitmapMsgType->SetBitmap(getResourceImage(L"msg_warning")); m_textCtrlMessage->SetValue(messageText); - checkBoxDontShowAgain.SetLabel(_("Don't show this dialog again")); + checkBoxDontShowAgain.SetLabel(_("Don't show this warning again")); buttonIgnore.SetLabel(_("&Ignore")); buttonSwitch.SetLabel(_("&Switch")); //buttonIgnore.SetId(wxID_IGNORE); -> see comment in ErrorDlg diff --git a/ui/progress_indicator.cpp b/ui/progress_indicator.cpp index b36ccb0f..d23de61a 100644 --- a/ui/progress_indicator.cpp +++ b/ui/progress_indicator.cpp @@ -196,17 +196,17 @@ void CompareProgressDialog::Pimpl::updateStatusPanelNow() //remaining time and speed: only visible during binary comparison if (perf) { - if (numeric::dist(lastStatCallSpeed, timeElapsed.Time()) >= 500) + const long timeNow = timeElapsed.Time(); + if (numeric::dist(lastStatCallSpeed, timeNow) >= 500) { - lastStatCallSpeed = timeElapsed.Time(); + lastStatCallSpeed = timeNow; - perf->addSample(objectsCurrent, to<double>(dataCurrent), timeElapsed.Time()); + perf->addSample(objectsCurrent, to<double>(dataCurrent), timeNow); //current speed -> Win 7 copy uses 1 sec update interval setText(*m_staticTextSpeed, perf->getBytesPerSecond(), &layoutChanged); } - warn_static("more often: eveyr 100 ms?") //remaining time setText(*m_staticTextRemTime, perf->getRemainingTime(to<double>(dataTotal - dataCurrent)), &layoutChanged); } @@ -1257,11 +1257,12 @@ void SyncProgressDialog::Pimpl::updateGui(bool allowYield) assert(perf); if (perf) { - if (numeric::dist(lastStatCallSpeed, timeElapsed.Time()) >= 500) + const long timeNow = timeElapsed.Time(); + if (numeric::dist(lastStatCallSpeed, timeNow) >= 500) { - lastStatCallSpeed = timeElapsed.Time(); + lastStatCallSpeed = timeNow; - perf->addSample(objectsCurrent, to<double>(dataCurrent), timeElapsed.Time()); + perf->addSample(objectsCurrent, to<double>(dataCurrent), timeNow); //current speed -> Win 7 copy uses 1 sec update interval setText(*m_staticTextSpeed, perf->getBytesPerSecond(), &layoutChanged); @@ -1617,6 +1618,7 @@ void SyncProgressDialog::Pimpl::processHasFinished(SyncResult resultId, const Er void SyncProgressDialog::Pimpl::OnOkay(wxCommandEvent& event) { + isZombie = true; //on Fedora the iconize event is executed *before* entering SyncProgressDialog::Pimpl::OnClose()!!! Close(); //generate close event: do NOT destroy window unconditionally! } @@ -1726,7 +1728,9 @@ SyncProgressDialog::SyncProgressDialog(AbortCallback& abortCb, if (showProgress) { pimpl->Show(); - pimpl->updateGui(false); //clear gui flicker, remove dummy texts: window must be visible to make this work! + warn_static("problem??") + //clear gui flicker, remove dummy texts: window must be visible to make this work! + pimpl->updateGui(true); //at least on OS X a real Yield() is required to flush pending GUI updates; Update() is not enough } else pimpl->minimizeToTray(); diff --git a/ui/small_dlgs.cpp b/ui/small_dlgs.cpp index fd62b0cb..3827c837 100644 --- a/ui/small_dlgs.cpp +++ b/ui/small_dlgs.cpp @@ -143,7 +143,7 @@ private: virtual void OnClose (wxCloseEvent& event) { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } virtual void OnCancel (wxCommandEvent& event) { EndModal(ReturnSmallDlg::BUTTON_CANCEL); } virtual void OnHelp (wxCommandEvent& event) { displayHelpEntry(L"html/Exclude Items.html", this); } - virtual void OnDefault (wxCommandEvent& event); + virtual void OnClear (wxCommandEvent& event); virtual void OnApply (wxCommandEvent& event); virtual void OnUpdateChoice(wxCommandEvent& event) { updateGui(); } virtual void OnUpdateNameFilter(wxCommandEvent& event) { updateGui(); } @@ -153,7 +153,6 @@ private: FilterConfig getFilter() const; void onKeyEvent(wxKeyEvent& event); - const bool isGlobalFilter_; FilterConfig& outputRef; EnumDescrList<UnitTime> enumTimeDescr; @@ -165,7 +164,6 @@ FilterDlg::FilterDlg(wxWindow* parent, bool isGlobalFilter, //global or local filter dialog? FilterConfig& filter) : FilterDlgGenerated(parent), - isGlobalFilter_(isGlobalFilter), outputRef(filter) //just hold reference { #ifdef FFS_WIN @@ -207,6 +205,7 @@ FilterDlg::FilterDlg(wxWindow* parent, // else // m_staticTexHeader->SetLabel("Filter single folder pair")); // + m_staticTextHeader->SetLabel(_("Filter")); Fit(); //child-element widths have changed: image was set @@ -234,30 +233,23 @@ void FilterDlg::updateGui() { FilterConfig activeCfg = getFilter(); - m_bitmapInclude->SetBitmap( - !NameFilter::isNull(activeCfg.includeFilter, FilterConfig().excludeFilter) ? - getResourceImage(L"filter_include") : - greyScale(getResourceImage(L"filter_include"))); - - m_bitmapExclude->SetBitmap( - !NameFilter::isNull(FilterConfig().includeFilter, activeCfg.excludeFilter) ? - getResourceImage(L"filter_exclude") : - greyScale(getResourceImage(L"filter_exclude"))); - - m_bitmapFilterDate->SetBitmap( - activeCfg.unitTimeSpan != UTIME_NONE ? - getResourceImage(L"clock") : - greyScale(getResourceImage(L"clock"))); - - m_bitmapFilterSize->SetBitmap( - activeCfg.unitSizeMin != USIZE_NONE || - activeCfg.unitSizeMax != USIZE_NONE ? - getResourceImage(L"size") : - greyScale(getResourceImage(L"size"))); + auto setStatusBitmap = [&](wxStaticBitmap& staticBmp, const wxString& bmpName, bool active) + { + if (active) + staticBmp.SetBitmap(getResourceImage(bmpName)); + else + staticBmp.SetBitmap(greyScale(getResourceImage(bmpName))); + }; + setStatusBitmap(*m_bitmapInclude, L"filter_include", !NameFilter::isNull(activeCfg.includeFilter, FilterConfig().excludeFilter)); + setStatusBitmap(*m_bitmapExclude, L"filter_exclude", !NameFilter::isNull(FilterConfig().includeFilter, activeCfg.excludeFilter)); + setStatusBitmap(*m_bitmapFilterDate, L"clock", activeCfg.unitTimeSpan != UTIME_NONE); + setStatusBitmap(*m_bitmapFilterSize, L"size", activeCfg.unitSizeMin != USIZE_NONE || activeCfg.unitSizeMax != USIZE_NONE); m_spinCtrlTimespan->Enable(activeCfg.unitTimeSpan == UTIME_LAST_X_DAYS); m_spinCtrlMinSize ->Enable(activeCfg.unitSizeMin != USIZE_NONE); m_spinCtrlMaxSize ->Enable(activeCfg.unitSizeMax != USIZE_NONE); + + m_buttonClear ->Enable(!(activeCfg == FilterConfig())); } @@ -291,21 +283,15 @@ FilterConfig FilterDlg::getFilter() const } -void FilterDlg::OnDefault(wxCommandEvent& event) +void FilterDlg::OnClear(wxCommandEvent& event) { - if (isGlobalFilter_) - setFilter(MainConfiguration().globalFilter); - else - setFilter(FilterConfig()); - - //changes to mainDialog are only committed when the OK button is pressed - Fit(); + setFilter(FilterConfig()); } void FilterDlg::OnApply(wxCommandEvent& event) { - //only if user presses ApplyFilter, he wants the changes to be committed + //changes to mainDialog are only committed when the OK button is pressed outputRef = getFilter(); //when leaving dialog: filter and redraw grid, if filter is active @@ -395,14 +381,14 @@ void DeleteDialog::updateGui() wxString header; if (m_checkBoxUseRecycler->GetValue()) { - header = _P("Do you really want to move the following object to the Recycle Bin?", - "Do you really want to move the following %x objects to the Recycle Bin?", delInfo.second); + header = _P("Do you really want to move the following item to the Recycle Bin?", + "Do you really want to move the following %x items to the Recycle Bin?", delInfo.second); m_bitmapDeleteType->SetBitmap(getResourceImage(L"recycler")); } else { - header = _P("Do you really want to delete the following object?", - "Do you really want to delete the following %x objects?", delInfo.second); + header = _P("Do you really want to delete the following item?", + "Do you really want to delete the following %x items?", delInfo.second); m_bitmapDeleteType->SetBitmap(getResourceImage(L"deleteFile")); } replace(header, L"%x", toGuiString(delInfo.second)); diff --git a/ui/switch_to_gui.h b/ui/switch_to_gui.h index a12c7b03..a3cf6827 100644 --- a/ui/switch_to_gui.h +++ b/ui/switch_to_gui.h @@ -16,7 +16,7 @@ namespace zen class SwitchToGui { public: - SwitchToGui(const wxString& referenceFile, + SwitchToGui(const Zstring& referenceFile, const xmlAccess::XmlBatchConfig& batchCfg, xmlAccess::XmlGlobalSettings& globalSettings) : guiCfg(xmlAccess::convertBatchToGui(batchCfg)), @@ -31,7 +31,7 @@ public: } private: - std::vector<wxString> referenceFiles; + std::vector<Zstring> referenceFiles; const xmlAccess::XmlGuiConfig guiCfg; xmlAccess::XmlGlobalSettings& globalSettings_; }; diff --git a/ui/sync_cfg.cpp b/ui/sync_cfg.cpp index 2d92e231..dbef3619 100644 --- a/ui/sync_cfg.cpp +++ b/ui/sync_cfg.cpp @@ -233,12 +233,10 @@ SyncCfgDialog::SyncCfgDialog(wxWindow* parent, setRelativeFontSize(*m_toggleBtnMirror, 1.25); setRelativeFontSize(*m_toggleBtnUpdate, 1.25); setRelativeFontSize(*m_toggleBtnCustom, 1.25); - setRelativeFontSize(*m_staticTextHeaderCategory, 0.90); - setRelativeFontSize(*m_staticTextHeaderAction, 0.90); enumVersioningStyle. - add(VER_STYLE_ADD_TIMESTAMP, _("Versioning"), _("Append a timestamp to each file name")). - add(VER_STYLE_REPLACE, _("Replace"), _("Move files and replace if existing")); + add(VER_STYLE_ADD_TIMESTAMP, _("Time stamp"), _("Append a timestamp to each file name")). + add(VER_STYLE_REPLACE, _("Replace"), _("Move files and replace if existing")); //hide controls for optional parameters if (!handleError && !execWhenFinished) //currently either both or neither are bound! diff --git a/ui/tree_view.cpp b/ui/tree_view.cpp index eff840bc..07f7a942 100644 --- a/ui/tree_view.cpp +++ b/ui/tree_view.cpp @@ -24,13 +24,13 @@ inline void TreeView::compressNode(Container& cont) //remove single-element sub-trees -> gain clarity + usability (call *after* inclusion check!!!) { if (cont.subDirs.empty() || //single files node or... - (cont.firstFile == nullptr && //single dir node... + (cont.firstFileId == nullptr && //single dir node... cont.subDirs.size() == 1 && // - cont.subDirs[0].firstFile == nullptr && //...that is empty + cont.subDirs[0].firstFileId == nullptr && //...that is empty cont.subDirs[0].subDirs.empty())) // { cont.subDirs.clear(); - cont.firstFile = nullptr; + cont.firstFileId = nullptr; } } @@ -56,50 +56,57 @@ void TreeView::extractVisibleSubtree(HierarchyObject& hierObj, //in return std::max(fileObj.getFileSize<LEFT_SIDE>(), fileObj.getFileSize<RIGHT_SIDE>()); }; - - cont.firstFile = nullptr; + cont.firstFileId = nullptr; std::for_each(hierObj.refSubFiles().begin(), hierObj.refSubFiles().end(), [&](FileMapping& fileObj) { if (pred(fileObj)) { cont.bytesNet += getBytes(fileObj); + ++cont.itemCountNet; - if (!cont.firstFile) - cont.firstFile = fileObj.getId(); + if (!cont.firstFileId) + cont.firstFileId = fileObj.getId(); } }); - cont.bytesGross += cont.bytesNet; - if (!cont.firstFile) - std::find_if(hierObj.refSubLinks().begin(), hierObj.refSubLinks().end(), - [&](SymLinkMapping& linkObj) -> bool + std::for_each(hierObj.refSubLinks().begin(), hierObj.refSubLinks().end(), + [&](SymLinkMapping& linkObj) { if (pred(linkObj)) { - cont.firstFile = linkObj.getId(); - return true; + ++cont.itemCountNet; + + if (!cont.firstFileId) + cont.firstFileId = linkObj.getId(); } - return false; }); + cont.bytesGross += cont.bytesNet; + cont.itemCountGross += cont.itemCountNet; cont.subDirs.reserve(hierObj.refSubDirs().size()); //avoid expensive reallocations! std::for_each(hierObj.refSubDirs().begin(), hierObj.refSubDirs().end(), [&cont, pred](DirMapping& subDirObj) { + const bool included = pred(subDirObj); + cont.subDirs.push_back(TreeView::DirNodeImpl()); // auto& subDirView = cont.subDirs.back(); TreeView::extractVisibleSubtree(subDirObj, subDirView, pred); - cont.bytesGross += subDirView.bytesGross; + if (included) + ++subDirView.itemCountGross; + + cont.bytesGross += subDirView.bytesGross; + cont.itemCountGross += subDirView.itemCountGross; - if (pred(subDirObj) || subDirView.firstFile || !subDirView.subDirs.empty()) + if (!included && !subDirView.firstFileId && subDirView.subDirs.empty()) + cont.subDirs.pop_back(); + else { subDirView.objId = subDirObj.getId(); compressNode(subDirView); } - else - cont.subDirs.pop_back(); }); } @@ -202,7 +209,23 @@ void TreeView::sortSingleLevel(std::vector<TreeLine>& items, ColumnTypeNavi colu return 0U; }; + auto getCount = [](const TreeLine& line) -> int + { + switch (line.type_) + { + case TreeView::TYPE_ROOT: + case TreeView::TYPE_DIRECTORY: + return line.node_->itemCountGross; + + case TreeView::TYPE_FILES: + return line.node_->itemCountNet; + } + assert(false); + return 0; + }; + const auto lessBytes = [&](const TreeLine& lhs, const TreeLine& rhs) { return getBytes(lhs) < getBytes(rhs); }; + const auto lessCount = [&](const TreeLine& lhs, const TreeLine& rhs) { return getCount(lhs) < getCount(rhs); }; switch (columnType) { @@ -213,6 +236,10 @@ void TreeView::sortSingleLevel(std::vector<TreeLine>& items, ColumnTypeNavi colu case COL_TYPE_NAVI_DIRECTORY: std::sort(items.begin(), items.end(), LessShortName<ascending>()); break; + + case COL_TYPE_NAVI_ITEM_COUNT: + std::sort(items.begin(), items.end(), makeSortDirection(lessCount, Int2Type<ascending>())); + break; } } @@ -230,7 +257,7 @@ void TreeView::getChildren(const Container& cont, size_t level, std::vector<Tree workList.push_back(std::make_pair(subDir.bytesGross, &output.back().percent_)); }); - if (cont.firstFile) + if (cont.firstFileId) { output.push_back(TreeLine(level, 0, &cont, TreeView::TYPE_FILES)); workList.push_back(std::make_pair(cont.bytesNet, &output.back().percent_)); @@ -302,7 +329,7 @@ void TreeView::applySubView(std::vector<RootNodeImpl>&& newView) } //restore node expansion status - for (size_t row = 0; row < flatTree.size(); ++row) //flatTree size changes within loop! + for (size_t row = 0; row < flatTree.size(); ++row) //flatTree size changes during loop! { const TreeLine& line = flatTree[row]; @@ -334,15 +361,16 @@ void TreeView::updateView(Predicate pred) //warning: the following lines are almost 1:1 copy from extractVisibleSubtree: //however we *cannot* reuse code here; this were only possible if we could replace "std::vector<RootNodeImpl>" by "Container"! - if (root.firstFile || !root.subDirs.empty()) + if (!root.firstFileId && root.subDirs.empty()) + newView.pop_back(); + else { root.baseMap = baseObj; this->compressNode(root); //"this->" required by two-pass lookup as enforced by GCC 4.7 } - else - newView.pop_back(); }); + lastViewFilterPred = pred; applySubView(std::move(newView)); } @@ -365,6 +393,8 @@ bool TreeView::getDefaultSortDirection(ColumnTypeNavi colType) return false; case COL_TYPE_NAVI_DIRECTORY: return true; + case COL_TYPE_NAVI_ITEM_COUNT: + return false; } assert(false); return true; @@ -383,7 +413,7 @@ TreeView::NodeStatus TreeView::getStatus(size_t row) const { case TreeView::TYPE_DIRECTORY: case TreeView::TYPE_ROOT: - return flatTree[row].node_->firstFile || !flatTree[row].node_->subDirs.empty() ? TreeView::STATUS_REDUCED : TreeView::STATUS_EMPTY; + return flatTree[row].node_->firstFileId || !flatTree[row].node_->subDirs.empty() ? TreeView::STATUS_REDUCED : TreeView::STATUS_EMPTY; case TreeView::TYPE_FILES: return TreeView::STATUS_EMPTY; @@ -466,7 +496,14 @@ void TreeView::updateCmpResult(bool hideFiltered, bool equalFilesActive, bool conflictFilesActive) { - updateView([&](const FileSystemObject& fsObj) -> bool + updateView([hideFiltered, //make sure the predicate can be stored safely! + leftOnlyFilesActive, + rightOnlyFilesActive, + leftNewerFilesActive, + rightNewerFilesActive, + differentFilesActive, + equalFilesActive, + conflictFilesActive](const FileSystemObject& fsObj) -> bool { if (hideFiltered && !fsObj.isActive()) return false; @@ -506,7 +543,16 @@ void TreeView::updateSyncPreview(bool hideFiltered, bool syncEqualActive, bool conflictFilesActive) { - updateView([&](const FileSystemObject& fsObj) -> bool + updateView([hideFiltered, //make sure the predicate can be stored safely! + syncCreateLeftActive, + syncCreateRightActive, + syncDeleteLeftActive, + syncDeleteRightActive, + syncDirOverwLeftActive, + syncDirOverwRightActive, + syncDirNoneActive, + syncEqualActive, + conflictFilesActive](const FileSystemObject& fsObj) -> bool { if (hideFiltered && !fsObj.isActive()) return false; @@ -564,14 +610,14 @@ std::unique_ptr<TreeView::Node> TreeView::getLine(size_t row) const if (row < flatTree.size()) { const auto level = flatTree[row].level_; - const int percent = flatTree[row].percent_; + switch (flatTree[row].type_) { case TreeView::TYPE_ROOT: { const auto* root = static_cast<const TreeView::RootNodeImpl*>(flatTree[row].node_); - return make_unique<TreeView::RootNode>(percent, getStatus(row), root->bytesGross, *(root->baseMap)); + return make_unique<TreeView::RootNode>(percent, root->bytesGross, root->itemCountGross, getStatus(row), *(root->baseMap)); } break; @@ -579,15 +625,34 @@ std::unique_ptr<TreeView::Node> TreeView::getLine(size_t row) const { const auto* dir = static_cast<const TreeView::DirNodeImpl*>(flatTree[row].node_); if (auto dirObj = dynamic_cast<DirMapping*>(FileSystemObject::retrieve(dir->objId))) - return make_unique<TreeView::DirNode>(percent, level, getStatus(row), dir->bytesGross, *dirObj); + return make_unique<TreeView::DirNode>(percent, dir->bytesGross, dir->itemCountGross, level, getStatus(row), *dirObj); } break; case TreeView::TYPE_FILES: { const auto* parentDir = flatTree[row].node_; - if (auto firstFile = FileSystemObject::retrieve(parentDir->firstFile)) - return make_unique<TreeView::FilesNode>(percent, level, parentDir->bytesNet, *firstFile); + if (auto firstFile = FileSystemObject::retrieve(parentDir->firstFileId)) + { + std::vector<FileSystemObject*> filesAndLinks; + HierarchyObject& parent = firstFile->parent(); + + //lazy evaluation: recheck "lastViewFilterPred" again rather than buffer and bloat "lastViewFilterPred" + std::for_each(parent.refSubFiles().begin(), parent.refSubFiles().end(), + [&](FileSystemObject& fsObj) + { + if (lastViewFilterPred(fsObj)) + filesAndLinks.push_back(&fsObj); + }); + std::for_each(parent.refSubLinks().begin(), parent.refSubLinks().end(), + [&](FileSystemObject& fsObj) + { + if (lastViewFilterPred(fsObj)) + filesAndLinks.push_back(&fsObj); + }); + + return make_unique<TreeView::FilesNode>(percent, parentDir->bytesNet, parentDir->itemCountNet, level, filesAndLinks); + } } break; } @@ -672,13 +737,16 @@ private: else if (dirRight.empty()) return dirLeft; else - return utfCvrtTo<wxString>(dirLeft + L" \x2212 " + dirRight); //\x2212 = unicode minus + return dirLeft + L" \x2212 " + dirRight; //\x2212 = unicode minus } else if (const TreeView::DirNode* dir = dynamic_cast<const TreeView::DirNode*>(node.get())) return utfCvrtTo<wxString>(dir->dirObj_.getObjShortName()); else if (dynamic_cast<const TreeView::FilesNode*>(node.get())) return _("Files"); break; + + case COL_TYPE_NAVI_ITEM_COUNT: + return toGuiString(node->itemCount_); } } return wxEmptyString; @@ -876,8 +944,9 @@ private: { int alignment = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL; - //have file size right-justified (but don't change for RTL languages) - if (static_cast<ColumnTypeNavi>(colType) == COL_TYPE_NAVI_BYTES && grid.GetLayoutDirection() != wxLayout_RightToLeft) + //have file size and item count right-justified (but don't change for RTL languages) + if ((static_cast<ColumnTypeNavi>(colType) == COL_TYPE_NAVI_BYTES || + static_cast<ColumnTypeNavi>(colType) == COL_TYPE_NAVI_ITEM_COUNT) && grid.GetLayoutDirection() != wxLayout_RightToLeft) { rectTmp.width -= 2 * GAP_SIZE; alignment = wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL; @@ -918,6 +987,8 @@ private: return _("Size"); case COL_TYPE_NAVI_DIRECTORY: return _("Name"); + case COL_TYPE_NAVI_ITEM_COUNT: + return _("Items"); } return wxEmptyString; } @@ -1043,6 +1114,8 @@ private: ContextMenu menu; //-------------------------------------------------------------------------------------------------------- + menu.addCheckBox(_("Percentage"), [this] { setShowPercentage(!getShowPercentage()); }, getShowPercentage()); + //-------------------------------------------------------------------------------------------------------- auto toggleColumn = [&](const Grid::ColumnAttribute& ca) { auto colAttr = grid_.getColumnConfig(); @@ -1065,8 +1138,6 @@ private: ca.visible_, ca.type_ != static_cast<ColumnType>(COL_TYPE_NAVI_DIRECTORY)); //do not allow user to hide file name column! } //-------------------------------------------------------------------------------------------------------- - menu.addCheckBox(_("Percentage"), [this] { setShowPercentage(!getShowPercentage()); }, getShowPercentage()); - //-------------------------------------------------------------------------------------------------------- menu.addSeparator(); auto setDefaultColumns = [&] diff --git a/ui/tree_view.h b/ui/tree_view.h index af269d53..9ea7d0e4 100644 --- a/ui/tree_view.h +++ b/ui/tree_view.h @@ -7,10 +7,11 @@ #ifndef TREE_H_INCLUDED_841703190201835280256673425 #define TREE_H_INCLUDED_841703190201835280256673425 +#include <functional> +#include <zen/optional.h> #include <wx+/grid.h> -#include "../file_hierarchy.h" #include "column_attr.h" -#include <zen/optional.h> +#include "../file_hierarchy.h" namespace zen { @@ -56,31 +57,32 @@ public: //--------------------------------------------------------------------- struct Node { - Node(int percent, size_t level, NodeStatus status, UInt64 bytes) : - percent_(percent), level_(level), status_(status), bytes_(bytes) {} + Node(int percent, UInt64 bytes, int itemCount, size_t level, NodeStatus status) : + percent_(percent), level_(level), status_(status), bytes_(bytes), itemCount_(itemCount) {} virtual ~Node() {} const int percent_; //[0, 100] const size_t level_; const NodeStatus status_; const UInt64 bytes_; + const int itemCount_; }; struct FilesNode : public Node { - FilesNode(int percent, size_t level, UInt64 bytes, FileSystemObject& firstFile) : Node(percent, level, STATUS_EMPTY, bytes), firstFile_(firstFile) {} - FileSystemObject& firstFile_; //or symlink + FilesNode(int percent, UInt64 bytes, int itemCount, size_t level, const std::vector<FileSystemObject*>& filesAndLinks) : Node(percent, bytes, itemCount, level, STATUS_EMPTY), filesAndLinks_(filesAndLinks) {} + std::vector<FileSystemObject*> filesAndLinks_; //files or symlinks; pointers are bound! }; struct DirNode : public Node { - DirNode(int percent, size_t level, NodeStatus status, UInt64 bytes, DirMapping& dirObj) : Node(percent, level, status, bytes), dirObj_(dirObj) {} + DirNode(int percent, UInt64 bytes, int itemCount, size_t level, NodeStatus status, DirMapping& dirObj) : Node(percent, bytes, itemCount, level, status), dirObj_(dirObj) {} DirMapping& dirObj_; }; struct RootNode : public Node { - RootNode(int percent, NodeStatus status, UInt64 bytes, BaseDirMapping& baseMap) : Node(percent, 0, status, bytes), baseMap_(baseMap) {} + RootNode(int percent, UInt64 bytes, int itemCount, NodeStatus status, BaseDirMapping& baseMap) : Node(percent, bytes, itemCount, 0, status), baseMap_(baseMap) {} BaseDirMapping& baseMap_; }; @@ -101,11 +103,17 @@ private: struct Container { - Container() : firstFile(nullptr) {} + Container() : itemCountGross(), itemCountNet(), firstFileId(nullptr) {} UInt64 bytesGross; - UInt64 bytesNet; //files in this directory only + UInt64 bytesNet; //bytes for files on view in this directory only + int itemCountGross; + int itemCountNet; //number of files on view for in this directory only + std::vector<DirNodeImpl> subDirs; - FileSystemObject::ObjectId firstFile; //weak pointer to first FileMapping or SymLinkMapping + FileSystemObject::ObjectId firstFileId; //weak pointer to first FileMapping or SymLinkMapping + //- "compress" algorithm may hide file nodes for directories with a single included file, i.e. itemCountGross == itemCountNet == 1 + //- a HierarchyObject* would a better fit, but we need weak pointer semantics! + //- a std::vector<FileSystemObject::ObjectId> would be a better design, but we don't want a second memory structure as large as custom grid! }; struct DirNodeImpl : public Container @@ -134,13 +142,13 @@ private: size_t level_; int percent_; //[0, 100] const Container* node_; // - NodeType type_; //we choose to increase size of "flatTree" rather than "folderCmpView" by not using dynamic polymorphism! + NodeType type_; //we choose to increase size of "flatTree" rather than "folderCmpView" by not using dynamic polymorphism! }; static void compressNode(Container& cont); template <class Function> static void extractVisibleSubtree(HierarchyObject& hierObj, Container& cont, Function includeObject); - void getChildren(const Container& cont, size_t level, std::vector<TreeLine>& output); + void getChildren(const Container& cont, size_t level, std::vector<TreeLine>& output); template <class Predicate> void updateView(Predicate pred); void applySubView(std::vector<RootNodeImpl>&& newView); @@ -152,6 +160,7 @@ private: | (update...) | */ std::vector<RootNodeImpl> folderCmpView; //partial view on folderCmp -> unsorted (cannot be, because files are not a separate entity) + std::function<bool(const FileSystemObject& fsObj)> lastViewFilterPred; //buffer view filter predicate for lazy evaluation of files/symlinks corresponding to a TYPE_FILES node /* /|\ | (update...) | */ |