diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:22:18 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:22:18 +0200 |
commit | bcc5cc28c6dc5178e8f4fd0cc521034ae5def388 (patch) | |
tree | bacc60d27b435d32172f97643576c5e4e953177d /ui | |
parent | 5.9 (diff) | |
download | FreeFileSync-bcc5cc28c6dc5178e8f4fd0cc521034ae5def388.tar.gz FreeFileSync-bcc5cc28c6dc5178e8f4fd0cc521034ae5def388.tar.bz2 FreeFileSync-bcc5cc28c6dc5178e8f4fd0cc521034ae5def388.zip |
5.10
Diffstat (limited to 'ui')
-rw-r--r-- | ui/IFileDialog_Vista/IFileDialog_Vista.vcxproj | 13 | ||||
-rw-r--r-- | ui/Taskbar_Seven/Taskbar_Seven.vcxproj | 15 | ||||
-rw-r--r-- | ui/batch_config.cpp | 2 | ||||
-rw-r--r-- | ui/batch_status_handler.cpp | 31 | ||||
-rw-r--r-- | ui/batch_status_handler.h | 2 | ||||
-rw-r--r-- | ui/column_attr.h | 5 | ||||
-rw-r--r-- | ui/custom_grid.cpp | 80 | ||||
-rw-r--r-- | ui/custom_grid.h | 12 | ||||
-rw-r--r-- | ui/dir_name.cpp | 2 | ||||
-rw-r--r-- | ui/gui_generated.cpp | 110 | ||||
-rw-r--r-- | ui/gui_generated.h | 10 | ||||
-rw-r--r-- | ui/gui_status_handler.cpp | 17 | ||||
-rw-r--r-- | ui/gui_status_handler.h | 2 | ||||
-rw-r--r-- | ui/main_dlg.cpp | 297 | ||||
-rw-r--r-- | ui/main_dlg.h | 2 | ||||
-rw-r--r-- | ui/progress_indicator.cpp | 411 | ||||
-rw-r--r-- | ui/small_dlgs.cpp | 6 | ||||
-rw-r--r-- | ui/tree_view.cpp | 15 |
18 files changed, 720 insertions, 312 deletions
diff --git a/ui/IFileDialog_Vista/IFileDialog_Vista.vcxproj b/ui/IFileDialog_Vista/IFileDialog_Vista.vcxproj index f3dfcd23..1b84eade 100644 --- a/ui/IFileDialog_Vista/IFileDialog_Vista.vcxproj +++ b/ui/IFileDialog_Vista/IFileDialog_Vista.vcxproj @@ -19,6 +19,7 @@ </ProjectConfiguration> </ItemGroup> <ItemGroup> + <ClCompile Include="..\..\zen\debug_memory_leaks.cpp" /> <ClCompile Include="dll_main.cpp" /> <ClCompile Include="ifile_dialog.cpp" /> </ItemGroup> @@ -117,6 +118,7 @@ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX86</TargetMachine> <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> @@ -150,7 +152,9 @@ </ProfileGuidedDatabase> <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX64</TargetMachine> - <AdditionalLibraryDirectories></AdditionalLibraryDirectories> + <AdditionalLibraryDirectories> + </AdditionalLibraryDirectories> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> @@ -171,6 +175,7 @@ <DisableSpecificWarnings>4100</DisableSpecificWarnings> <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories> + <SmallerTypeCheck>true</SmallerTypeCheck> </ClCompile> <Link> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> @@ -185,6 +190,7 @@ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX86</TargetMachine> <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> @@ -208,6 +214,7 @@ <DisableSpecificWarnings>4100</DisableSpecificWarnings> <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories> + <SmallerTypeCheck>true</SmallerTypeCheck> </ClCompile> <Link> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> @@ -221,7 +228,9 @@ </ProfileGuidedDatabase> <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX64</TargetMachine> - <AdditionalLibraryDirectories></AdditionalLibraryDirectories> + <AdditionalLibraryDirectories> + </AdditionalLibraryDirectories> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> diff --git a/ui/Taskbar_Seven/Taskbar_Seven.vcxproj b/ui/Taskbar_Seven/Taskbar_Seven.vcxproj index 37786768..a979a1a2 100644 --- a/ui/Taskbar_Seven/Taskbar_Seven.vcxproj +++ b/ui/Taskbar_Seven/Taskbar_Seven.vcxproj @@ -96,8 +96,9 @@ <WarningLevel>Level4</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>EditAndContinue</DebugInformationFormat> - <DisableSpecificWarnings>4100</DisableSpecificWarnings> + <DisableSpecificWarnings>4100,4996</DisableSpecificWarnings> <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories> + <SmallerTypeCheck>true</SmallerTypeCheck> </ClCompile> <Link> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> @@ -109,6 +110,7 @@ </ProfileGuidedDatabase> <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX86</TargetMachine> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> @@ -129,8 +131,9 @@ <WarningLevel>Level4</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> - <DisableSpecificWarnings>4100</DisableSpecificWarnings> + <DisableSpecificWarnings>4100,4996</DisableSpecificWarnings> <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories> + <SmallerTypeCheck>true</SmallerTypeCheck> </ClCompile> <Link> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> @@ -142,6 +145,7 @@ </ProfileGuidedDatabase> <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX64</TargetMachine> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> @@ -159,7 +163,7 @@ <WarningLevel>Level4</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> - <DisableSpecificWarnings>4100</DisableSpecificWarnings> + <DisableSpecificWarnings>4100,4996</DisableSpecificWarnings> <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories> </ClCompile> @@ -175,6 +179,7 @@ </ProfileGuidedDatabase> <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX86</TargetMachine> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> @@ -195,7 +200,7 @@ <WarningLevel>Level4</WarningLevel> <SuppressStartupBanner>true</SuppressStartupBanner> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> - <DisableSpecificWarnings>4100</DisableSpecificWarnings> + <DisableSpecificWarnings>4100,4996</DisableSpecificWarnings> <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed> <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories> </ClCompile> @@ -211,9 +216,11 @@ </ProfileGuidedDatabase> <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX64</TargetMachine> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemGroup> + <ClCompile Include="..\..\zen\debug_memory_leaks.cpp" /> <ClCompile Include="dll_main.cpp"> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> </PrecompiledHeader> diff --git a/ui/batch_config.cpp b/ui/batch_config.cpp index d5aa7bc2..0463bd80 100644 --- a/ui/batch_config.cpp +++ b/ui/batch_config.cpp @@ -570,7 +570,7 @@ void BatchDialog::loadBatchFile(const std::vector<wxString>& filenames) const wxString activeFile = filenames.size() == 1 ? filenames[0] : wxString(); if (activeFile.empty()) - SetTitle(_("Create a batch job")); + SetTitle(_("Save as batch job")); else SetTitle(activeFile); diff --git a/ui/batch_status_handler.cpp b/ui/batch_status_handler.cpp index b33b0d80..5a32e545 100644 --- a/ui/batch_status_handler.cpp +++ b/ui/batch_status_handler.cpp @@ -38,7 +38,7 @@ private: virtual std::shared_ptr<TraverseCallback> onDir (const Zchar* shortName, const Zstring& fullName) { return nullptr; } //DON'T traverse into subdirs virtual HandleLink onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) { return LINK_SKIP; } - virtual HandleError onError (const std::wstring& msg) { return ON_ERROR_IGNORE; } //errors are not really critical in this context + virtual HandleError onError (const std::wstring& msg) { assert(false); return ON_ERROR_IGNORE; } //errors are not really critical in this context const Zstring prefix_; std::vector<Zstring>& logfiles_; @@ -100,6 +100,7 @@ BatchStatusHandler::BatchStatusHandler(bool showProgress, const TimeComp& timeStamp, const Zstring& logfileDirectory, //may be empty int logfilesCountLimit, + size_t lastSyncsLogFileSizeMax, const xmlAccess::OnError handleError, const SwitchToGui& switchBatchToGui, //functionality to change from batch mode to GUI mode FfsReturnCode& returnCode, @@ -108,6 +109,7 @@ BatchStatusHandler::BatchStatusHandler(bool showProgress, switchBatchToGui_(switchBatchToGui), showFinalResults(showProgress), //=> exit immediately or wait when finished switchToGuiRequested(false), + lastSyncsLogFileSizeMax_(lastSyncsLogFileSizeMax), handleError_(handleError), returnCode_(returnCode), syncStatusFrame(*this, *this, nullptr, showProgress, jobName, execWhenFinished, execFinishedHistory), @@ -129,7 +131,7 @@ BatchStatusHandler::BatchStatusHandler(bool showProgress, totalTime.Start(); //measure total time //if (logFile) - // ::wxSetEnv(L"logfile", utfCvrtTo<wxString>(logFile->getFilename())); -> requires a command line interpreter to take advantage of + // ::wxSetEnv(L"logfile", utfCvrtTo<wxString>(logFile->getFilename())); } @@ -168,24 +170,35 @@ BatchStatusHandler::~BatchStatusHandler() errorLog.logMsg(finalStatus, TYPE_INFO); } - const Utf8String logStream = generateLogStream(errorLog, jobName_, finalStatus, - getObjectsCurrent(PHASE_SYNCHRONIZING), getDataCurrent(PHASE_SYNCHRONIZING), - getObjectsTotal (PHASE_SYNCHRONIZING), getDataTotal (PHASE_SYNCHRONIZING), totalTime.Time() / 1000); + const SummaryInfo summary = + { + jobName_, + finalStatus, + getObjectsCurrent(PHASE_SYNCHRONIZING), getDataCurrent(PHASE_SYNCHRONIZING), + getObjectsTotal (PHASE_SYNCHRONIZING), getDataTotal (PHASE_SYNCHRONIZING), + totalTime.Time() / 1000 + }; + //print the results list: logfile if (logFile.get()) { + //saving log file below may take a *long* time, so report (without logging) try { - if (!logStream.empty()) - logFile->write(&*logStream.begin(), logStream.size()); //throw FileError + reportStatus(replaceCpy(_("Saving log file %x"), L"%x", fmtFileName(logFile->getFilename()))); //throw? + forceUiRefresh(); // + } + catch (...) {} + try + { + saveLogToFile(summary, errorLog, *logFile); //throw FileError } catch (FileError&) {} - logFile.reset(); //close file now: user may do something with it in "on completion" } try { - saveToLastSyncsLog(logStream); //throw FileError + saveToLastSyncsLog(summary, errorLog, lastSyncsLogFileSizeMax_); //throw FileError } catch (FileError&) {} diff --git a/ui/batch_status_handler.h b/ui/batch_status_handler.h index d83f8acc..884f22e5 100644 --- a/ui/batch_status_handler.h +++ b/ui/batch_status_handler.h @@ -29,6 +29,7 @@ public: const zen::TimeComp& timeStamp, const Zstring& logfileDirectory, int logfilesCountLimit, //0: logging inactive; < 0: no limit + size_t lastSyncsLogFileSizeMax, const xmlAccess::OnError handleError, const zen::SwitchToGui& switchBatchToGui, //functionality to change from batch mode to GUI mode zen::FfsReturnCode& returnCode, @@ -51,6 +52,7 @@ private: const zen::SwitchToGui& switchBatchToGui_; //functionality to change from batch mode to GUI mode bool showFinalResults; bool switchToGuiRequested; + const size_t lastSyncsLogFileSizeMax_; xmlAccess::OnError handleError_; zen::ErrorLog errorLog; //list of non-resolved errors and warnings zen::FfsReturnCode& returnCode_; diff --git a/ui/column_attr.h b/ui/column_attr.h index 23cff92c..8152c01c 100644 --- a/ui/column_attr.h +++ b/ui/column_attr.h @@ -44,8 +44,8 @@ std::vector<ColumnAttributeRim> getDefaultColumnAttributesLeft() attr.push_back(ColumnAttributeRim(COL_TYPE_DIRECTORY, 200, 0, false)); attr.push_back(ColumnAttributeRim(COL_TYPE_REL_PATH, 200, 0, true)); attr.push_back(ColumnAttributeRim(COL_TYPE_FILENAME, -280, 1, true)); //stretch to full width and substract sum of fixed size widths! - attr.push_back(ColumnAttributeRim(COL_TYPE_SIZE, 80, 0, true)); attr.push_back(ColumnAttributeRim(COL_TYPE_DATE, 112, 0, false)); + attr.push_back(ColumnAttributeRim(COL_TYPE_SIZE, 80, 0, true)); attr.push_back(ColumnAttributeRim(COL_TYPE_EXTENSION, 60, 0, false)); return attr; } @@ -57,8 +57,8 @@ std::vector<ColumnAttributeRim> getDefaultColumnAttributesRight() attr.push_back(ColumnAttributeRim(COL_TYPE_DIRECTORY, 200, 0, false)); attr.push_back(ColumnAttributeRim(COL_TYPE_REL_PATH, 200, 0, false)); //already shown on left side attr.push_back(ColumnAttributeRim(COL_TYPE_FILENAME, -80, 1, true)); //stretch to full width and substract sum of fixed size widths! - attr.push_back(ColumnAttributeRim(COL_TYPE_SIZE, 80, 0, true)); attr.push_back(ColumnAttributeRim(COL_TYPE_DATE, 112, 0, false)); + attr.push_back(ColumnAttributeRim(COL_TYPE_SIZE, 80, 0, true)); attr.push_back(ColumnAttributeRim(COL_TYPE_EXTENSION, 60, 0, false)); return attr; } @@ -72,7 +72,6 @@ enum ColumnTypeMiddle COL_TYPE_BORDER }; - //------------------------------------------------------------------ enum ColumnTypeNavi diff --git a/ui/custom_grid.cpp b/ui/custom_grid.cpp index e7152905..975dca5a 100644 --- a/ui/custom_grid.cpp +++ b/ui/custom_grid.cpp @@ -11,6 +11,7 @@ #include <zen/file_error.h> #include <zen/basic_math.h> #include <zen/format_unit.h> +#include <zen/scope_guard.h> #include <wx+/tooltip.h> #include <wx+/string_conv.h> #include <wx+/rtl.h> @@ -71,7 +72,7 @@ void refreshCell(Grid& grid, size_t row, ColumnType colType) } -std::pair<ptrdiff_t, ptrdiff_t> getVisibleRows(Grid& grid) //returns range [from, to) +std::pair<ptrdiff_t, ptrdiff_t> getVisibleRows(const Grid& grid) //returns range [from, to) { const wxSize clientSize = grid.getMainWin().GetClientSize(); if (clientSize.GetHeight() > 0) @@ -79,15 +80,15 @@ std::pair<ptrdiff_t, ptrdiff_t> getVisibleRows(Grid& grid) //returns range [from wxPoint topLeft = grid.CalcUnscrolledPosition(wxPoint(0, 0)); wxPoint bottom = grid.CalcUnscrolledPosition(wxPoint(0, clientSize.GetHeight() - 1)); - ptrdiff_t rowFrom = grid.getRowAtPos(topLeft.y); //returns < 0 if column not found; absolute coordinates! + const ptrdiff_t rowCount = grid.getRowCount(); + const ptrdiff_t rowFrom = grid.getRowAtPos(topLeft.y); //return -1 for invalid position, rowCount if out of range if (rowFrom >= 0) { - ptrdiff_t rowEnd = grid.getRowAtPos(bottom.y); //returns < 0 if column not found; absolute coordinates! - if (rowEnd < 0) - rowEnd = grid.getRowCount(); + const ptrdiff_t rowTo = grid.getRowAtPos(bottom.y); + if (0 <= rowTo && rowTo < rowCount) + return std::make_pair(rowFrom, rowTo + 1); else - ++rowEnd; - return std::make_pair(rowFrom, rowEnd); + return std::make_pair(rowFrom, rowCount); } } return std::make_pair(0, 0); @@ -448,7 +449,6 @@ private: static const int CELL_BORDER = 2; - virtual void renderCell(Grid& grid, wxDC& dc, const wxRect& rect, size_t row, ColumnType colType) { wxRect rectTmp = rect; @@ -784,20 +784,20 @@ public: if (static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE && row < refGrid().getRowCount()) { - refGrid().clearSelection(); - dragSelection.reset(new std::pair<size_t, BlockPosition>(row, mousePosToBlock(clientPos, row))); + refGrid().clearSelection(false); //don't emit event, prevent recursion! + dragSelection = make_unique<std::pair<size_t, BlockPosition>>(row, mousePosToBlock(clientPos, row)); } } - void onSelectEnd(size_t rowFrom, size_t rowTo) //we cannot reuse row from "onSelectBegin": rowFrom and rowTo may be different if user is holding shift + void onSelectEnd(size_t rowFirst, size_t rowLast) //we cannot reuse row from "onSelectBegin": if user is holding shift, this may now be in the middle of the range! { - refGrid().clearSelection(); + refGrid().clearSelection(false); //don't emit event, prevent recursion! //issue custom event if (dragSelection) { - if (rowFrom < refGrid().getRowCount() && - rowTo < refGrid().getRowCount()) //row is -1 on capture lost! + if (rowFirst < rowLast && //may be empty? probably not in this context + rowLast <= refGrid().getRowCount()) { if (wxEvtHandler* evtHandler = refGrid().GetEventHandler()) switch (dragSelection->second) @@ -807,25 +807,25 @@ public: const FileSystemObject* fsObj = getRawData(dragSelection->first); const bool setIncluded = fsObj ? !fsObj->isActive() : true; - CheckRowsEvent evt(rowFrom, rowTo, setIncluded); + CheckRowsEvent evt(rowFirst, rowLast, setIncluded); evtHandler->ProcessEvent(evt); } break; case BLOCKPOS_LEFT: { - SyncDirectionEvent evt(rowFrom, rowTo, SYNC_DIR_LEFT); + SyncDirectionEvent evt(rowFirst, rowLast, SYNC_DIR_LEFT); evtHandler->ProcessEvent(evt); } break; case BLOCKPOS_MIDDLE: { - SyncDirectionEvent evt(rowFrom, rowTo, SYNC_DIR_NONE); + SyncDirectionEvent evt(rowFirst, rowLast, SYNC_DIR_NONE); evtHandler->ProcessEvent(evt); } break; case BLOCKPOS_RIGHT: { - SyncDirectionEvent evt(rowFrom, rowTo, SYNC_DIR_RIGHT); + SyncDirectionEvent evt(rowFirst, rowLast, SYNC_DIR_RIGHT); evtHandler->ProcessEvent(evt); } break; @@ -844,12 +844,13 @@ public: } else { - if (static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE) + if (static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE && + row < refGrid().getRowCount()) { if (highlight) //refresh old highlight refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE)); - highlight.reset(new std::pair<size_t, BlockPosition>(row, mousePosToBlock(clientPos, row))); + highlight = make_unique<std::pair<size_t, BlockPosition>>(row, mousePosToBlock(clientPos, row)); refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE)); //show custom tooltip @@ -1193,7 +1194,8 @@ public: GridDataMiddle& provMiddle, GridDataRight& provRight) : gridL_(gridL), gridC_(gridC), gridR_(gridR), scrollMaster(nullptr), - provLeft_(provLeft), provMiddle_(provMiddle), provRight_(provRight) + provLeft_(provLeft), provMiddle_(provMiddle), provRight_(provRight), + scrollbarUpdatePending(false) { gridL_.Connect(EVENT_GRID_COL_RESIZE, GridColumnResizeEventHandler(GridEventManager::onResizeColumnL), nullptr, this); gridR_.Connect(EVENT_GRID_COL_RESIZE, GridColumnResizeEventHandler(GridEventManager::onResizeColumnR), nullptr, this); @@ -1238,6 +1240,8 @@ public: Connect(EVENT_ALIGN_SCROLLBARS, wxEventHandler(GridEventManager::onAlignScrollBars), NULL, this); } + ~GridEventManager() { assert(!scrollbarUpdatePending); } + private: void onCenterSelectBegin(GridClickEvent& event) { @@ -1248,15 +1252,14 @@ private: void onCenterSelectEnd(GridRangeSelectEvent& event) { - if (event.positive_) //we do NOT want to react on GridRangeSelectEvent() within Grid::clearSelectionAll() directly following right mouse click! - provMiddle_.onSelectEnd(event.rowFrom_, event.rowTo_); + provMiddle_.onSelectEnd(event.rowFirst_, event.rowLast_); event.Skip(); } void onCenterMouseMovement(wxMouseEvent& event) { const wxPoint& topLeftAbs = gridC_.CalcUnscrolledPosition(event.GetPosition()); - const int row = gridC_.getRowAtPos(topLeftAbs.y); //returns < 0 if column not found; absolute coordinates! + const ptrdiff_t row = gridC_.getRowAtPos(topLeftAbs.y); //return -1 for invalid position, rowCount if one past the end if (auto colInfo = gridC_.getColumnAtPos(topLeftAbs.x)) //(column type, component position) { //redirect mouse movement to middle grid component @@ -1277,7 +1280,7 @@ private: void onGridSelection(const Grid& grid, Grid& other) { if (!wxGetKeyState(WXK_CONTROL)) //clear other grid unless user is holding CTRL - other.clearSelection(); + other.clearSelection(false); //don't emit event, prevent recursion! } void onKeyDownL(wxKeyEvent& event) { onKeyDown(event, gridL_); } @@ -1397,13 +1400,22 @@ private: //harmonize placement of horizontal scrollbar to avoid grids getting out of sync! //since this affects the grid that is currently repainted as well, we do work asynchronously! //avoids at least this problem: remaining graphics artifact when changing from Grid::SB_SHOW_ALWAYS to Grid::SB_SHOW_NEVER at location of old scrollbar (Windows only) - wxCommandEvent alignEvent(EVENT_ALIGN_SCROLLBARS); - AddPendingEvent(alignEvent); //waits until next idle event - may take up to a second if the app is busy on wxGTK! + + //perf note: send one async event at most, else they may accumulate and create perf issues, see grid.cpp + if (!scrollbarUpdatePending) + { + scrollbarUpdatePending = true; + wxCommandEvent alignEvent(EVENT_ALIGN_SCROLLBARS); + AddPendingEvent(alignEvent); //waits until next idle event - may take up to a second if the app is busy on wxGTK! + } } void onAlignScrollBars(wxEvent& event) { - auto needsHorizontalScrollbars = [](Grid& grid) -> bool + ZEN_ON_SCOPE_EXIT(scrollbarUpdatePending = false); + assert(scrollbarUpdatePending); + + auto needsHorizontalScrollbars = [](const Grid& grid) -> bool { const wxWindow& mainWin = grid.getMainWin(); return mainWin.GetVirtualSize().GetWidth() > mainWin.GetClientSize().GetWidth(); @@ -1433,6 +1445,8 @@ private: GridDataLeft& provLeft_; GridDataMiddle& provMiddle_; GridDataRight& provRight_; + + bool scrollbarUpdatePending; }; } @@ -1541,6 +1555,7 @@ private: }; } + void gridview::setupIcons(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, bool show, IconBuffer::IconSize sz) { auto* provLeft = dynamic_cast<GridDataLeft*>(gridLeft .getDataProvider()); @@ -1548,7 +1563,7 @@ void gridview::setupIcons(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, boo if (provLeft && provRight) { - int newRowHeight = 0; + int iconHeight = 0; if (show) { auto iconMgr = std::make_shared<IconManager>(sz); @@ -1556,14 +1571,17 @@ void gridview::setupIcons(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, boo provLeft ->setIconManager(iconMgr); provRight->setIconManager(iconMgr); - newRowHeight = iconMgr->iconBuffer.getSize() + 1; //+ 1 for line between rows + iconHeight = iconMgr->iconBuffer.getSize(); } else { provLeft ->setIconManager(nullptr); provRight->setIconManager(nullptr); - newRowHeight = IconBuffer(IconBuffer::SIZE_SMALL).getSize() + 1; //+ 1 for line between rows + iconHeight = IconBuffer(IconBuffer::SIZE_SMALL).getSize(); } + + const int newRowHeight = std::max(iconHeight, gridLeft.getMainWin().GetCharHeight()) + 1; //add some space + gridLeft .setRowHeight(newRowHeight); gridCenter.setRowHeight(newRowHeight); gridRight .setRowHeight(newRowHeight); diff --git a/ui/custom_grid.h b/ui/custom_grid.h index b5a4cce1..6381c8c0 100644 --- a/ui/custom_grid.h +++ b/ui/custom_grid.h @@ -48,22 +48,22 @@ extern const wxEventType EVENT_GRID_SYNC_DIRECTION; struct CheckRowsEvent : public wxCommandEvent { - CheckRowsEvent(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool setIncluded) : wxCommandEvent(EVENT_GRID_CHECK_ROWS), rowFrom_(rowFrom), rowTo_(rowTo), setIncluded_(setIncluded) {} + CheckRowsEvent(size_t rowFirst, size_t rowLast, bool setIncluded) : wxCommandEvent(EVENT_GRID_CHECK_ROWS), rowFirst_(rowFirst), rowLast_(rowLast), setIncluded_(setIncluded) { assert(rowFirst <= rowLast); } virtual wxEvent* Clone() const { return new CheckRowsEvent(*this); } - const ptrdiff_t rowFrom_; - const ptrdiff_t rowTo_; + const size_t rowFirst_; //selected range: [rowFirst_, rowLast_) + const size_t rowLast_; //range is empty when clearing selection const bool setIncluded_; }; struct SyncDirectionEvent : public wxCommandEvent { - SyncDirectionEvent(ptrdiff_t rowFrom, ptrdiff_t rowTo, SyncDirection direction) : wxCommandEvent(EVENT_GRID_SYNC_DIRECTION), rowFrom_(rowFrom), rowTo_(rowTo), direction_(direction) {} + SyncDirectionEvent(size_t rowFirst, size_t rowLast, SyncDirection direction) : wxCommandEvent(EVENT_GRID_SYNC_DIRECTION), rowFirst_(rowFirst), rowLast_(rowLast), direction_(direction) { assert(rowFirst <= rowLast); } virtual wxEvent* Clone() const { return new SyncDirectionEvent(*this); } - const ptrdiff_t rowFrom_; - const ptrdiff_t rowTo_; + const size_t rowFirst_; //see CheckRowsEvent + const size_t rowLast_; // const SyncDirection direction_; }; diff --git a/ui/dir_name.cpp b/ui/dir_name.cpp index 7d4f7a31..a1a00d74 100644 --- a/ui/dir_name.cpp +++ b/ui/dir_name.cpp @@ -175,7 +175,7 @@ void DirectoryName<NameControl>::OnSelectDir(wxCommandEvent& event) } } - //wxDirDialog internally uses lame looking SHBrowseForFolder(); Better use IFileDialog() instead! (remembers size and position!) + //wxDirDialog internally uses lame-looking SHBrowseForFolder(); we better use IFileDialog() instead! (remembers size and position!) std::unique_ptr<wxString> newFolder; #ifdef FFS_WIN if (vistaOrLater()) diff --git a/ui/gui_generated.cpp b/ui/gui_generated.cpp index d132d748..6619b646 100644 --- a/ui/gui_generated.cpp +++ b/ui/gui_generated.cpp @@ -41,6 +41,9 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const m_menuItemSaveAs = new wxMenuItem( m_menuFile, wxID_SAVEAS, wxString( _("Save &as...") ) , wxEmptyString, wxITEM_NORMAL ); m_menuFile->Append( m_menuItemSaveAs ); + m_menuItem7 = new wxMenuItem( m_menuFile, wxID_ANY, wxString( _("Save as &batch job...") ) , wxEmptyString, wxITEM_NORMAL ); + m_menuFile->Append( m_menuItem7 ); + m_menuFile->AppendSeparator(); m_menuItem10 = new wxMenuItem( m_menuFile, wxID_ANY, wxString( _("1. &Compare") ) + wxT('\t') + wxT("F5"), wxEmptyString, wxITEM_NORMAL ); @@ -76,9 +79,6 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const m_menuItemGlobSett = new wxMenuItem( m_menuAdvanced, wxID_PREFERENCES, wxString( _("&Global settings...") ) , wxEmptyString, wxITEM_NORMAL ); m_menuAdvanced->Append( m_menuItemGlobSett ); - m_menuItem7 = new wxMenuItem( m_menuAdvanced, wxID_ANY, wxString( _("&Create batch job...") ) , wxEmptyString, wxITEM_NORMAL ); - m_menuAdvanced->Append( m_menuItem7 ); - wxMenuItem* m_menuItem5; m_menuItem5 = new wxMenuItem( m_menuAdvanced, wxID_ANY, wxString( _("&Export file list...") ) , wxEmptyString, wxITEM_NORMAL ); m_menuAdvanced->Append( m_menuItem5 ); @@ -346,7 +346,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const bSizer1601->Add( bSizer91, 0, wxEXPAND, 5 ); m_scrolledWindowFolderPairs = new wxScrolledWindow( m_panelDirectoryPairs, wxID_ANY, wxDefaultPosition, wxSize( -1,-1 ), wxHSCROLL|wxVSCROLL ); - m_scrolledWindowFolderPairs->SetScrollRate( 5, 5 ); + m_scrolledWindowFolderPairs->SetScrollRate( 10, 10 ); m_scrolledWindowFolderPairs->SetMinSize( wxSize( -1,0 ) ); bSizerAddFolderPairs = new wxBoxSizer( wxVERTICAL ); @@ -539,7 +539,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const bSizer151 = new wxBoxSizer( wxHORIZONTAL ); m_bpButtonLoad = new wxBitmapButton( m_panelConfig, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 42,42 ), wxBU_AUTODRAW ); - m_bpButtonLoad->SetToolTip( _("Open...") ); + m_bpButtonLoad->SetToolTip( _("Open") ); bSizer151->Add( m_bpButtonLoad, 0, wxALIGN_CENTER_VERTICAL, 5 ); @@ -548,6 +548,11 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const bSizer151->Add( m_bpButtonSave, 0, wxALIGN_CENTER_VERTICAL, 5 ); + m_bpButtonBatchJob = new wxBitmapButton( m_panelConfig, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 42,42 ), wxBU_AUTODRAW ); + m_bpButtonBatchJob->SetToolTip( _("Save as batch job") ); + + bSizer151->Add( m_bpButtonBatchJob, 0, wxALIGN_CENTER_VERTICAL, 5 ); + bSizerConfig->Add( bSizer151, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); @@ -846,11 +851,11 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const this->Connect( m_menuItemLoad->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnConfigLoad ) ); this->Connect( m_menuItemSave->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnConfigSave ) ); this->Connect( m_menuItemSaveAs->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnConfigSaveAs ) ); + this->Connect( m_menuItem7->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuBatchJob ) ); this->Connect( m_menuItem10->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnCompare ) ); this->Connect( m_menuItem11->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnStartSync ) ); this->Connect( m_menuItem4->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuQuit ) ); this->Connect( m_menuItemGlobSett->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuGlobalSettings ) ); - this->Connect( m_menuItem7->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuBatchJob ) ); 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 ) ); @@ -864,6 +869,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const m_bpButtonSwapSides->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSwapSides ), NULL, this ); m_bpButtonLoad->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigLoad ), NULL, this ); m_bpButtonSave->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigSave ), NULL, this ); + m_bpButtonBatchJob->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnMenuBatchJob ), NULL, this ); m_listBoxHistory->Connect( wxEVT_CHAR, wxKeyEventHandler( MainDialogGenerated::OnCfgHistoryKeyEvent ), NULL, this ); m_listBoxHistory->Connect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnLoadFromHistory ), NULL, this ); m_listBoxHistory->Connect( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, wxCommandEventHandler( MainDialogGenerated::OnLoadFromHistoryDoubleClick ), NULL, this ); @@ -893,11 +899,11 @@ MainDialogGenerated::~MainDialogGenerated() this->Disconnect( wxID_OPEN, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnConfigLoad ) ); this->Disconnect( wxID_SAVE, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnConfigSave ) ); this->Disconnect( wxID_SAVEAS, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnConfigSaveAs ) ); + this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuBatchJob ) ); this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnCompare ) ); this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnStartSync ) ); this->Disconnect( wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuQuit ) ); this->Disconnect( wxID_PREFERENCES, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuGlobalSettings ) ); - this->Disconnect( wxID_ANY, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnMenuBatchJob ) ); 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 ) ); @@ -911,6 +917,7 @@ MainDialogGenerated::~MainDialogGenerated() m_bpButtonSwapSides->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnSwapSides ), NULL, this ); m_bpButtonLoad->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigLoad ), NULL, this ); m_bpButtonSave->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigSave ), NULL, this ); + m_bpButtonBatchJob->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnMenuBatchJob ), NULL, this ); m_listBoxHistory->Disconnect( wxEVT_CHAR, wxKeyEventHandler( MainDialogGenerated::OnCfgHistoryKeyEvent ), NULL, this ); m_listBoxHistory->Disconnect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( MainDialogGenerated::OnLoadFromHistory ), NULL, this ); m_listBoxHistory->Disconnect( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, wxCommandEventHandler( MainDialogGenerated::OnLoadFromHistoryDoubleClick ), NULL, this ); @@ -1184,31 +1191,31 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS wxBoxSizer* bSizer87; bSizer87 = new wxBoxSizer( wxHORIZONTAL ); - m_panel8 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER|wxTAB_TRAVERSAL ); + m_panel8 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panel8->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_3DLIGHT ) ); wxBoxSizer* bSizer72; bSizer72 = new wxBoxSizer( wxHORIZONTAL ); - m_bitmap27 = new wxStaticBitmap( m_panel8, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,40 ), 0 ); + m_bitmap27 = new wxStaticBitmap( m_panel8, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 ); bSizer72->Add( m_bitmap27, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); m_staticText56 = new wxStaticText( m_panel8, wxID_ANY, _("Batch job"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText56->Wrap( -1 ); - m_staticText56->SetFont( wxFont( 14, 70, 90, 92, false, wxEmptyString ) ); + m_staticText56->SetFont( wxFont( 12, 70, 90, 92, false, wxEmptyString ) ); m_staticText56->SetForegroundColour( wxColour( 0, 0, 0 ) ); bSizer72->Add( m_staticText56, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + m_staticText44 = new wxStaticText( m_panel8, wxID_ANY, _("Create a batch file to automate synchronization. Double-click this file or schedule in your system's task planner: FreeFileSync.exe <job name>.ffs_batch"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText44->Wrap( 480 ); + bSizer72->Add( m_staticText44, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); + m_panel8->SetSizer( bSizer72 ); m_panel8->Layout(); bSizer72->Fit( m_panel8 ); - bSizer87->Add( m_panel8, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - - m_staticText44 = new wxStaticText( this, wxID_ANY, _("Create a batch file to automate synchronization. Double-click this file or schedule in your system's task planner: FreeFileSync.exe <job name>.ffs_batch"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText44->Wrap( 480 ); - bSizer87->Add( m_staticText44, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); + bSizer87->Add( m_panel8, 1, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5 ); m_bpButtonHelp = new wxBitmapButton( this, wxID_HELP, wxNullBitmap, wxDefaultPosition, wxSize( 42,42 ), wxBU_AUTODRAW ); m_bpButtonHelp->SetToolTip( _("Help") ); @@ -1216,7 +1223,7 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer87->Add( m_bpButtonHelp, 0, wxALIGN_CENTER_VERTICAL, 5 ); - bSizer54->Add( bSizer87, 0, wxALIGN_CENTER_HORIZONTAL|wxTOP|wxRIGHT|wxLEFT, 5 ); + bSizer54->Add( bSizer87, 0, wxALIGN_CENTER_HORIZONTAL|wxEXPAND, 5 ); m_notebook1 = new wxNotebook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); m_panelOverview = new wxPanel( m_notebook1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); @@ -1305,7 +1312,7 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer120->Add( 0, 5, 0, 0, 5 ); m_scrolledWindow6 = new wxScrolledWindow( m_panelOverview, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); - m_scrolledWindow6->SetScrollRate( 5, 5 ); + m_scrolledWindow6->SetScrollRate( 10, 10 ); wxBoxSizer* bSizer141; bSizer141 = new wxBoxSizer( wxVERTICAL ); @@ -1533,7 +1540,7 @@ BatchDlgGenerated::BatchDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer117->Fit( m_panelBatchSettings ); m_notebook1->AddPage( m_panelBatchSettings, _("Batch settings"), false ); - bSizer54->Add( m_notebook1, 1, wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 ); + bSizer54->Add( m_notebook1, 1, wxEXPAND|wxRIGHT|wxLEFT, 5 ); wxBoxSizer* bSizer68; bSizer68 = new wxBoxSizer( wxHORIZONTAL ); @@ -2505,6 +2512,12 @@ LogControlGenerated::LogControlGenerated( wxWindow* parent, wxWindowID id, const { this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); + wxBoxSizer* bSizer179; + bSizer179 = new wxBoxSizer( wxVERTICAL ); + + m_staticline12 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + bSizer179->Add( m_staticline12, 0, wxEXPAND, 5 ); + wxBoxSizer* bSizer153; bSizer153 = new wxBoxSizer( wxHORIZONTAL ); @@ -2526,14 +2539,17 @@ LogControlGenerated::LogControlGenerated( wxWindow* parent, wxWindowID id, const m_staticline13 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL ); bSizer153->Add( m_staticline13, 0, wxEXPAND, 5 ); - m_textCtrlInfo = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1,-1 ), wxTE_DONTWRAP|wxTE_MULTILINE|wxTE_READONLY|wxNO_BORDER ); - m_textCtrlInfo->SetMaxLength( 0 ); - bSizer153->Add( m_textCtrlInfo, 1, wxEXPAND|wxALIGN_CENTER_VERTICAL, 5 ); + m_gridMessages = new zen::Grid( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_gridMessages->SetScrollRate( 5, 5 ); + bSizer153->Add( m_gridMessages, 1, wxEXPAND|wxALIGN_CENTER_VERTICAL, 5 ); + + bSizer179->Add( bSizer153, 1, wxEXPAND, 5 ); - this->SetSizer( bSizer153 ); + + this->SetSizer( bSizer179 ); this->Layout(); - bSizer153->Fit( this ); + bSizer179->Fit( this ); // Connect Events m_bpButtonErrors->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( LogControlGenerated::OnErrors ), NULL, this ); @@ -2580,7 +2596,7 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS m_build = new wxStaticText( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); m_build->Wrap( -1 ); - bSizer53->Add( m_build, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + bSizer53->Add( m_build, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); bSizer53->Add( 0, 5, 0, 0, 5 ); @@ -2730,7 +2746,7 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer53->Add( m_panel40, 0, wxEXPAND|wxBOTTOM|wxALIGN_CENTER_HORIZONTAL, 5 ); m_scrolledWindowTranslators = new wxScrolledWindow( this, wxID_ANY, wxDefaultPosition, wxSize( -1,-1 ), wxDOUBLE_BORDER|wxHSCROLL|wxVSCROLL ); - m_scrolledWindowTranslators->SetScrollRate( 5, 5 ); + m_scrolledWindowTranslators->SetScrollRate( 10, 10 ); m_scrolledWindowTranslators->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); m_scrolledWindowTranslators->SetMinSize( wxSize( -1,180 ) ); @@ -3076,45 +3092,39 @@ FilterDlgGenerated::FilterDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer70 = new wxBoxSizer( wxHORIZONTAL ); bSizer70->SetMinSize( wxSize( 550,-1 ) ); - - bSizer70->Add( 0, 0, 1, wxEXPAND|wxALIGN_CENTER_VERTICAL, 5 ); - - m_panel8 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER|wxTAB_TRAVERSAL ); + m_panel8 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panel8->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_3DLIGHT ) ); wxBoxSizer* bSizer72; bSizer72 = new wxBoxSizer( wxHORIZONTAL ); - m_bitmap26 = new wxStaticBitmap( m_panel8, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,40 ), 0 ); + m_bitmap26 = new wxStaticBitmap( m_panel8, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 ); bSizer72->Add( m_bitmap26, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 ); m_staticTexHeader = new wxStaticText( m_panel8, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticTexHeader->Wrap( -1 ); - m_staticTexHeader->SetFont( wxFont( 14, 70, 90, 92, false, wxEmptyString ) ); + m_staticTexHeader->SetFont( wxFont( 12, 70, 90, 92, false, wxEmptyString ) ); m_staticTexHeader->SetForegroundColour( wxColour( 0, 0, 0 ) ); bSizer72->Add( m_staticTexHeader, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + m_staticText44 = new wxStaticText( m_panel8, wxID_ANY, _("Only files that match all filter settings will be synchronized.\nNote: File names must be relative to base directories!"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); + m_staticText44->Wrap( 480 ); + bSizer72->Add( m_staticText44, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); + m_panel8->SetSizer( bSizer72 ); m_panel8->Layout(); bSizer72->Fit( m_panel8 ); - bSizer70->Add( m_panel8, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); - - m_staticText44 = new wxStaticText( this, wxID_ANY, _("Only files that match all filter settings will be synchronized.\nNote: File names must be relative to base directories!"), wxDefaultPosition, wxSize( -1,-1 ), 0 ); - m_staticText44->Wrap( 480 ); - bSizer70->Add( m_staticText44, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); + bSizer70->Add( m_panel8, 1, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5 ); m_bpButtonHelp = new wxBitmapButton( this, wxID_HELP, wxNullBitmap, wxDefaultPosition, wxSize( 42,42 ), wxBU_AUTODRAW ); m_bpButtonHelp->SetToolTip( _("Help") ); - bSizer70->Add( m_bpButtonHelp, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 ); - + bSizer70->Add( m_bpButtonHelp, 0, wxALIGN_CENTER_VERTICAL, 5 ); - bSizer70->Add( 0, 0, 1, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 ); - - bSizer21->Add( bSizer70, 0, wxALIGN_CENTER_HORIZONTAL|wxTOP|wxRIGHT|wxLEFT|wxEXPAND, 5 ); + bSizer21->Add( bSizer70, 0, wxALIGN_CENTER_HORIZONTAL|wxEXPAND, 5 ); wxBoxSizer* bSizer159; bSizer159 = new wxBoxSizer( wxHORIZONTAL ); @@ -3253,7 +3263,7 @@ FilterDlgGenerated::FilterDlgGenerated( wxWindow* parent, wxWindowID id, const w bSizer159->Add( bSizer160, 0, wxEXPAND, 5 ); - bSizer21->Add( bSizer159, 1, wxALIGN_CENTER_HORIZONTAL|wxEXPAND|wxTOP|wxRIGHT|wxLEFT, 5 ); + bSizer21->Add( bSizer159, 1, wxALIGN_CENTER_HORIZONTAL|wxEXPAND|wxRIGHT|wxLEFT, 5 ); wxBoxSizer* bSizer22; bSizer22 = new wxBoxSizer( wxHORIZONTAL ); @@ -3323,33 +3333,27 @@ GlobalSettingsDlgGenerated::GlobalSettingsDlgGenerated( wxWindow* parent, wxWind wxBoxSizer* bSizer95; bSizer95 = new wxBoxSizer( wxVERTICAL ); - wxBoxSizer* bSizer86; - bSizer86 = new wxBoxSizer( wxHORIZONTAL ); - - m_panel8 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER|wxTAB_TRAVERSAL ); + m_panel8 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); m_panel8->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_3DLIGHT ) ); wxBoxSizer* bSizer72; bSizer72 = new wxBoxSizer( wxHORIZONTAL ); - m_bitmapSettings = new wxStaticBitmap( m_panel8, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,40 ), 0 ); + m_bitmapSettings = new wxStaticBitmap( m_panel8, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 ); bSizer72->Add( m_bitmapSettings, 0, wxRIGHT|wxLEFT|wxALIGN_CENTER_VERTICAL, 5 ); m_staticText56 = new wxStaticText( m_panel8, wxID_ANY, _("Global settings"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText56->Wrap( -1 ); - m_staticText56->SetFont( wxFont( 14, 70, 90, 92, false, wxEmptyString ) ); + m_staticText56->SetFont( wxFont( 12, 70, 90, 92, false, wxEmptyString ) ); m_staticText56->SetForegroundColour( wxColour( 0, 0, 0 ) ); - bSizer72->Add( m_staticText56, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + bSizer72->Add( m_staticText56, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); m_panel8->SetSizer( bSizer72 ); m_panel8->Layout(); bSizer72->Fit( m_panel8 ); - bSizer86->Add( m_panel8, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 ); - - - bSizer95->Add( bSizer86, 0, wxALIGN_CENTER_HORIZONTAL|wxTOP|wxRIGHT|wxLEFT, 5 ); + bSizer95->Add( m_panel8, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND|wxALL, 5 ); wxStaticBoxSizer* sbSizer23; sbSizer23 = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, wxEmptyString ), wxVERTICAL ); diff --git a/ui/gui_generated.h b/ui/gui_generated.h index 14e30f75..b14e0034 100644 --- a/ui/gui_generated.h +++ b/ui/gui_generated.h @@ -76,12 +76,12 @@ class MainDialogGenerated : public wxFrame wxMenuItem* m_menuItemLoad; wxMenuItem* m_menuItemSave; wxMenuItem* m_menuItemSaveAs; + wxMenuItem* m_menuItem7; wxMenuItem* m_menuItem10; wxMenuItem* m_menuItem11; wxMenu* m_menuAdvanced; wxMenu* m_menuLanguages; wxMenuItem* m_menuItemGlobSett; - wxMenuItem* m_menuItem7; wxMenu* m_menuHelp; wxMenuItem* m_menuItemManual; wxMenuItem* m_menuItemCheckVer; @@ -134,6 +134,7 @@ class MainDialogGenerated : public wxFrame wxBoxSizer* bSizerConfig; wxBitmapButton* m_bpButtonLoad; wxBitmapButton* m_bpButtonSave; + wxBitmapButton* m_bpButtonBatchJob; wxListBox* m_listBoxHistory; wxPanel* m_panelFilter; wxBitmapButton* m_bpButtonFilter; @@ -177,11 +178,11 @@ class MainDialogGenerated : public wxFrame virtual void OnConfigLoad( wxCommandEvent& event ) { event.Skip(); } virtual void OnConfigSave( wxCommandEvent& event ) { event.Skip(); } virtual void OnConfigSaveAs( wxCommandEvent& event ) { event.Skip(); } + virtual void OnMenuBatchJob( wxCommandEvent& event ) { event.Skip(); } virtual void OnCompare( wxCommandEvent& event ) { event.Skip(); } virtual void OnStartSync( wxCommandEvent& event ) { event.Skip(); } virtual void OnMenuQuit( wxCommandEvent& event ) { event.Skip(); } virtual void OnMenuGlobalSettings( wxCommandEvent& event ) { event.Skip(); } - virtual void OnMenuBatchJob( wxCommandEvent& event ) { event.Skip(); } virtual void OnMenuExportFileList( wxCommandEvent& event ) { event.Skip(); } virtual void OnShowHelp( wxCommandEvent& event ) { event.Skip(); } virtual void OnMenuCheckVersion( wxCommandEvent& event ) { event.Skip(); } @@ -366,7 +367,7 @@ class BatchDlgGenerated : public wxDialog wxBitmapButton* m_bpButtonAltSyncCfg; FolderHistoryBox* m_comboBoxLogfileDir; - BatchDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Create a batch job"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxMINIMIZE_BOX|wxRESIZE_BORDER ); + BatchDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Save as batch job"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxMINIMIZE_BOX|wxRESIZE_BORDER ); ~BatchDlgGenerated(); }; @@ -593,11 +594,12 @@ class LogControlGenerated : public wxPanel private: protected: + wxStaticLine* m_staticline12; ToggleButton* m_bpButtonErrors; ToggleButton* m_bpButtonWarnings; ToggleButton* m_bpButtonInfo; wxStaticLine* m_staticline13; - wxTextCtrl* m_textCtrlInfo; + zen::Grid* m_gridMessages; // Virtual event handlers, overide them in your derived class virtual void OnErrors( wxCommandEvent& event ) { event.Skip(); } diff --git a/ui/gui_status_handler.cpp b/ui/gui_status_handler.cpp index 120eab39..c8311811 100644 --- a/ui/gui_status_handler.cpp +++ b/ui/gui_status_handler.cpp @@ -175,16 +175,18 @@ void CompareStatusHandler::abortThisProcess() requestAbortion(); //just make sure... throw GuiAbortProcess(); } -//######################################################################################################## +//######################################################################################################## SyncStatusHandler::SyncStatusHandler(MainDialog* parentDlg, + size_t lastSyncsLogFileSizeMax, OnGuiError handleError, const std::wstring& jobName, const std::wstring& execWhenFinished, std::vector<std::wstring>& execFinishedHistory) : parentDlg_(parentDlg), syncStatusFrame(*this, *this, parentDlg, true, jobName, execWhenFinished, execFinishedHistory), + lastSyncsLogFileSizeMax_(lastSyncsLogFileSizeMax), handleError_(handleError), jobName_(jobName) { @@ -225,12 +227,17 @@ SyncStatusHandler::~SyncStatusHandler() errorLog.logMsg(finalStatus, TYPE_INFO); } - const Utf8String logStream = generateLogStream(errorLog, jobName_, finalStatus, - getObjectsCurrent(PHASE_SYNCHRONIZING), getDataCurrent(PHASE_SYNCHRONIZING), - getObjectsTotal (PHASE_SYNCHRONIZING), getDataTotal (PHASE_SYNCHRONIZING), totalTime.Time() / 1000); + const SummaryInfo summary = + { + jobName_, finalStatus, + getObjectsCurrent(PHASE_SYNCHRONIZING), getDataCurrent(PHASE_SYNCHRONIZING), + getObjectsTotal (PHASE_SYNCHRONIZING), getDataTotal (PHASE_SYNCHRONIZING), + totalTime.Time() / 1000 + }; + try { - saveToLastSyncsLog(logStream); //throw FileError + saveToLastSyncsLog(summary, errorLog, lastSyncsLogFileSizeMax_); //throw FileError } catch (FileError&) {} diff --git a/ui/gui_status_handler.h b/ui/gui_status_handler.h index e1940a2d..fb0dbf51 100644 --- a/ui/gui_status_handler.h +++ b/ui/gui_status_handler.h @@ -49,6 +49,7 @@ class SyncStatusHandler : public zen::StatusHandler { public: SyncStatusHandler(MainDialog* parentDlg, + size_t lastSyncsLogFileSizeMax, xmlAccess::OnGuiError handleError, const std::wstring& jobName, const std::wstring& execWhenFinished, @@ -69,6 +70,7 @@ private: MainDialog* parentDlg_; SyncStatus syncStatusFrame; //the window managed by SyncStatus has longer lifetime than this handler! + const size_t lastSyncsLogFileSizeMax_; xmlAccess::OnGuiError handleError_; zen::ErrorLog errorLog; const std::wstring jobName_; diff --git a/ui/main_dlg.cpp b/ui/main_dlg.cpp index 0004d2fc..80c5e18f 100644 --- a/ui/main_dlg.cpp +++ b/ui/main_dlg.cpp @@ -594,6 +594,8 @@ MainDialog::MainDialog(const xmlAccess::XmlGuiConfig& guiCfg, m_bpButtonSyncConfig->SetBitmapLabel(GlobalResources::getImage(L"syncConfig")); m_bpButtonCmpConfig ->SetBitmapLabel(GlobalResources::getImage(L"cmpConfig")); m_bpButtonLoad ->SetBitmapLabel(GlobalResources::getImage(L"load")); + m_bpButtonBatchJob ->SetBitmapLabel(GlobalResources::getImage(L"batch")); + m_bpButtonAddPair ->SetBitmapLabel(GlobalResources::getImage(L"item_add")); { IconBuffer tmp(IconBuffer::SIZE_SMALL); @@ -955,43 +957,50 @@ typedef Zbase<wchar_t> zxString; //guaranteed exponential growth void MainDialog::copySelectionToClipboard() { - zxString clipboardString; - - auto addSelection = [&](const Grid& grid) + try { - if (auto prov = grid.getDataProvider()) + zxString clipboardString; + + auto addSelection = [&](const Grid& grid) { - std::vector<Grid::ColumnAttribute> colAttr = grid.getColumnConfig(); - vector_remove_if(colAttr, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); - if (!colAttr.empty()) + if (auto prov = grid.getDataProvider()) { - const std::vector<size_t> selection = grid.getSelectedRows(); - std::for_each(selection.begin(), selection.end(), - [&](size_t row) + std::vector<Grid::ColumnAttribute> colAttr = grid.getColumnConfig(); + vector_remove_if(colAttr, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); + if (!colAttr.empty()) { - std::for_each(colAttr.begin(), colAttr.end() - 1, - [&](const Grid::ColumnAttribute& ca) + const std::vector<size_t> selection = grid.getSelectedRows(); + std::for_each(selection.begin(), selection.end(), + [&](size_t row) { - clipboardString += copyStringTo<zxString>(prov->getValue(row, ca.type_)); - clipboardString += L'\t'; + std::for_each(colAttr.begin(), colAttr.end() - 1, + [&](const Grid::ColumnAttribute& ca) + { + clipboardString += copyStringTo<zxString>(prov->getValue(row, ca.type_)); + clipboardString += L'\t'; + }); + clipboardString += copyStringTo<zxString>(prov->getValue(row, colAttr.back().type_)); + clipboardString += L'\n'; }); - clipboardString += copyStringTo<zxString>(prov->getValue(row, colAttr.back().type_)); - clipboardString += L'\n'; - }); + } } - } - }; + }; - addSelection(*m_gridMainL); - addSelection(*m_gridMainR); + addSelection(*m_gridMainL); + addSelection(*m_gridMainR); - //finally write to clipboard - if (!clipboardString.empty()) - if (wxTheClipboard->Open()) - { - wxTheClipboard->SetData(new wxTextDataObject(copyStringTo<wxString>(clipboardString))); //ownership passed - wxTheClipboard->Close(); - } + //finally write to clipboard + if (!clipboardString.empty()) + if (wxClipboard::Get()->Open()) + { + ZEN_ON_SCOPE_EXIT(wxClipboard::Get()->Close()); + wxClipboard::Get()->SetData(new wxTextDataObject(copyStringTo<wxString>(clipboardString))); //ownership passed + } + } + catch (const std::bad_alloc& e) + { + wxMessageBox(_("Out of memory!") + L" " + utfCvrtTo<std::wstring>(e.what()), _("Error"), wxOK | wxICON_ERROR); + } } @@ -1625,7 +1634,7 @@ void MainDialog::onGridButtonEvent(wxKeyEvent& event, Grid& grid, bool leftSide) case WXK_SPACE: case WXK_NUMPAD_SPACE: { - const std::vector<FileSystemObject*>& selection = getGridSelection(); + const std::vector<FileSystemObject*>& selection = getGridSelection(); if (!selection.empty()) setFilterManually(selection, !selection[0]->isActive()); } @@ -1730,7 +1739,7 @@ void MainDialog::OnGlobalKeyEvent(wxKeyEvent& event) //process key events withou { m_gridMainL->SetFocus(); - event.SetEventType(wxEVT_KEY_DOWN); //the grid event handler doesn't expect wxEVT_CHAR_HOOK! + event.SetEventType(wxEVT_KEY_DOWN); //the grid event handler doesn't expect wxEVT_CHAR_HOOK! evtHandler->ProcessEvent(event); //propagating event catched at wxTheApp to child leads to recursion, but we prevented it... event.Skip(false); //definitively handled now! return; @@ -1746,24 +1755,25 @@ void MainDialog::OnGlobalKeyEvent(wxKeyEvent& event) //process key events withou void MainDialog::onNaviSelection(GridRangeSelectEvent& event) { //scroll m_gridMain to user's new selection on m_gridNavi - int leadRow = -1; - if (std::unique_ptr<TreeView::Node> node = treeDataView->getLine(event.rowFrom_)) - { - if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get())) - leadRow = gridDataView->findRowFirstChild(&(root->baseMap_)); - else if (const TreeView::DirNode* dir = dynamic_cast<const TreeView::DirNode*>(node.get())) + ptrdiff_t leadRow = -1; + if (event.rowFirst_ != event.rowLast_) + if (std::unique_ptr<TreeView::Node> node = treeDataView->getLine(event.rowFirst_)) { - leadRow = gridDataView->findRowDirect(&(dir->dirObj_)); - if (leadRow < 0) //directory was filtered out! still on tree view (but NOT on grid view) - leadRow = gridDataView->findRowFirstChild(&(dir->dirObj_)); + if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get())) + leadRow = gridDataView->findRowFirstChild(&(root->baseMap_)); + else if (const TreeView::DirNode* dir = dynamic_cast<const TreeView::DirNode*>(node.get())) + { + leadRow = gridDataView->findRowDirect(&(dir->dirObj_)); + if (leadRow < 0) //directory was filtered out! still on tree view (but NOT on grid view) + leadRow = gridDataView->findRowFirstChild(&(dir->dirObj_)); + } + else if (const TreeView::FilesNode* files = dynamic_cast<const TreeView::FilesNode*>(node.get())) + leadRow = gridDataView->findRowDirect(files->firstFile_.getId()); } - else if (const TreeView::FilesNode* files = dynamic_cast<const TreeView::FilesNode*>(node.get())) - leadRow = gridDataView->findRowDirect(files->firstFile_.getId()); - } if (leadRow >= 0) { - leadRow =std::max(0, leadRow - 1); //scroll one more row + leadRow = std::max<ptrdiff_t>(0, leadRow - 1); //scroll one more row m_gridMainL->scrollTo(leadRow); m_gridMainC->scrollTo(leadRow); @@ -1857,7 +1867,7 @@ void MainDialog::onNaviGridContext(GridClickEvent& event) //---------------------------------------------------------------------------------------------------- //CONTEXT_DELETE_FILES menu.addSeparator(); - menu.addItem(_("Delete") + L"\tDel", [&] { deleteSelectedFiles(selection, selection); }, nullptr, !selection.empty()); + menu.addItem(_("Delete") + L"\tDel", [&] { deleteSelectedFiles(selection, selection); }, nullptr, !selection.empty(), wxID_DELETE); menu.popup(*this); } @@ -2454,7 +2464,7 @@ bool MainDialog::trySaveConfig(const wxString* fileName) //return true if saved try { - xmlAccess::writeConfig(guiCfg, toZ(targetFilename)); //write config to XML + xmlAccess::writeConfig(guiCfg, toZ(targetFilename)); //throw FfsXmlError setLastUsedConfig(targetFilename, guiCfg); flashStatusInformation(_("Configuration saved!")); @@ -2686,16 +2696,14 @@ void MainDialog::OnClose(wxCloseEvent& event) void MainDialog::onCheckRows(CheckRowsEvent& event) { - const int rowFirst = std::min(event.rowFrom_, event.rowTo_); // [rowFirst, rowLast) - int rowLast = std::max(event.rowFrom_, event.rowTo_) + 1; // - rowLast = std::min(rowLast, static_cast<int>(gridDataView->rowsOnView())); //consider dummy rows + std::set<size_t> selectedRows; - if (0 <= rowFirst && rowFirst < rowLast) - { - std::set<size_t> selectedRows; - for (int i = rowFirst; i < rowLast; ++i) - selectedRows.insert(i); + const size_t rowLast = std::min(event.rowLast_, gridDataView->rowsOnView()); //consider dummy rows + for (size_t i = event.rowFirst_; i < rowLast; ++i) + selectedRows.insert(i); + if (!selectedRows.empty()) + { std::vector<FileSystemObject*> objects = gridDataView->getAllFileRef(selectedRows); setFilterManually(objects, event.setIncluded_); } @@ -2704,16 +2712,14 @@ void MainDialog::onCheckRows(CheckRowsEvent& event) void MainDialog::onSetSyncDirection(SyncDirectionEvent& event) { - const int rowFirst = std::min(event.rowFrom_, event.rowTo_); // [rowFirst, rowLast) - int rowLast = std::max(event.rowFrom_, event.rowTo_) + 1; // - rowLast = std::min(rowLast, static_cast<int>(gridDataView->rowsOnView())); //consider dummy rows + std::set<size_t> selectedRows; - if (0 <= rowFirst && rowFirst < rowLast) - { - std::set<size_t> selectedRows; - for (int i = rowFirst; i < rowLast; ++i) - selectedRows.insert(i); + const size_t rowLast = std::min(event.rowLast_, gridDataView->rowsOnView()); //consider dummy rows + for (size_t i = event.rowFirst_; i < rowLast; ++i) + selectedRows.insert(i); + if (!selectedRows.empty()) + { std::vector<FileSystemObject*> objects = gridDataView->getAllFileRef(selectedRows); setSyncDirManually(objects, event.direction_); } @@ -3172,7 +3178,7 @@ void MainDialog::OnCompare(wxCommandEvent& event) wxWindow* oldFocus = wxWindow::FindFocus(); ZEN_ON_SCOPE_EXIT(if (oldFocus) oldFocus->SetFocus();) //e.g. keep focus on main grid after pressing F5 - int scrollPosX = 0; + int scrollPosX = 0; int scrollPosY = 0; m_gridMainL->GetViewStart(&scrollPosX, &scrollPosY); //preserve current scroll position ZEN_ON_SCOPE_EXIT( @@ -3193,12 +3199,14 @@ void MainDialog::OnCompare(wxCommandEvent& event) std::unique_ptr<LockHolder> dummy2; if (globalCfg.createLockFile) { - dummy2.reset(new LockHolder(true)); //allow pw prompt - for (auto iter = cmpConfig.begin(); iter != cmpConfig.end(); ++iter) + std::vector<Zstring> dirnames; + std::for_each(cmpConfig.begin(), cmpConfig.end(), + [&](const FolderPairCfg& fpCfg) { - dummy2->addDir(iter->leftDirectoryFmt, statusHandler); - dummy2->addDir(iter->rightDirectoryFmt, statusHandler); - } + dirnames.push_back(fpCfg.leftDirectoryFmt); + dirnames.push_back(fpCfg.rightDirectoryFmt); + }); + dummy2 = make_unique<LockHolder>(dirnames, statusHandler, true); //allow pw prompt } //COMPARE DIRECTORIES @@ -3234,7 +3242,7 @@ void MainDialog::OnCompare(wxCommandEvent& event) //add to folder history after successful comparison only folderHistoryLeft ->addItem(toZ(m_directoryLeft->GetValue())); folderHistoryRight->addItem(toZ(m_directoryRight->GetValue())); - + //prepare status information if (allElementsEqual(folderCmp)) flashStatusInformation(_("All folders are in sync!")); @@ -3378,7 +3386,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event) if (wxEvtHandler* evtHandler = m_buttonCompare->GetEventHandler()) evtHandler->ProcessEvent(dummy2); //synchronous call - if (folderCmp.empty()) //check if user aborted or error occured, ect... + if (folderCmp.empty()) //check if user aborted or error occurred, ect... return; } @@ -3410,6 +3418,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event) //class handling status updates and error messages SyncStatusHandler statusHandler(this, //throw GuiAbortProcess + globalCfg.lastSyncsLogFileSizeMax, currentCfg.handleError, xmlAccess::extractJobName(activeFileName), guiCfg.mainCfg.onCompletion, @@ -3419,12 +3428,13 @@ void MainDialog::OnStartSync(wxCommandEvent& event) std::unique_ptr<LockHolder> dummy2; if (globalCfg.createLockFile) { - dummy2.reset(new LockHolder(true)); //allow pw prompt + std::vector<Zstring> dirnames; for (auto iter = begin(folderCmp); iter != end(folderCmp); ++iter) { - dummy2->addDir(iter->getBaseDirPf<LEFT_SIDE >(), statusHandler); - dummy2->addDir(iter->getBaseDirPf<RIGHT_SIDE>(), statusHandler); + dirnames.push_back(iter->getBaseDirPf<LEFT_SIDE >()); + dirnames.push_back(iter->getBaseDirPf<RIGHT_SIDE>()); } + dummy2 = make_unique<LockHolder>(dirnames, statusHandler, true); //allow pw prompt } //START SYNCHRONIZATION @@ -3465,7 +3475,7 @@ void MainDialog::onGridDoubleClickR(GridClickEvent& event) onGridDoubleClickRim(event.row_, false); } -void MainDialog::onGridDoubleClickRim(int row, bool leftSide) +void MainDialog::onGridDoubleClickRim(size_t row, bool leftSide) { if (!globalCfg.gui.externelApplications.empty()) openExternalApplication(globalCfg.gui.externelApplications[0].second, @@ -3966,8 +3976,8 @@ void MainDialog::clearAddFolderPairs() //m_scrolledWindowFolderPairs->SetMinSize(wxSize(-1, 0)); //bSizer1->Layout(); } -//######################################################################################################## +//######################################################################################################## //menu events void MainDialog::OnMenuGlobalSettings(wxCommandEvent& event) @@ -3992,44 +4002,44 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) const Zstring filename = utfCvrtTo<Zstring>(filePicker.GetPath()); - Utf8String buffer; //perf: wxString doesn't model exponential growth and so is out, std::string doesn't give performance guarantee! - buffer += BYTE_ORDER_MARK_UTF8; + Utf8String header; //perf: wxString doesn't model exponential growth and so is out, std::string doesn't give performance guarantee! + header += BYTE_ORDER_MARK_UTF8; //write legend - buffer += utfCvrtTo<Utf8String>(_("Legend")) + '\n'; + header += utfCvrtTo<Utf8String>(_("Legend")) + '\n'; if (showSyncAction_) { - buffer += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_EQUAL)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_EQUAL)) + '\n'; - buffer += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_CREATE_NEW_LEFT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_CREATE_NEW_LEFT)) + '\n'; - buffer += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_CREATE_NEW_RIGHT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_CREATE_NEW_RIGHT)) + '\n'; - buffer += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_OVERWRITE_LEFT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_OVERWRITE_LEFT)) + '\n'; - buffer += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_OVERWRITE_RIGHT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_OVERWRITE_RIGHT)) + '\n'; - buffer += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_DELETE_LEFT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_DELETE_LEFT)) + '\n'; - buffer += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_DELETE_RIGHT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_DELETE_RIGHT)) + '\n'; - buffer += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_DO_NOTHING)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_DO_NOTHING)) + '\n'; - buffer += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_UNRESOLVED_CONFLICT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_UNRESOLVED_CONFLICT)) + '\n'; + header += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_EQUAL)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_EQUAL)) + '\n'; + header += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_CREATE_NEW_LEFT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_CREATE_NEW_LEFT)) + '\n'; + header += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_CREATE_NEW_RIGHT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_CREATE_NEW_RIGHT)) + '\n'; + header += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_OVERWRITE_LEFT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_OVERWRITE_LEFT)) + '\n'; + header += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_OVERWRITE_RIGHT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_OVERWRITE_RIGHT)) + '\n'; + header += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_DELETE_LEFT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_DELETE_LEFT)) + '\n'; + header += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_DELETE_RIGHT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_DELETE_RIGHT)) + '\n'; + header += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_DO_NOTHING)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_DO_NOTHING)) + '\n'; + header += "\"" + utfCvrtTo<Utf8String>(getSyncOpDescription(SO_UNRESOLVED_CONFLICT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(SO_UNRESOLVED_CONFLICT)) + '\n'; } else { - buffer += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_EQUAL)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_EQUAL)) + '\n'; - buffer += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_DIFFERENT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_DIFFERENT)) + '\n'; - buffer += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_LEFT_SIDE_ONLY)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_LEFT_SIDE_ONLY)) + '\n'; - buffer += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_RIGHT_SIDE_ONLY)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_RIGHT_SIDE_ONLY)) + '\n'; - buffer += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_LEFT_NEWER)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_LEFT_NEWER)) + '\n'; - buffer += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_RIGHT_NEWER)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_RIGHT_NEWER)) + '\n'; - buffer += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_CONFLICT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_CONFLICT)) + '\n'; + header += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_EQUAL)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_EQUAL)) + '\n'; + header += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_DIFFERENT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_DIFFERENT)) + '\n'; + header += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_LEFT_SIDE_ONLY)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_LEFT_SIDE_ONLY)) + '\n'; + header += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_RIGHT_SIDE_ONLY)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_RIGHT_SIDE_ONLY)) + '\n'; + header += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_LEFT_NEWER)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_LEFT_NEWER)) + '\n'; + header += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_RIGHT_NEWER)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_RIGHT_NEWER)) + '\n'; + header += "\"" + utfCvrtTo<Utf8String>(getCategoryDescription(FILE_CONFLICT)) + "\";" + utfCvrtTo<Utf8String>(getSymbol(FILE_CONFLICT)) + '\n'; } - buffer += '\n'; + header += '\n'; //base folders - buffer += utfCvrtTo<Utf8String>(_("Folder pairs")) + '\n' ; + header += utfCvrtTo<Utf8String>(_("Folder pairs")) + '\n' ; std::for_each(begin(folderCmp), end(folderCmp), [&](BaseDirMapping& baseMap) { - buffer += utfCvrtTo<Utf8String>(baseMap.getBaseDirPf<LEFT_SIDE >()) + ';'; - buffer += utfCvrtTo<Utf8String>(baseMap.getBaseDirPf<RIGHT_SIDE>()) + '\n'; + header += utfCvrtTo<Utf8String>(baseMap.getBaseDirPf<LEFT_SIDE >()) + ';'; + header += utfCvrtTo<Utf8String>(baseMap.getBaseDirPf<RIGHT_SIDE>()) + '\n'; }); - buffer += '\n'; + header += '\n'; //write header auto provLeft = m_gridMainL->getDataProvider(); @@ -4044,12 +4054,12 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) vector_remove_if(colAttrMiddle, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); vector_remove_if(colAttrRight , [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); - auto addCellValue = [&](const wxString& val) + auto fmtCellValue = [](const wxString& val) -> Utf8String { if (val.find(L';') != wxString::npos) - buffer += '\"' + utfCvrtTo<Utf8String>(val) + '\"'; + return '\"' + utfCvrtTo<Utf8String>(val) + '\"'; else - buffer += utfCvrtTo<Utf8String>(val); + return utfCvrtTo<Utf8String>(val); }; if (provLeft && provMiddle && provRight) @@ -4057,62 +4067,73 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) std::for_each(colAttrLeft.begin(), colAttrLeft.end(), [&](const Grid::ColumnAttribute& ca) { - addCellValue(provLeft->getColumnLabel(ca.type_)); - buffer += ';'; + header += fmtCellValue(provLeft->getColumnLabel(ca.type_)); + header += ';'; }); std::for_each(colAttrMiddle.begin(), colAttrMiddle.end(), [&](const Grid::ColumnAttribute& ca) { - addCellValue(provMiddle->getColumnLabel(ca.type_)); - buffer += ';'; + header += fmtCellValue(provMiddle->getColumnLabel(ca.type_)); + header += ';'; }); if (!colAttrRight.empty()) { std::for_each(colAttrRight.begin(), colAttrRight.end() - 1, [&](const Grid::ColumnAttribute& ca) { - addCellValue(provRight->getColumnLabel(ca.type_)); - buffer += ';'; + header += fmtCellValue(provRight->getColumnLabel(ca.type_)); + header += ';'; }); - addCellValue(provRight->getColumnLabel(colAttrRight.back().type_)); + header += fmtCellValue(provRight->getColumnLabel(colAttrRight.back().type_)); } - buffer += '\n'; + header += '\n'; - //main grid - const size_t rowCount = m_gridMainL->getRowCount(); - for (size_t row = 0; row < rowCount; ++row) + try { - std::for_each(colAttrLeft.begin(), colAttrLeft.end(), - [&](const Grid::ColumnAttribute& ca) - { - addCellValue(provLeft->getValue(row, ca.type_)); - buffer += ';'; - }); - std::for_each(colAttrMiddle.begin(), colAttrMiddle.end(), - [&](const Grid::ColumnAttribute& ca) - { - addCellValue(provMiddle->getValue(row, ca.type_)); - buffer += ';'; - }); - if (!colAttrRight.empty()) + //write file + FileOutput fileOut(filename, zen::FileOutput::ACC_OVERWRITE); //throw FileError + + replace(header, '\n', LINE_BREAK); + fileOut.write(&*header.begin(), header.size()); //throw FileError + + //main grid: write rows one after the other instead of creating one big string: memory allocation might fail; think 1 million rows! + /* + performance test case "export 600.000 rows" to CSV: + aproach 1. assemble single temporary string, then write file: 4.6s + aproach 2. write to buffered file output directly for each row: 6.4s + */ + const size_t rowCount = m_gridMainL->getRowCount(); + for (size_t row = 0; row < rowCount; ++row) { - std::for_each(colAttrRight.begin(), colAttrRight.end() - 1, + Utf8String tmp; + + std::for_each(colAttrLeft.begin(), colAttrLeft.end(), [&](const Grid::ColumnAttribute& ca) { - addCellValue(provRight->getValue(row, ca.type_)); - buffer += ';'; + tmp += fmtCellValue(provLeft->getValue(row, ca.type_)); + tmp += ';'; }); - addCellValue(provRight->getValue(row, colAttrRight.back().type_)); - } - buffer += '\n'; - } - - //write file - try - { - replace(buffer, '\n', LINE_BREAK); + std::for_each(colAttrMiddle.begin(), colAttrMiddle.end(), + [&](const Grid::ColumnAttribute& ca) + { + tmp += fmtCellValue(provMiddle->getValue(row, ca.type_)); + tmp += ';'; + }); + if (!colAttrRight.empty()) + { + std::for_each(colAttrRight.begin(), colAttrRight.end() - 1, + [&](const Grid::ColumnAttribute& ca) + { + tmp += fmtCellValue(provRight->getValue(row, ca.type_)); + tmp += ';'; + }); + tmp += fmtCellValue(provRight->getValue(row, colAttrRight.back().type_)); + } + tmp += '\n'; - saveBinStream(filename, buffer); //throw FileError + replace(tmp, '\n', LINE_BREAK); + fileOut.write(&*tmp.begin(), tmp.size()); //throw FileError + } flashStatusInformation(_("File list exported!")); } diff --git a/ui/main_dlg.h b/ui/main_dlg.h index f5f07624..07687f88 100644 --- a/ui/main_dlg.h +++ b/ui/main_dlg.h @@ -163,7 +163,7 @@ private: void onGridDoubleClickL(zen::GridClickEvent& event); void onGridDoubleClickR(zen::GridClickEvent& event); - void onGridDoubleClickRim(int row, bool leftSide); + void onGridDoubleClickRim(size_t row, bool leftSide); void onGridLabelLeftClickC(zen::GridClickEvent& event); void onGridLabelLeftClickL(zen::GridClickEvent& event); diff --git a/ui/progress_indicator.cpp b/ui/progress_indicator.cpp index ebbc9edd..caf87428 100644 --- a/ui/progress_indicator.cpp +++ b/ui/progress_indicator.cpp @@ -10,12 +10,15 @@ #include <wx/stopwatch.h> #include <wx/wupdlock.h> #include <wx/sound.h> +#include <wx/clipbrd.h> +#include <wx/msgdlg.h> #include <zen/basic_math.h> #include <zen/format_unit.h> #include <wx+/mouse_move_dlg.h> #include <wx+/toggle_button.h> #include <wx+/image_tools.h> #include <wx+/graph.h> +#include <wx+/context_menu.h> #include <wx+/no_flicker.h> #include <zen/file_handling.h> #include "gui_generated.h" @@ -272,7 +275,7 @@ namespace inline wxBitmap buttonPressed(const std::string& name) { - wxBitmap background = GlobalResources::getImage(wxT("log button pressed")); + wxBitmap background = GlobalResources::getImage(L"log button pressed"); return layOver(GlobalResources::getImage(utfCvrtTo<wxString>(name)), background); } @@ -287,17 +290,283 @@ wxBitmap buttonReleased(const std::string& name) zen::move(output, 0, -1); //move image right one pixel return output; } + + +//a vector-view on ErrorLog considering multi-line messages: prepare consumption by Grid +class MessageView +{ +public: + MessageView(const ErrorLog& log) : log_(log) {} + + size_t rowsOnView() const { return viewRef.size(); } + + struct LogEntryView + { + time_t time; + MessageType type; + MsgString messageLine; + bool firstLine; //if LogEntry::message spans multiple rows + }; + bool getEntry(size_t row, LogEntryView& out) const + { + if (row < viewRef.size()) + { + const Line& line = viewRef[row]; + out.time = line.entry_->time; + out.type = line.entry_->type; + out.messageLine = extractLine(line.entry_->message, line.rowNumber_); + out.firstLine = line.rowNumber_ == 0; //this is virtually always correct, unless first line of the original message is empty! + return true; + } + return false; + } + + void updateView(int includedTypes) //TYPE_INFO | TYPE_WARNING, ect. see error_log.h + { + viewRef.clear(); + + for (auto iter = log_.begin(); iter != log_.end(); ++iter) + if (iter->type & includedTypes) + { + assert_static((IsSameType<GetCharType<MsgString>::Type, wchar_t>::value)); + assert(!startsWith(iter->message, L'\n')); + + size_t rowNumber = 0; + bool lastCharNewline = true; + std::for_each(iter->message.begin(), iter->message.end(), + [&](wchar_t c) + { + typedef Line Line; //workaround MSVC compiler bug! + + if (c == L'\n') + { + if (!lastCharNewline) //do not reference empty lines! + viewRef.push_back(Line(&*iter, rowNumber)); + ++rowNumber; + lastCharNewline = true; + } + else + lastCharNewline = false; + }); + if (!lastCharNewline) + viewRef.push_back(Line(&*iter, rowNumber)); + } + } + +private: + static MsgString extractLine(const MsgString& message, size_t textRow) + { + auto iter1 = message.begin(); + for (;;) + { + auto iter2 = std::find_if(iter1, message.end(), [](wchar_t c) { return c == L'\n'; }); + if (textRow == 0) + return iter1 == message.end() ? MsgString() : MsgString(&*iter1, iter2 - iter1); //must not dereference iterator pointing to "end"! + + if (iter2 == message.end()) + { + assert(false); + return MsgString(); + } + + iter1 = iter2 + 1; //skip newline + --textRow; + } + } + + struct Line + { + Line(const LogEntry* entry, size_t rowNumber) : entry_(entry), rowNumber_(rowNumber) {} + const LogEntry* entry_; //always bound! + size_t rowNumber_; //LogEntry::message may span multiple rows + }; + + std::vector<Line> viewRef; //partial view on log_ + /* /|\ + | updateView() + | */ + const ErrorLog log_; +}; + +//----------------------------------------------------------------------------- + +enum ColumnTypeMsg +{ + COL_TYPE_MSG_TIME, + COL_TYPE_MSG_CATEGORY, + COL_TYPE_MSG_TEXT, +}; + +//Grid data implementation referencing MessageView +class GridDataMessages : public GridData +{ + static const int COLUMN_BORDER_LEFT = 4; //for left-aligned text + +public: + GridDataMessages(const std::shared_ptr<MessageView>& msgView) : msgView_(msgView) {} + + virtual size_t getRowCount() const { return msgView_ ? msgView_->rowsOnView() : 0; } + + virtual wxString getValue(size_t row, ColumnType colType) const + { + MessageView::LogEntryView entry = {}; + if (msgView_ && msgView_->getEntry(row, entry)) + switch (static_cast<ColumnTypeMsg>(colType)) + { + case COL_TYPE_MSG_TIME: + if (entry.firstLine) + return formatTime<wxString>(FORMAT_TIME, localTime(entry.time)); + break; + + case COL_TYPE_MSG_CATEGORY: + if (entry.firstLine) + switch (entry.type) + { + case TYPE_INFO: + return _("Info"); + case TYPE_WARNING: + return _("Warning"); + case TYPE_ERROR: + return _("Error"); + case TYPE_FATAL_ERROR: + return _("Fatal Error"); + } + break; + + case COL_TYPE_MSG_TEXT: + return copyStringTo<wxString>(entry.messageLine); + } + return wxEmptyString; + } + + virtual void renderCell(Grid& grid, wxDC& dc, const wxRect& rect, size_t row, ColumnType colType) + { + wxRect rectTmp = rect; + + const wxColor colorGridLine = wxColour(192, 192, 192); //light grey + + wxDCPenChanger dummy2(dc, wxPen(colorGridLine, 1, wxSOLID)); + const bool drawBottomLine = [&]() -> bool //don't separate multi-line messages + { + MessageView::LogEntryView nextEntry = {}; + if (msgView_ && msgView_->getEntry(row + 1, nextEntry)) + return nextEntry.firstLine; + return true; + }(); + + if (drawBottomLine) + { + dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); + --rectTmp.height; + } + + //-------------------------------------------------------- + + MessageView::LogEntryView entry = {}; + if (msgView_ && msgView_->getEntry(row, entry)) + switch (static_cast<ColumnTypeMsg>(colType)) + { + case COL_TYPE_MSG_TIME: + drawCellText(dc, rectTmp, getValue(row, colType), grid.IsEnabled(), wxALIGN_CENTER); + break; + + case COL_TYPE_MSG_CATEGORY: + if (entry.firstLine) + switch (entry.type) + { + case TYPE_INFO: + dc.DrawLabel(wxString(), GlobalResources::getImage(L"msg_small_info"), rectTmp, wxALIGN_CENTER); + break; + case TYPE_WARNING: + dc.DrawLabel(wxString(), GlobalResources::getImage(L"msg_small_warning"), rectTmp, wxALIGN_CENTER); + break; + case TYPE_ERROR: + case TYPE_FATAL_ERROR: + dc.DrawLabel(wxString(), GlobalResources::getImage(L"msg_small_error"), rectTmp, wxALIGN_CENTER); + break; + } + break; + + case COL_TYPE_MSG_TEXT: + { + rectTmp.x += COLUMN_BORDER_LEFT; + rectTmp.width -= COLUMN_BORDER_LEFT; + drawCellText(dc, rectTmp, getValue(row, colType), grid.IsEnabled()); + } + break; + } + } + + virtual size_t getBestSize(wxDC& dc, size_t row, ColumnType colType) + { + // -> synchronize renderCell() <-> getBestSize() + + MessageView::LogEntryView entry = {}; + if (msgView_ && msgView_->getEntry(row, entry)) + switch (static_cast<ColumnTypeMsg>(colType)) + { + case COL_TYPE_MSG_TIME: + return 2 * COLUMN_BORDER_LEFT + dc.GetTextExtent(getValue(row, colType)).GetWidth(); + + case COL_TYPE_MSG_CATEGORY: + return GlobalResources::getImage(L"msg_small_info").GetWidth(); + + case COL_TYPE_MSG_TEXT: + return COLUMN_BORDER_LEFT + dc.GetTextExtent(getValue(row, colType)).GetWidth(); + } + return 0; + } + + static int getColumnTimeDefaultWidth(Grid& grid) + { + wxClientDC dc(&grid.getMainWin()); + dc.SetFont(grid.getMainWin().GetFont()); + return 2 * COLUMN_BORDER_LEFT + dc.GetTextExtent(formatTime<wxString>(FORMAT_TIME)).GetWidth(); + } + + static int getColumnCategoryDefaultWidth() + { + return GlobalResources::getImage(L"msg_small_info").GetWidth(); + } + + static int getRowDefaultHeight(const Grid& grid) + { + return std::max(GlobalResources::getImage(L"msg_small_info").GetHeight(), grid.getMainWin().GetCharHeight() + 2) + 1; //+ some space + bottom border + } + + virtual wxString getToolTip(size_t row, ColumnType colType) const + { + MessageView::LogEntryView entry = {}; + if (msgView_ && msgView_->getEntry(row, entry)) + switch (static_cast<ColumnTypeMsg>(colType)) + { + case COL_TYPE_MSG_TIME: + case COL_TYPE_MSG_TEXT: + break; + + case COL_TYPE_MSG_CATEGORY: + return getValue(row, colType); + } + return wxEmptyString; + } + + virtual wxString getColumnLabel(ColumnType colType) const { return wxEmptyString; } + +private: + const std::shared_ptr<MessageView> msgView_; +}; } class LogControl : public LogControlGenerated { public: - LogControl(wxWindow* parent, const ErrorLog& log) : LogControlGenerated(parent), log_(log) + LogControl(wxWindow* parent, const ErrorLog& log) : LogControlGenerated(parent), + msgView(std::make_shared<MessageView>(log)) { - const int errorCount = log_.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR); - const int warningCount = log_.getItemCount(TYPE_WARNING); - const int infoCount = log_.getItemCount(TYPE_INFO); + const int errorCount = log.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR); + const int warningCount = log.getItemCount(TYPE_WARNING); + const int infoCount = log.getItemCount(TYPE_INFO); m_bpButtonErrors ->init(buttonPressed ("msg_error" ), buttonReleased("msg_error" ), _("Error" ) + wxString::Format(L" (%d)", errorCount )); m_bpButtonWarnings->init(buttonPressed ("msg_warning"), buttonReleased("msg_warning"), _("Warning") + wxString::Format(L" (%d)", warningCount)); @@ -311,47 +580,49 @@ public: m_bpButtonWarnings->Show(warningCount != 0); m_bpButtonInfo ->Show(infoCount != 0); - m_textCtrlInfo->SetMaxLength(0); //allow large entries! + //init grid, determine default sizes + const int rowHeight = GridDataMessages::getRowDefaultHeight(*m_gridMessages); + const int colMsgTimeWidth = GridDataMessages::getColumnTimeDefaultWidth(*m_gridMessages); + const int colMsgCategoryWidth = GridDataMessages::getColumnCategoryDefaultWidth(); + + m_gridMessages->setDataProvider(std::make_shared<GridDataMessages>(msgView)); + m_gridMessages->setColumnLabelHeight(0); + m_gridMessages->showRowLabel(false); + m_gridMessages->setRowHeight(rowHeight); + std::vector<Grid::ColumnAttribute> attr; + attr.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_MSG_TIME ), colMsgTimeWidth, 0)); + attr.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_MSG_CATEGORY), colMsgCategoryWidth, 0)); + attr.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_MSG_TEXT ), -colMsgTimeWidth - colMsgCategoryWidth, 1)); + m_gridMessages->setColumnConfig(attr); + + //support for CTRL + C + m_gridMessages->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(LogControl::onGridButtonEvent), nullptr, this); - updateLogText(); + m_gridMessages->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(LogControl::onMsgGridContext), nullptr, this); - m_textCtrlInfo->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(LogControl::onKeyEvent), nullptr, this); + updateGrid(); } private: virtual void OnErrors(wxCommandEvent& event) { m_bpButtonErrors->toggle(); - updateLogText(); + updateGrid(); } virtual void OnWarnings(wxCommandEvent& event) { m_bpButtonWarnings->toggle(); - updateLogText(); + updateGrid(); } virtual void OnInfo(wxCommandEvent& event) { m_bpButtonInfo->toggle(); - updateLogText(); + updateGrid(); } - void onKeyEvent(wxKeyEvent& event) - { - const int keyCode = event.GetKeyCode(); - - if (event.ControlDown()) - switch (keyCode) - { - case 'A': //CTRL + A - m_textCtrlInfo->SetSelection(-1, -1); //select all - return; - } - event.Skip(); - } - - void updateLogText() + void updateGrid() { int includedTypes = 0; if (m_bpButtonErrors->isActive()) @@ -363,27 +634,83 @@ private: if (m_bpButtonInfo->isActive()) includedTypes |= TYPE_INFO; - //fast replacement for wxString modelling exponential growth - MsgString logText; + msgView->updateView(includedTypes); //update MVC "model" + m_gridMessages->Refresh(); //update MVC "view" + } + - const auto& entries = log_.getEntries(); - for (auto iter = entries.begin(); iter != entries.end(); ++iter) - if (iter->type & includedTypes) + void onGridButtonEvent(wxKeyEvent& event) + { + int keyCode = event.GetKeyCode(); + + if (event.ControlDown()) + switch (keyCode) { - logText += formatMessage(*iter); - logText += L'\n'; + case 'C': + case WXK_INSERT: //CTRL + C || CTRL + INS + copySelectionToClipboard(); + return; // -> swallow event! don't allow default grid commands! } - if (logText.empty()) //if no messages match selected view filter, at least show final status message - if (!entries.empty()) - logText = formatMessage(entries.back()); + event.Skip(); //unknown keypress: propagate + } + + void onMsgGridContext(GridClickEvent& event) + { + const std::vector<size_t> selection = m_gridMessages->getSelectedRows(); + + ContextMenu menu; + menu.addItem(_("Copy") + L"\tCtrl+C", [this] { copySelectionToClipboard(); }, nullptr, !selection.empty(), wxID_COPY); + menu.popup(*this); + } + + void copySelectionToClipboard() + { + try + { + typedef Zbase<wchar_t> zxString; //guaranteed exponential growth, unlike wxString + zxString clipboardString; + + if (auto prov = m_gridMessages->getDataProvider()) + { + std::vector<Grid::ColumnAttribute> colAttr = m_gridMessages->getColumnConfig(); + vector_remove_if(colAttr, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); + if (!colAttr.empty()) + { + const std::vector<size_t> selection = m_gridMessages->getSelectedRows(); + std::for_each(selection.begin(), selection.end(), + [&](size_t row) + { +#ifdef _MSC_VER + typedef zxString zxString; //workaround MSVC compiler bug! +#endif + std::for_each(colAttr.begin(), --colAttr.end(), + [&](const Grid::ColumnAttribute& ca) + { + clipboardString += copyStringTo<zxString>(prov->getValue(row, ca.type_)); + clipboardString += L'\t'; + }); + clipboardString += copyStringTo<zxString>(prov->getValue(row, colAttr.back().type_)); + clipboardString += L'\n'; + }); + } + } - wxWindowUpdateLocker dummy(m_textCtrlInfo); - m_textCtrlInfo->ChangeValue(copyStringTo<wxString>(logText)); - m_textCtrlInfo->ShowPosition(m_textCtrlInfo->GetLastPosition()); + //finally write to clipboard + if (!clipboardString.empty()) + if (wxClipboard::Get()->Open()) + { + ZEN_ON_SCOPE_EXIT(wxClipboard::Get()->Close()); + wxClipboard::Get()->SetData(new wxTextDataObject(copyStringTo<wxString>(clipboardString))); //ownership passed + } + } + catch (const std::bad_alloc& e) + { + wxMessageBox(_("Out of memory!") + L" " + utfCvrtTo<std::wstring>(e.what()), _("Error"), wxOK | wxICON_ERROR); + } } - const ErrorLog log_; + std::shared_ptr<MessageView> msgView; //bound! }; //######################################################################################## @@ -983,12 +1310,12 @@ void SyncStatus::SyncStatusImpl::updateProgress(bool allowYield) if (paused_) { stopTimer(); + ZEN_ON_SCOPE_EXIT(resumeTimer()); while (paused_) { wxMilliSleep(UI_UPDATE_INTERVAL); updateUiNow(); //receive UI message that ends pause } - resumeTimer(); } /* /|\ @@ -1242,7 +1569,7 @@ void SyncStatus::SyncStatusImpl::processHasFinished(SyncResult resultId, const E m_listbookResult->AddPage(logControl, _("Logging"), false); //bSizerHoldStretch->Insert(0, logControl, 1, wxEXPAND); - //show log instead of graph if errors occured! (not required for ignored warnings) + //show log instead of graph if errors occurred! (not required for ignored warnings) if (log.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR) > 0) m_listbookResult->ChangeSelection(posLog); diff --git a/ui/small_dlgs.cpp b/ui/small_dlgs.cpp index 4b070dce..2b6c9d4a 100644 --- a/ui/small_dlgs.cpp +++ b/ui/small_dlgs.cpp @@ -109,14 +109,10 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent) bmpLogo = wxBitmap(tmp); } { - wxMemoryDC dc; - dc.SelectObject(bmpLogo); - + wxMemoryDC dc(bmpLogo); dc.SetTextForeground(*wxBLACK); dc.SetFont(wxFont(18, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, L"Tahoma")); dc.DrawLabel(wxString(L"FreeFileSync ") + zen::currentVersion, wxNullBitmap, wxRect(0, 0, bmpLogo.GetWidth(), bmpLogo.GetHeight()), wxALIGN_CENTER); - - dc.SelectObject(wxNullBitmap); } m_bitmap11->SetBitmap(bmpLogo); diff --git a/ui/tree_view.cpp b/ui/tree_view.cpp index a3a6b2aa..119091a0 100644 --- a/ui/tree_view.cpp +++ b/ui/tree_view.cpp @@ -939,7 +939,7 @@ private: { const int tolerance = 1; const int xNodeStatusFirst = -tolerance + cellArea.x + static_cast<int>(node->level_) * widthLevelStep + CELL_BORDER + (showPercentBar ? widthPercentBar + 2 * CELL_BORDER : 0); - const int xNodeStatusLast = xNodeStatusFirst + widthNodeStatus + 2 * tolerance; + const int xNodeStatusLast = (xNodeStatusFirst + tolerance) + widthNodeStatus + tolerance; // -> synchronize renderCell() <-> getBestSize() <-> onMouseLeft() if (xNodeStatusFirst <= absX && absX < xNodeStatusLast) @@ -948,7 +948,7 @@ private: } //-------------------------------------------------------------------------------------------------- - if (clickOnNodeStatus && event.row_ >= 0) + if (clickOnNodeStatus) switch (treeDataView_->getStatus(event.row_)) { case TreeView::STATUS_EXPANDED: @@ -964,7 +964,7 @@ private: void onMouseLeftDouble(GridClickEvent& event) { - if (event.row_ >= 0 && treeDataView_) + if (treeDataView_) switch (treeDataView_->getStatus(event.row_)) { case TreeView::STATUS_EXPANDED: @@ -1102,7 +1102,7 @@ private: void expandNode(size_t row) { treeDataView_->expandNode(row); - grid_.Refresh(); //this one clears selection (changed row count) + grid_.Refresh(); //implicitly clears selection (changed row count after expand) grid_.setGridCursor(row); //grid_.autoSizeColumns(); -> doesn't look as good as expected } @@ -1110,9 +1110,8 @@ private: void reduceNode(size_t row) { treeDataView_->reduceNode(row); - grid_.Refresh(); //this one clears selection (changed row count) + grid_.Refresh(); grid_.setGridCursor(row); - //grid_.autoSizeColumns(); -> doesn't look as good as expected } std::shared_ptr<TreeView> treeDataView_; @@ -1134,7 +1133,9 @@ void treeview::init(Grid& grid, const std::shared_ptr<TreeView>& treeDataView) { grid.setDataProvider(std::make_shared<GridDataNavi>(grid, treeDataView)); grid.showRowLabel(false); - grid.setRowHeight(IconBuffer(IconBuffer::SIZE_SMALL).getSize() + 2); //add some space + + const int rowHeight = std::max(IconBuffer(IconBuffer::SIZE_SMALL).getSize(), grid.getMainWin().GetCharHeight()) + 1; //add some space + grid.setRowHeight(rowHeight); } |