summaryrefslogtreecommitdiff
path: root/ui
diff options
context:
space:
mode:
Diffstat (limited to 'ui')
-rw-r--r--ui/batch_status_handler.cpp128
-rw-r--r--ui/batch_status_handler.h6
-rw-r--r--ui/column_attr.h5
-rw-r--r--ui/custom_grid.cpp493
-rw-r--r--ui/custom_grid.h2
-rw-r--r--ui/dir_name.cpp5
-rw-r--r--ui/grid_view.cpp1
-rw-r--r--ui/gui_generated.cpp30
-rw-r--r--ui/gui_generated.h5
-rw-r--r--ui/gui_status_handler.cpp120
-rw-r--r--ui/gui_status_handler.h14
-rw-r--r--ui/main_dlg.cpp346
-rw-r--r--ui/main_dlg.h14
-rw-r--r--ui/msg_popup.cpp68
-rw-r--r--ui/msg_popup.h17
-rw-r--r--ui/progress_indicator.cpp314
-rw-r--r--ui/progress_indicator.h66
-rw-r--r--ui/small_dlgs.cpp17
-rw-r--r--ui/sorting.h2
-rw-r--r--ui/sync_cfg.cpp4
-rw-r--r--ui/tree_view.cpp105
21 files changed, 995 insertions, 767 deletions
diff --git a/ui/batch_status_handler.cpp b/ui/batch_status_handler.cpp
index 0edd289f..e06a0000 100644
--- a/ui/batch_status_handler.cpp
+++ b/ui/batch_status_handler.cpp
@@ -38,7 +38,8 @@ 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) { assert(false); return ON_ERROR_IGNORE; } //errors are not really critical in this context
+ virtual HandleError reportDirError (const std::wstring& msg) { assert(false); return ON_ERROR_IGNORE; } //errors are not really critical in this context
+ virtual HandleError reportItemError(const std::wstring& msg, const Zchar* shortName) { assert(false); return ON_ERROR_IGNORE; } //
const Zstring prefix_;
std::vector<Zstring>& logfiles_;
@@ -112,13 +113,14 @@ BatchStatusHandler::BatchStatusHandler(bool showProgress,
lastSyncsLogFileSizeMax_(lastSyncsLogFileSizeMax),
handleError_(handleError),
returnCode_(returnCode),
- syncStatusFrame(*this, *this, nullptr, showProgress, jobName, execWhenFinished, execFinishedHistory),
- jobName_(jobName)
+ progressDlg(createProgressDialog(*this, [this] { this->onProgressDialogTerminate(); }, *this, nullptr, showProgress, jobName, execWhenFinished, execFinishedHistory)),
+ jobName_(jobName)
{
if (logfilesCountLimit != 0) //init log file: starts internal timer!
{
- if (!tryReportingError([&] { logFile = prepareNewLogfile(logfileDirectory, jobName, timeStamp); }, //throw FileError; return value always bound!
- *this))
+ zen::Opt<std::wstring> errMsg = tryReportingError2([&] { logFile = prepareNewLogfile(logfileDirectory, jobName, timeStamp); }, //throw FileError; return value always bound!
+ *this);
+ if (errMsg)
{
raiseReturnCode(returnCode_, FFS_RC_ABORTED);
throw BatchAbortProcess();
@@ -202,59 +204,73 @@ BatchStatusHandler::~BatchStatusHandler()
}
catch (FileError&) {}
- //decide whether to stay on status screen or exit immediately...
- if (switchToGuiRequested) //-> avoid recursive yield() calls, thous switch not before ending batch mode
+ if (progressDlg)
{
- try
+ //decide whether to stay on status screen or exit immediately...
+ if (switchToGuiRequested) //-> avoid recursive yield() calls, thous switch not before ending batch mode
{
- switchBatchToGui_.execute(); //open FreeFileSync GUI
+ try
+ {
+ switchBatchToGui_.execute(); //open FreeFileSync GUI
+ }
+ catch (...) {}
+ progressDlg->closeWindowDirectly(); //progressDlg is not main window anymore
}
- catch (...) {}
- syncStatusFrame.closeWindowDirectly(); //syncStatusFrame is not main window anymore
- }
- else
- {
- if (syncStatusFrame.getAsWindow()->IsShown())
- showFinalResults = true;
-
- //execute "on completion" command (even in case of ignored errors)
- if (!abortIsRequested()) //if aborted (manually), we don't execute the command
+ else
{
- const std::wstring finalCommand = syncStatusFrame.getExecWhenFinishedCommand(); //final value (after possible user modification)
- if (isCloseProgressDlgCommand(finalCommand))
- showFinalResults = false; //take precedence over current visibility status
- else if (!finalCommand.empty())
+ if (progressDlg->getAsWindow()->IsShown())
+ showFinalResults = true;
+
+ //execute "on completion" command (even in case of ignored errors)
+ if (!abortIsRequested()) //if aborted (manually), we don't execute the command
{
- auto cmdexp = expandMacros(utfCvrtTo<Zstring>(finalCommand));
- shellExecute(cmdexp);
+ const std::wstring finalCommand = progressDlg->getExecWhenFinishedCommand(); //final value (after possible user modification)
+ if (isCloseProgressDlgCommand(finalCommand))
+ showFinalResults = false; //take precedence over current visibility status
+ else if (!finalCommand.empty())
+ {
+ auto cmdexp = expandMacros(utfCvrtTo<Zstring>(finalCommand));
+ shellExecute(cmdexp);
+ }
}
+
+ if (showFinalResults) //warning: wxWindow::Show() is called within processHasFinished()!
+ {
+ //notify about (logical) application main window => program won't quit, but stay on this dialog
+ setMainWindow(progressDlg->getAsWindow());
+
+ //notify to progressDlg that current process has ended
+ if (abortIsRequested())
+ progressDlg->processHasFinished(SyncProgressDialog::RESULT_ABORTED, errorLog); //enable okay and close events
+ else if (totalErrors > 0)
+ progressDlg->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_ERROR, errorLog);
+ else if (totalWarnings > 0)
+ progressDlg->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_WARNINGS, errorLog);
+ else
+ progressDlg->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_SUCCESS, errorLog);
+ }
+ else
+ progressDlg->closeWindowDirectly(); //progressDlg is main window => program will quit directly
}
- if (showFinalResults) //warning: wxWindow::Show() is called within processHasFinished()!
+ //wait until progress dialog notified shutdown via onProgressDialogTerminate()
+ //-> required since it has our "this" pointer captured in lambda "notifyWindowTerminate"!
+ //-> nicely manages dialog lifetime
+ while (progressDlg)
{
- //notify about (logical) application main window => program won't quit, but stay on this dialog
- setMainWindow(syncStatusFrame.getAsWindow());
-
- //notify to syncStatusFrame that current process has ended
- if (abortIsRequested())
- syncStatusFrame.processHasFinished(SyncProgressDialog::RESULT_ABORTED, errorLog); //enable okay and close events
- else if (totalErrors > 0)
- syncStatusFrame.processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_ERROR, errorLog);
- else if (totalWarnings > 0)
- syncStatusFrame.processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_WARNINGS, errorLog);
- else
- syncStatusFrame.processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_SUCCESS, errorLog);
+ boost::this_thread::sleep(boost::posix_time::milliseconds(UI_UPDATE_INTERVAL));
+ updateUiNow();
}
- else
- syncStatusFrame.closeWindowDirectly(); //syncStatusFrame is main window => program will quit directly
}
}
+
void BatchStatusHandler::initNewPhase(int objectsTotal, Int64 dataTotal, ProcessCallback::Phase phaseID)
{
StatusHandler::initNewPhase(objectsTotal, dataTotal, phaseID);
- syncStatusFrame.initNewPhase(); //call after "StatusHandler::initNewPhase"
+ if (progressDlg)
+ progressDlg->initNewPhase(); //call after "StatusHandler::initNewPhase"
}
@@ -262,7 +278,8 @@ void BatchStatusHandler::updateProcessedData(int objectsDelta, Int64 dataDelta)
{
StatusHandler::updateProcessedData(objectsDelta, dataDelta);
- syncStatusFrame.notifyProgressChange(); //noexcept
+ if (progressDlg)
+ progressDlg->notifyProgressChange(); //noexcept
//note: this method should NOT throw in order to properly allow undoing setting of statistics!
}
@@ -285,11 +302,12 @@ void BatchStatusHandler::reportWarning(const std::wstring& warningMessage, bool&
{
case xmlAccess::ON_ERROR_POPUP:
{
- PauseTimers dummy(syncStatusFrame);
+ if (!progressDlg) abortThisProcess();
+ PauseTimers dummy(*progressDlg);
forceUiRefresh();
bool dontWarnAgain = false;
- switch (showWarningDlg(syncStatusFrame.getAsWindow(),
+ switch (showWarningDlg(progressDlg->getAsWindow(),
ReturnWarningDlg::BUTTON_IGNORE | ReturnWarningDlg::BUTTON_SWITCH | ReturnWarningDlg::BUTTON_CANCEL,
warningMessage + L"\n\n" + _("Press \"Switch\" to resolve issues in FreeFileSync main dialog."),
dontWarnAgain))
@@ -329,11 +347,12 @@ ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& er
{
case xmlAccess::ON_ERROR_POPUP:
{
- PauseTimers dummy(syncStatusFrame);
+ if (!progressDlg) abortThisProcess();
+ PauseTimers dummy(*progressDlg);
forceUiRefresh();
bool ignoreNextErrors = false;
- switch (showErrorDlg(syncStatusFrame.getAsWindow(),
+ switch (showErrorDlg(progressDlg->getAsWindow(),
ReturnErrorDlg::BUTTON_IGNORE | ReturnErrorDlg::BUTTON_RETRY | ReturnErrorDlg::BUTTON_CANCEL,
errorMessage, &ignoreNextErrors))
{
@@ -373,11 +392,12 @@ void BatchStatusHandler::reportFatalError(const std::wstring& errorMessage)
{
case xmlAccess::ON_ERROR_POPUP:
{
- PauseTimers dummy(syncStatusFrame);
+ if (!progressDlg) abortThisProcess();
+ PauseTimers dummy(*progressDlg);
forceUiRefresh();
bool ignoreNextErrors = false;
- switch (showFatalErrorDlg(syncStatusFrame.getAsWindow(),
+ switch (showFatalErrorDlg(progressDlg->getAsWindow(),
ReturnFatalErrorDlg::BUTTON_IGNORE | ReturnFatalErrorDlg::BUTTON_CANCEL,
errorMessage, &ignoreNextErrors))
{
@@ -405,12 +425,20 @@ void BatchStatusHandler::reportFatalError(const std::wstring& errorMessage)
void BatchStatusHandler::forceUiRefresh()
{
- syncStatusFrame.updateGui();
+ if (progressDlg)
+ progressDlg->updateGui();
}
void BatchStatusHandler::abortThisProcess()
{
requestAbortion(); //just make sure...
- throw BatchAbortProcess(); //abort can be triggered by syncStatusFrame
+ throw BatchAbortProcess(); //abort can be triggered by progressDlg
+}
+
+
+void BatchStatusHandler::onProgressDialogTerminate()
+{
+ //it's responsibility of "progressDlg" to call requestAbortion() when closing dialog
+ progressDlg = nullptr;
}
diff --git a/ui/batch_status_handler.h b/ui/batch_status_handler.h
index dff2f7b3..7a77785b 100644
--- a/ui/batch_status_handler.h
+++ b/ui/batch_status_handler.h
@@ -21,6 +21,7 @@
class BatchAbortProcess {};
+//BatchStatusHandler(SyncProgressDialog) will internally process Window messages! disable GUI controls to avoid unexpected callbacks!
class BatchStatusHandler : public zen::StatusHandler //throw BatchAbortProcess
{
public:
@@ -37,7 +38,7 @@ public:
std::vector<std::wstring>& execFinishedHistory);
~BatchStatusHandler();
- virtual void initNewPhase (int objectsTotal, zen::Int64 dataTotal, Phase phaseID);
+ virtual void initNewPhase (int objectsTotal, zen::Int64 dataTotal, Phase phaseID);
virtual void updateProcessedData(int objectsDelta, zen::Int64 dataDelta);
virtual void reportInfo(const std::wstring& text);
virtual void forceUiRefresh();
@@ -48,6 +49,7 @@ public:
private:
virtual void abortThisProcess(); //throw BatchAbortProcess
+ void onProgressDialogTerminate();
const zen::SwitchToGui& switchBatchToGui_; //functionality to change from batch mode to GUI mode
bool showFinalResults;
@@ -57,7 +59,7 @@ private:
zen::ErrorLog errorLog; //list of non-resolved errors and warnings
zen::FfsReturnCode& returnCode_;
- SyncProgressDialog syncStatusFrame; //the window managed by SyncStatus has longer lifetime than this handler!
+ SyncProgressDialog* progressDlg; //managed to have shorter lifetime than this handler!
std::unique_ptr<zen::FileOutput> logFile; //optional!
const std::wstring jobName_;
diff --git a/ui/column_attr.h b/ui/column_attr.h
index 5f0d79aa..399f553b 100644
--- a/ui/column_attr.h
+++ b/ui/column_attr.h
@@ -67,8 +67,9 @@ std::vector<ColumnAttributeRim> getDefaultColumnAttributesRight()
enum ColumnTypeMiddle
{
- COL_TYPE_MIDDLE_VALUE,
- COL_TYPE_BORDER
+ COL_TYPE_CHECKBOX,
+ COL_TYPE_CMP_CATEGORY,
+ COL_TYPE_SYNC_ACTION,
};
//------------------------------------------------------------------
diff --git a/ui/custom_grid.cpp b/ui/custom_grid.cpp
index 35d3248d..c1a39a0c 100644
--- a/ui/custom_grid.cpp
+++ b/ui/custom_grid.cpp
@@ -39,11 +39,6 @@ const wxColour COLOR_NOT_ACTIVE (228, 228, 228); //light grey
const Zstring ICON_FILE_FOLDER = Zstr("folder");
-
-const int CHECK_BOX_IMAGE = 12; //width of checkbox image
-const int CHECK_BOX_SPACE_LEFT = 2;
-const int CHECK_BOX_WIDTH = CHECK_BOX_SPACE_LEFT + CHECK_BOX_IMAGE; //width of first block
-
const size_t ROW_COUNT_NO_DATA = 10;
/*
@@ -95,6 +90,51 @@ std::pair<ptrdiff_t, ptrdiff_t> getVisibleRows(const Grid& grid) //returns range
}
+void fillBackgroundDefaultColorAlternating(wxDC& dc, const wxRect& rect, bool evenRowNumber)
+{
+ //alternate background color to improve readability (while lacking cell borders)
+ if (!evenRowNumber)
+ {
+ //accessibility, support high-contrast schemes => work with user-defined background color!
+ const auto backCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
+
+ auto incChannel = [](unsigned char c, int diff) { return static_cast<unsigned char>(std::max(0, std::min(255, c + diff))); };
+
+ auto getAdjustedColor = [&](int diff)
+ {
+ return wxColor(incChannel(backCol.Red (), diff),
+ incChannel(backCol.Green(), diff),
+ incChannel(backCol.Blue (), diff));
+ };
+
+ auto colorDist = [](const wxColor& lhs, const wxColor& rhs) //just some metric
+ {
+ return numeric::power<2>(static_cast<int>(lhs.Red ()) - static_cast<int>(rhs.Red ())) +
+ numeric::power<2>(static_cast<int>(lhs.Green()) - static_cast<int>(rhs.Green())) +
+ numeric::power<2>(static_cast<int>(lhs.Blue ()) - static_cast<int>(rhs.Blue ()));
+ };
+
+ const int signLevel = colorDist(backCol, *wxBLACK) < colorDist(backCol, *wxWHITE) ? 1 : -1; //brighten or darken
+
+ const wxColor colOutter = getAdjustedColor(signLevel * 14); //just some very faint gradient to avoid visual distraction
+ const wxColor colInner = getAdjustedColor(signLevel * 11); //
+
+ //clearArea(dc, rect, backColAlt);
+
+ //add some nice background gradient
+ wxRect rectUpper = rect;
+ rectUpper.height /= 2;
+ wxRect rectLower = rect;
+ rectLower.y += rectUpper.height;
+ rectLower.height -= rectUpper.height;
+ dc.GradientFillLinear(rectUpper, colOutter, colInner, wxSOUTH);
+ dc.GradientFillLinear(rectLower, colOutter, colInner, wxNORTH);
+ }
+ else
+ clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
+}
+
+
Zstring getExtension(const Zstring& shortName)
{
return contains(shortName, Zchar('.')) ? afterLast(shortName, Zchar('.')) : Zstring();
@@ -235,43 +275,8 @@ protected:
else
{
//alternate background color to improve readability (while lacking cell borders)
- if (getRowDisplayType(row) == DISP_TYPE_NORMAL && row % 2 == 1)
- {
- //accessibility, support high-contrast schemes => work with user-defined background color!
- const auto backCol = getBackGroundColor(row);
-
- auto incChannel = [](unsigned char c, int diff) { return static_cast<unsigned char>(std::max(0, std::min(255, c + diff))); };
-
- auto getAdjustedColor = [&](int diff)
- {
- return wxColor(incChannel(backCol.Red (), diff),
- incChannel(backCol.Green(), diff),
- incChannel(backCol.Blue (), diff));
- };
-
- auto colorDist = [](const wxColor& lhs, const wxColor& rhs) //just some metric
- {
- return numeric::power<2>(static_cast<int>(lhs.Red ()) - static_cast<int>(rhs.Red ())) +
- numeric::power<2>(static_cast<int>(lhs.Green()) - static_cast<int>(rhs.Green())) +
- numeric::power<2>(static_cast<int>(lhs.Blue ()) - static_cast<int>(rhs.Blue ()));
- };
-
- const int signLevel = colorDist(backCol, *wxBLACK) < colorDist(backCol, *wxWHITE) ? 1 : -1; //brighten or darken
-
- const wxColor colOutter = getAdjustedColor(signLevel * 14); //just some very faint gradient to avoid visual distraction
- const wxColor colInner = getAdjustedColor(signLevel * 11); //
-
- //clearArea(dc, rect, backColAlt);
-
- //add some nice background gradient
- wxRect rectUpper = rect;
- rectUpper.height /= 2;
- wxRect rectLower = rect;
- rectLower.y += rectUpper.height;
- rectLower.height -= rectUpper.height;
- dc.GradientFillLinear(rectUpper, colOutter, colInner, wxSOUTH);
- dc.GradientFillLinear(rectLower, colOutter, colInner, wxNORTH);
- }
+ if (getRowDisplayType(row) == DISP_TYPE_NORMAL)
+ fillBackgroundDefaultColorAlternating(dc, rect, row % 2 == 0);
else
clearArea(dc, rect, getBackGroundColor(row));
}
@@ -295,7 +300,6 @@ protected:
return COLOR_ORANGE;
case DISP_TYPE_INACTIVE:
return COLOR_NOT_ACTIVE;
-
}
return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
}
@@ -630,9 +634,7 @@ private:
}
virtual void visit(const SymLinkMapping& linkObj)
{
- iconName = linkObj.getLinkType<side>() == LinkDescriptor::TYPE_DIR ?
- ICON_FILE_FOLDER :
- linkObj.getFullName<side>();
+ iconName = linkObj.getFullName<side>();
}
virtual void visit(const DirMapping& dirObj)
{
@@ -770,16 +772,16 @@ class GridDataMiddle : public GridDataBase
public:
GridDataMiddle(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid) :
GridDataBase(grid, gridDataView),
- showSyncAction_(true),
- toolTip(grid) {} //tool tip must not live longer than grid!
+ highlightSyncAction_(false),
+ toolTip(grid), //tool tip must not live longer than grid!
+ notch(getResourceImage(L"notch").ConvertToImage()) {}
void onSelectBegin(const wxPoint& clientPos, size_t row, ColumnType colType)
{
- if (static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE &&
- row < refGrid().getRowCount())
+ if (row < refGrid().getRowCount())
{
refGrid().clearSelection(false); //don't emit event, prevent recursion!
- dragSelection = make_unique<std::pair<size_t, BlockPosition>>(row, mousePosToBlock(clientPos, row));
+ dragSelection = make_unique<std::pair<size_t, BlockPosition>>(row, mousePosToBlock(clientPos, row, static_cast<ColumnTypeMiddle>(colType)));
toolTip.hide(); //handle custom tooltip
}
}
@@ -839,22 +841,27 @@ public:
//manage block highlighting and custom tooltip
if (!dragSelection)
{
+ auto refreshHighlight = [&](size_t row)
+ {
+ refreshCell(refGrid(), row, static_cast<ColumnType>(COL_TYPE_CHECKBOX));
+ refreshCell(refGrid(), row, static_cast<ColumnType>(COL_TYPE_SYNC_ACTION));
+ };
+
const wxPoint& topLeftAbs = refGrid().CalcUnscrolledPosition(clientPos);
const size_t row = refGrid().getRowAtPos(topLeftAbs.y); //return -1 for invalid position, rowCount if one past the end
auto colInfo = refGrid().getColumnAtPos(topLeftAbs.x); //(column type, component position)
- if (row < refGrid().getRowCount() &&
- colInfo && static_cast<ColumnTypeMiddle>(colInfo->first) == COL_TYPE_MIDDLE_VALUE)
+ if (row < refGrid().getRowCount() && colInfo)
{
- if (highlight) //refresh old highlight
- refreshCell(refGrid(), highlight->row_, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE));
+ if (highlight) refreshHighlight(highlight->row_); //refresh old highlight
+
+ highlight = make_unique<MouseHighlight>(row, mousePosToBlock(clientPos, row, static_cast<ColumnTypeMiddle>(colInfo->first)));
- highlight = make_unique<MouseHighlight>(row, mousePosToBlock(clientPos, row));
- refreshCell(refGrid(), highlight->row_, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE));
+ refreshHighlight(highlight->row_);
//show custom tooltip
if (refGrid().getMainWin().GetClientRect().Contains(clientPos)) //cursor might have moved outside visible client area
- showToolTip(row, refGrid().getMainWin().ClientToScreen(clientPos));
+ showToolTip(row, static_cast<ColumnTypeMiddle>(colInfo->first), refGrid().getMainWin().ClientToScreen(clientPos));
}
else
onMouseLeave();
@@ -867,184 +874,231 @@ public:
{
if (highlight)
{
- refreshCell(refGrid(), highlight->row_, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE));
+ refreshCell(refGrid(), highlight->row_, static_cast<ColumnType>(COL_TYPE_CHECKBOX));
+ refreshCell(refGrid(), highlight->row_, static_cast<ColumnType>(COL_TYPE_SYNC_ACTION));
highlight.reset();
}
toolTip.hide(); //handle custom tooltip
}
}
- void showSyncAction(bool value) { showSyncAction_ = value; }
+ void highlightSyncAction(bool value) { highlightSyncAction_ = value; }
private:
virtual wxString getValue(size_t row, ColumnType colType) const
{
- if (static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE)
- {
- if (const FileSystemObject* fsObj = getRawData(row))
- return showSyncAction_ ? getSymbol(fsObj->getSyncOperation()) : getSymbol(fsObj->getCategory());
- }
- return wxEmptyString;
+ if (const FileSystemObject* fsObj = getRawData(row))
+ switch (static_cast<ColumnTypeMiddle>(colType))
+ {
+ case COL_TYPE_CHECKBOX:
+ break;
+ case COL_TYPE_CMP_CATEGORY:
+ return getSymbol(fsObj->getCategory());
+ case COL_TYPE_SYNC_ACTION:
+ return getSymbol(fsObj->getSyncOperation());
+ }
+ return wxString();
}
-
virtual void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected, bool hasFocus)
{
- drawCellBackground(dc, rect, enabled, selected, hasFocus, getBackGroundColor(row));
+ const FileSystemObject* fsObj = getRawData(row);
+ GridData::drawCellBackground(dc, rect, enabled, selected, hasFocus, highlightSyncAction_ ?
+ getBackGroundColorSyncAction(fsObj) :
+ getBackGroundColorCmpCategory(fsObj));
}
virtual void renderCell(Grid& grid, wxDC& dc, const wxRect& rect, size_t row, ColumnType colType)
{
switch (static_cast<ColumnTypeMiddle>(colType))
{
- case COL_TYPE_MIDDLE_VALUE:
- {
+ case COL_TYPE_CHECKBOX:
if (const FileSystemObject* fsObj = getRawData(row))
{
- //wxRect rectInside = drawCellBorder(dc, rect);
wxRect rectInside = rect;
- rectInside.width -= CHECK_BOX_SPACE_LEFT;
- rectInside.x += CHECK_BOX_SPACE_LEFT;
-
- //draw checkbox
- wxRect checkBoxArea = rectInside;
- checkBoxArea.SetWidth(CHECK_BOX_IMAGE);
+ //if sync action is shown draw notch on left side, right side otherwise
+ if (notch.GetHeight() != rectInside.GetHeight())
+ notch.Rescale(notch.GetWidth(), rectInside.GetHeight());
+ if (highlightSyncAction_)
+ {
+ drawBitmapRtlMirror(dc, notch, rectInside, wxALIGN_LEFT, buffer);
+ rectInside.x += notch.GetWidth();
+ rectInside.width -= notch.GetWidth();
+ }
+ else
+ {
+ //wxWidgets screws up again and has wxALIGN_RIGHT off by one pixel! -> use wxALIGN_LEFT instead
+ wxRect rectNotch(rectInside.x + rectInside.width - notch.GetWidth(), rectInside.y,
+ notch.GetWidth(), rectInside.height);
+ drawBitmapRtlMirror(dc, notch, rectNotch, wxALIGN_LEFT, buffer);
+ rectInside.width -= notch.GetWidth();
+ }
const bool rowHighlighted = dragSelection ? row == dragSelection->first : highlight ? row == highlight->row_ : false;
const BlockPosition highlightBlock = dragSelection ? dragSelection->second : highlight ? highlight->blockPos_ : BLOCKPOS_CHECK_BOX;
if (rowHighlighted && highlightBlock == BLOCKPOS_CHECK_BOX)
- drawBitmapRtlMirror(dc, getResourceImage(fsObj->isActive() ? L"checkboxTrueFocus" : L"checkboxFalseFocus"), checkBoxArea, wxALIGN_CENTER, buffer);
+ drawBitmapRtlMirror(dc, getResourceImage(fsObj->isActive() ? L"checkboxTrueFocus" : L"checkboxFalseFocus"), rectInside, wxALIGN_CENTER, buffer);
else //default
- drawBitmapRtlMirror(dc, getResourceImage(fsObj->isActive() ? L"checkboxTrue" : L"checkboxFalse" ), checkBoxArea, wxALIGN_CENTER, buffer);
+ drawBitmapRtlMirror(dc, getResourceImage(fsObj->isActive() ? L"checkboxTrue" : L"checkboxFalse" ), rectInside, wxALIGN_CENTER, buffer);
+ }
+ break;
- rectInside.width -= CHECK_BOX_IMAGE;
- rectInside.x += CHECK_BOX_IMAGE;
+ case COL_TYPE_CMP_CATEGORY:
+ if (const FileSystemObject* fsObj = getRawData(row))
+ {
+ if (highlightSyncAction_)
+ fillBackgroundDefaultColorAlternating(dc, rect, row % 2 == 0);
+
+ const wxBitmap& cmpImg = getCmpResultImage(fsObj->getCategory());
+ drawBitmapRtlMirror(dc, highlightSyncAction_ ? greyScale(cmpImg) : cmpImg, rect, wxALIGN_CENTER, buffer);
+ }
+ break;
+
+ case COL_TYPE_SYNC_ACTION:
+ if (const FileSystemObject* fsObj = getRawData(row))
+ {
+ if (!highlightSyncAction_)
+ fillBackgroundDefaultColorAlternating(dc, rect, row % 2 == 0);
+
+ const bool rowHighlighted = dragSelection ? row == dragSelection->first : highlight ? row == highlight->row_ : false;
+ const BlockPosition highlightBlock = dragSelection ? dragSelection->second : highlight ? highlight->blockPos_ : BLOCKPOS_CHECK_BOX;
//synchronization preview
- if (showSyncAction_)
+ if (rowHighlighted && highlightBlock != BLOCKPOS_CHECK_BOX)
+ switch (highlightBlock)
+ {
+ case BLOCKPOS_CHECK_BOX:
+ break;
+ case BLOCKPOS_LEFT:
+ drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SYNC_DIR_LEFT)), rect, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, buffer);
+ break;
+ case BLOCKPOS_MIDDLE:
+ drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SYNC_DIR_NONE)), rect, wxALIGN_CENTER, buffer);
+ break;
+ case BLOCKPOS_RIGHT:
+ drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SYNC_DIR_RIGHT)), rect, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, buffer);
+ break;
+ }
+ else //default
{
- if (rowHighlighted && highlightBlock != BLOCKPOS_CHECK_BOX)
- switch (highlightBlock)
- {
- case BLOCKPOS_CHECK_BOX:
- break;
- case BLOCKPOS_LEFT:
- drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SYNC_DIR_LEFT)), rectInside, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, buffer);
- break;
- case BLOCKPOS_MIDDLE:
- drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SYNC_DIR_NONE)), rectInside, wxALIGN_CENTER, buffer);
- break;
- case BLOCKPOS_RIGHT:
- drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SYNC_DIR_RIGHT)), rectInside, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, buffer);
- break;
- }
- else //default
- drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->getSyncOperation()), rectInside, wxALIGN_CENTER, buffer);
+ const wxBitmap& opImg = getSyncOpImage(fsObj->getSyncOperation());
+ drawBitmapRtlMirror(dc, highlightSyncAction_ ? opImg : greyScale(opImg), rect, wxALIGN_CENTER, buffer);
}
- else //comparison results view
- drawBitmapRtlMirror(dc, getCmpResultImage(fsObj->getCategory()), rectInside, wxALIGN_CENTER, buffer);
}
- }
- break;
- case COL_TYPE_BORDER:
- clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW));
break;
}
}
- virtual wxString getColumnLabel(ColumnType colType) const { return wxEmptyString; }
+ virtual wxString getColumnLabel(ColumnType colType) const
+ {
+ switch (static_cast<ColumnTypeMiddle>(colType))
+ {
+ case COL_TYPE_CHECKBOX:
+ break;
+ case COL_TYPE_CMP_CATEGORY:
+ return _("Category");
+ case COL_TYPE_SYNC_ACTION:
+ return _("Action");
+ }
+ return wxEmptyString;
+ }
+
+ virtual wxString getToolTip(ColumnType colType) const { return getColumnLabel(colType); }
virtual void renderColumnLabel(Grid& tree, wxDC& dc, const wxRect& rect, ColumnType colType, bool highlighted)
{
switch (static_cast<ColumnTypeMiddle>(colType))
{
- case COL_TYPE_MIDDLE_VALUE:
+ case COL_TYPE_CHECKBOX:
+ drawColumnLabelBackground(dc, rect, false);
+ break;
+
+ case COL_TYPE_CMP_CATEGORY:
{
wxRect rectInside = drawColumnLabelBorder(dc, rect);
drawColumnLabelBackground(dc, rectInside, highlighted);
- const wxBitmap& cmpIcon = getResourceImage(L"compareSmall");
- const wxBitmap& syncIcon = getResourceImage(L"syncSmall");
-
- const int space = 8;
- const int imageWidthTotal = cmpIcon.GetWidth() + space + syncIcon.GetWidth();
+ const wxBitmap& cmpIcon = getResourceImage(L"compareSmall");
+ drawBitmapRtlNoMirror(dc, highlightSyncAction_ ? greyScale(cmpIcon) : cmpIcon, rectInside, wxALIGN_CENTER, buffer);
+ }
+ break;
- const int posX = (rectInside.GetWidth () - imageWidthTotal) / 2;
- const int posY = (rectInside.GetHeight() - cmpIcon.GetHeight()) / 2;
- assert(cmpIcon.GetHeight() == syncIcon.GetHeight());
+ case COL_TYPE_SYNC_ACTION:
+ {
+ wxRect rectInside = drawColumnLabelBorder(dc, rect);
+ drawColumnLabelBackground(dc, rectInside, highlighted);
- dc.DrawBitmap(showSyncAction_ ? greyScale(cmpIcon) : cmpIcon, posX, posY, true);
- dc.DrawBitmap(showSyncAction_ ? syncIcon : greyScale(syncIcon), posX + cmpIcon.GetWidth() + space, posY, true);
+ const wxBitmap& syncIcon = getResourceImage(L"syncSmall");
+ drawBitmapRtlNoMirror(dc, highlightSyncAction_ ? syncIcon : greyScale(syncIcon), rectInside, wxALIGN_CENTER, buffer);
}
break;
-
- case COL_TYPE_BORDER:
- drawCellBackground(dc, rect, true, false, true, wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW));
- break;
}
}
- wxColor getBackGroundColor(size_t row) const
+ static wxColor getBackGroundColorSyncAction(const FileSystemObject* fsObj)
{
- if (const FileSystemObject* fsObj = getRawData(row))
+ if (fsObj)
{
if (!fsObj->isActive())
return COLOR_NOT_ACTIVE;
- else
- {
- if (showSyncAction_) //synchronization preview
- {
- switch (fsObj->getSyncOperation()) //evaluate comparison result and sync direction
- {
- case SO_DO_NOTHING:
- //return COLOR_NOT_ACTIVE;
- case SO_EQUAL:
- break; //usually white
-
- case SO_CREATE_NEW_LEFT:
- case SO_OVERWRITE_LEFT:
- case SO_DELETE_LEFT:
- case SO_MOVE_LEFT_SOURCE:
- case SO_MOVE_LEFT_TARGET:
- case SO_COPY_METADATA_TO_LEFT:
- return COLOR_SYNC_BLUE;
- case SO_CREATE_NEW_RIGHT:
- case SO_OVERWRITE_RIGHT:
- case SO_DELETE_RIGHT:
- case SO_MOVE_RIGHT_SOURCE:
- case SO_MOVE_RIGHT_TARGET:
- case SO_COPY_METADATA_TO_RIGHT:
- return COLOR_SYNC_GREEN;
-
- case SO_UNRESOLVED_CONFLICT:
- return COLOR_YELLOW;
- }
- }
- else //comparison results view
- {
- switch (fsObj->getCategory())
- {
- case FILE_LEFT_SIDE_ONLY:
- case FILE_LEFT_NEWER:
- return COLOR_SYNC_BLUE; //COLOR_CMP_BLUE;
+ switch (fsObj->getSyncOperation()) //evaluate comparison result and sync direction
+ {
+ case SO_DO_NOTHING:
+ return COLOR_NOT_ACTIVE;
+ case SO_EQUAL:
+ break; //usually white
+
+ case SO_CREATE_NEW_LEFT:
+ case SO_OVERWRITE_LEFT:
+ case SO_DELETE_LEFT:
+ case SO_MOVE_LEFT_SOURCE:
+ case SO_MOVE_LEFT_TARGET:
+ case SO_COPY_METADATA_TO_LEFT:
+ return COLOR_SYNC_BLUE;
+
+ case SO_CREATE_NEW_RIGHT:
+ case SO_OVERWRITE_RIGHT:
+ case SO_DELETE_RIGHT:
+ case SO_MOVE_RIGHT_SOURCE:
+ case SO_MOVE_RIGHT_TARGET:
+ case SO_COPY_METADATA_TO_RIGHT:
+ return COLOR_SYNC_GREEN;
+
+ case SO_UNRESOLVED_CONFLICT:
+ return COLOR_YELLOW;
+ }
+ }
+ return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
+ }
- case FILE_RIGHT_SIDE_ONLY:
- case FILE_RIGHT_NEWER:
- return COLOR_SYNC_GREEN; //COLOR_CMP_GREEN;
+ static wxColor getBackGroundColorCmpCategory(const FileSystemObject* fsObj)
+ {
+ if (fsObj)
+ {
+ if (!fsObj->isActive())
+ return COLOR_NOT_ACTIVE;
- case FILE_DIFFERENT:
- return COLOR_CMP_RED;
- case FILE_EQUAL:
- break; //usually white
- case FILE_CONFLICT:
- case FILE_DIFFERENT_METADATA: //= sub-category of equal, but hint via background that sync direction follows conflict-setting
- return COLOR_YELLOW;
- //return COLOR_YELLOW_LIGHT;
- }
- }
+ switch (fsObj->getCategory())
+ {
+ case FILE_LEFT_SIDE_ONLY:
+ case FILE_LEFT_NEWER:
+ return COLOR_SYNC_BLUE; //COLOR_CMP_BLUE;
+
+ case FILE_RIGHT_SIDE_ONLY:
+ case FILE_RIGHT_NEWER:
+ return COLOR_SYNC_GREEN; //COLOR_CMP_GREEN;
+
+ case FILE_DIFFERENT:
+ return COLOR_CMP_RED;
+ case FILE_EQUAL:
+ break; //usually white
+ case FILE_CONFLICT:
+ case FILE_DIFFERENT_METADATA: //= sub-category of equal, but hint via background that sync direction follows conflict-setting
+ return COLOR_YELLOW;
+ //return COLOR_YELLOW_LIGHT;
}
}
return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
@@ -1058,44 +1112,60 @@ private:
BLOCKPOS_RIGHT
};
- //determine blockposition within cell
- BlockPosition mousePosToBlock(const wxPoint& clientPos, size_t row) const
+ //determine block position within cell
+ BlockPosition mousePosToBlock(const wxPoint& clientPos, size_t row, ColumnTypeMiddle colType) const
{
- const int absX = refGrid().CalcUnscrolledPosition(clientPos).x;
-
- const wxRect rect = refGrid().getCellArea(row, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE)); //returns empty rect if column not found; absolute coordinates!
- if (rect.width > CHECK_BOX_WIDTH && rect.height > 0)
+ switch (static_cast<ColumnTypeMiddle>(colType))
{
- const FileSystemObject* const fsObj = getRawData(row);
- if (fsObj && showSyncAction_ &&
- fsObj->getSyncOperation() != SO_EQUAL) //in sync-preview equal files shall be treated as in cmp result, i.e. as full checkbox
- {
- // cell:
- // ----------------------------------
- // | checkbox | left | middle | right|
- // ----------------------------------
+ case COL_TYPE_CHECKBOX:
+ case COL_TYPE_CMP_CATEGORY:
+ break;
- const double blockWidth = (rect.GetWidth() - CHECK_BOX_WIDTH) / 3.0;
- if (rect.GetX() + CHECK_BOX_WIDTH <= absX && absX < rect.GetX() + rect.GetWidth())
- {
- if (absX < rect.GetX() + CHECK_BOX_WIDTH + blockWidth)
- return BLOCKPOS_LEFT;
- else if (absX < rect.GetX() + CHECK_BOX_WIDTH + 2 * blockWidth)
- return BLOCKPOS_MIDDLE;
- else
- return BLOCKPOS_RIGHT;
- }
+ case COL_TYPE_SYNC_ACTION:
+ {
+ const int absX = refGrid().CalcUnscrolledPosition(clientPos).x;
+
+ const wxRect rect = refGrid().getCellArea(row, static_cast<ColumnType>(COL_TYPE_SYNC_ACTION)); //returns empty rect if column not found; absolute coordinates!
+ if (rect.width > 0 && rect.height > 0)
+ if (const FileSystemObject* const fsObj = getRawData(row))
+ if (fsObj->getSyncOperation() != SO_EQUAL) //in sync-preview equal files shall be treated like a checkbox
+ // cell:
+ // -----------------------
+ // | left | middle | right|
+ // -----------------------
+ if (rect.GetX() <= absX)
+ {
+ if (absX < rect.GetX() + rect.GetWidth() / 3)
+ return BLOCKPOS_LEFT;
+ else if (absX < rect.GetX() + 2 * rect.GetWidth() / 3)
+ return BLOCKPOS_MIDDLE;
+ else if (absX < rect.GetX() + rect.GetWidth())
+ return BLOCKPOS_RIGHT;
+ }
}
-
+ break;
}
return BLOCKPOS_CHECK_BOX;
}
- void showToolTip(size_t row, wxPoint posScreen)
+ void showToolTip(size_t row, ColumnTypeMiddle colType, wxPoint posScreen)
{
if (const FileSystemObject* fsObj = getRawData(row))
{
- if (showSyncAction_) //synchronization preview
+ bool showTooltipSyncAction = true;
+ switch (colType)
+ {
+ case COL_TYPE_CHECKBOX:
+ showTooltipSyncAction = highlightSyncAction_;
+ break;
+ case COL_TYPE_CMP_CATEGORY:
+ showTooltipSyncAction = false;
+ break;
+ case COL_TYPE_SYNC_ACTION:
+ break;
+ }
+
+ if (showTooltipSyncAction) //synchronization preview
{
const wchar_t* imageName = [&]() -> const wchar_t*
{
@@ -1173,9 +1243,7 @@ private:
toolTip.hide(); //if invalid row...
}
- virtual wxString getToolTip(ColumnType colType) const { return showSyncAction_ ? _("Action") + L" (F8)" : _("Category") + L" (F8)"; }
-
- bool showSyncAction_;
+ bool highlightSyncAction_;
struct MouseHighlight
{
@@ -1184,9 +1252,10 @@ private:
const BlockPosition blockPos_;
};
std::unique_ptr<MouseHighlight> highlight; //current mouse highlight
- std::unique_ptr<std::pair<size_t, BlockPosition>> dragSelection; //(row, block)
+ std::unique_ptr<std::pair<size_t, BlockPosition>> dragSelection; //(row, block); area clicked when beginning selection
std::unique_ptr<wxBitmap> buffer; //avoid costs of recreating this temporal variable
Tooltip toolTip;
+ wxImage notch;
};
//########################################################################################################
@@ -1478,11 +1547,15 @@ void gridview::init(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, const std
//gridLeft .showScrollBars(Grid::SB_SHOW_AUTOMATIC, Grid::SB_SHOW_NEVER); -> redundant: configuration happens in GridEventManager::onAlignScrollBars()
//gridCenter.showScrollBars(Grid::SB_SHOW_NEVER, Grid::SB_SHOW_NEVER);
- gridCenter.SetSize(60 /*+ 2 * 5*/, -1);
+ const int widthCategory = 30;
+ const int widthCheckbox = getResourceImage(L"checkboxTrue").GetWidth() + 4 + getResourceImage(L"notch").GetWidth();
+ const int widthAction = 45;
+ gridCenter.SetSize(widthCategory + widthCheckbox + widthAction, -1);
+
std::vector<Grid::ColumnAttribute> attribMiddle;
- //attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_BORDER), 5, 0, true));
- attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE), 60, 0, true));
- //attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_BORDER), 5, 0, true));
+ attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_CMP_CATEGORY), widthCategory, 0, true));
+ attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_CHECKBOX ), widthCheckbox, 0, true));
+ attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_SYNC_ACTION ), widthAction, 0, true));
gridCenter.setColumnConfig(attribMiddle);
}
@@ -1629,10 +1702,10 @@ void gridview::setNavigationMarker(Grid& gridLeft,
}
-void gridview::showSyncAction(Grid& gridCenter, bool value)
+void gridview::highlightSyncAction(Grid& gridCenter, bool value)
{
if (auto* provMiddle = dynamic_cast<GridDataMiddle*>(gridCenter.getDataProvider()))
- provMiddle->showSyncAction(value);
+ provMiddle->highlightSyncAction(value);
else
assert(false);
}
diff --git a/ui/custom_grid.h b/ui/custom_grid.h
index c2e653ef..da6387f3 100644
--- a/ui/custom_grid.h
+++ b/ui/custom_grid.h
@@ -22,7 +22,7 @@ void init(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, const std::shared_p
std::vector<Grid::ColumnAttribute> convertConfig(const std::vector<ColumnAttributeRim>& attribs); //+ make consistent
std::vector<ColumnAttributeRim> convertConfig(const std::vector<Grid::ColumnAttribute>& attribs); //
-void showSyncAction(Grid& gridCenter, bool value);
+void highlightSyncAction(Grid& gridCenter, bool value);
void setupIcons(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, bool show, IconBuffer::IconSize sz);
diff --git a/ui/dir_name.cpp b/ui/dir_name.cpp
index a35de5fc..2cfc8a3d 100644
--- a/ui/dir_name.cpp
+++ b/ui/dir_name.cpp
@@ -44,8 +44,9 @@ void setDirectoryNameImpl(const wxString& dirname, wxWindow& tooltipWnd, wxStati
//change static box label only if there is a real difference to what is shown in wxTextCtrl anyway
wxString dirNormalized = dirname;
trim(dirNormalized);
- if (!dirNormalized.empty() && !endsWith(dirNormalized, FILE_NAME_SEPARATOR))
- dirNormalized += FILE_NAME_SEPARATOR;
+ if (!dirNormalized.empty())
+ if (!endsWith(dirNormalized, FILE_NAME_SEPARATOR))
+ dirNormalized += FILE_NAME_SEPARATOR;
staticText->SetLabel(dirNormalized == dirFormatted ? wxString(_("Drag && drop")) : dirFormatted);
}
diff --git a/ui/grid_view.cpp b/ui/grid_view.cpp
index 9a5143c1..a28a7ea4 100644
--- a/ui/grid_view.cpp
+++ b/ui/grid_view.cpp
@@ -554,4 +554,3 @@ void GridView::sortView(ColumnTypeRim type, bool onLeft, bool ascending)
break;
}
}
-
diff --git a/ui/gui_generated.cpp b/ui/gui_generated.cpp
index db1f03da..d2c886d8 100644
--- a/ui/gui_generated.cpp
+++ b/ui/gui_generated.cpp
@@ -790,6 +790,9 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
m_panelViewFilter = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
bSizerViewFilter = new wxBoxSizer( wxHORIZONTAL );
+ m_bpButtonViewTypeSyncAction = new ToggleButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( 42,42 ), wxBU_AUTODRAW );
+ bSizerViewFilter->Add( m_bpButtonViewTypeSyncAction, 0, wxALIGN_CENTER_VERTICAL, 5 );
+
bSizerViewFilter->Add( 0, 0, 1, wxEXPAND, 5 );
@@ -883,6 +886,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
m_bpButtonFilter->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigureFilter ), NULL, this );
m_bpButtonFilter->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnGlobalFilterContext ), NULL, this );
m_checkBoxHideExcluded->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnShowExcluded ), NULL, this );
+ m_bpButtonViewTypeSyncAction->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewType ), NULL, this );
m_bpButtonShowCreateLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
m_bpButtonShowCreateLeft->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewButtonRightClick ), NULL, this );
m_bpButtonShowUpdateLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
@@ -950,6 +954,7 @@ MainDialogGenerated::~MainDialogGenerated()
m_bpButtonFilter->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnConfigureFilter ), NULL, this );
m_bpButtonFilter->Disconnect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnGlobalFilterContext ), NULL, this );
m_checkBoxHideExcluded->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnShowExcluded ), NULL, this );
+ m_bpButtonViewTypeSyncAction->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewType ), NULL, this );
m_bpButtonShowCreateLeft->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
m_bpButtonShowCreateLeft->Disconnect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( MainDialogGenerated::OnViewButtonRightClick ), NULL, this );
m_bpButtonShowUpdateLeft->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MainDialogGenerated::OnToggleViewButton ), NULL, this );
@@ -1355,7 +1360,7 @@ SyncProgressDlgGenerated::SyncProgressDlgGenerated( wxWindow* parent, wxWindowID
bSizerRoot->Add( m_panelProgress, 1, wxEXPAND, 5 );
m_listbookResult = new wxListbook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLB_TOP );
- wxSize m_listbookResultImageSize = wxSize( 180,1 );
+ wxSize m_listbookResultImageSize = wxSize( 170,1 );
int m_listbookResultIndex = 0;
wxImageList* m_listbookResultImages = new wxImageList( m_listbookResultImageSize.GetWidth(), m_listbookResultImageSize.GetHeight() );
m_listbookResult->AssignImageList( m_listbookResultImages );
@@ -1406,7 +1411,7 @@ SyncProgressDlgGenerated::SyncProgressDlgGenerated( wxWindow* parent, wxWindowID
this->Centre( wxBOTH );
// Connect Events
- this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( SyncProgressDlgGenerated::OnCloseBtn ) );
+ this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( SyncProgressDlgGenerated::OnClose ) );
this->Connect( wxEVT_ICONIZE, wxIconizeEventHandler( SyncProgressDlgGenerated::OnIconize ) );
m_buttonClose->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncProgressDlgGenerated::OnOkay ), NULL, this );
m_buttonPause->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncProgressDlgGenerated::OnPause ), NULL, this );
@@ -1416,7 +1421,7 @@ SyncProgressDlgGenerated::SyncProgressDlgGenerated( wxWindow* parent, wxWindowID
SyncProgressDlgGenerated::~SyncProgressDlgGenerated()
{
// Disconnect Events
- this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( SyncProgressDlgGenerated::OnCloseBtn ) );
+ this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( SyncProgressDlgGenerated::OnClose ) );
this->Disconnect( wxEVT_ICONIZE, wxIconizeEventHandler( SyncProgressDlgGenerated::OnIconize ) );
m_buttonClose->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncProgressDlgGenerated::OnOkay ), NULL, this );
m_buttonPause->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SyncProgressDlgGenerated::OnPause ), NULL, this );
@@ -1998,20 +2003,20 @@ SyncCfgDlgGenerated::SyncCfgDlgGenerated( wxWindow* parent, wxWindowID id, const
m_bitmapDatabase = new wxStaticBitmap( m_panel37, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 );
bSizerConfig->Add( m_bitmapDatabase, 0, wxALIGN_CENTER_HORIZONTAL|wxTOP, 10 );
- wxBoxSizer* sbSizerKeepWidthStable;
- sbSizerKeepWidthStable = new wxBoxSizer( wxHORIZONTAL );
+ wxBoxSizer* sbSizerKeepWidthStableIfSyncDirsNotShown;
+ sbSizerKeepWidthStableIfSyncDirsNotShown = new wxBoxSizer( wxHORIZONTAL );
- sbSizerKeepWidthStable->Add( 45, 0, 0, 0, 5 );
+ sbSizerKeepWidthStableIfSyncDirsNotShown->Add( 45, 0, 0, 0, 5 );
- sbSizerKeepWidthStable->Add( 5, 0, 0, 0, 5 );
+ sbSizerKeepWidthStableIfSyncDirsNotShown->Add( 5, 0, 0, 0, 5 );
- sbSizerKeepWidthStable->Add( 46, 0, 0, 0, 5 );
+ sbSizerKeepWidthStableIfSyncDirsNotShown->Add( 46, 0, 0, 0, 5 );
- bSizerConfig->Add( sbSizerKeepWidthStable, 0, 0, 5 );
+ bSizerConfig->Add( sbSizerKeepWidthStableIfSyncDirsNotShown, 0, 0, 5 );
sbSizerSyncDirections = new wxBoxSizer( wxVERTICAL );
@@ -2626,6 +2631,7 @@ MessageDlgGenerated::MessageDlgGenerated( wxWindow* parent, wxWindowID id, const
// Connect Events
this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( MessageDlgGenerated::OnClose ) );
+ m_checkBoxCustom->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( MessageDlgGenerated::OnCheckBoxClick ), NULL, this );
m_buttonCustom1->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MessageDlgGenerated::OnButton1 ), NULL, this );
m_buttonCustom2->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MessageDlgGenerated::OnButton2 ), NULL, this );
m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MessageDlgGenerated::OnCancel ), NULL, this );
@@ -2635,6 +2641,7 @@ MessageDlgGenerated::~MessageDlgGenerated()
{
// Disconnect Events
this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( MessageDlgGenerated::OnClose ) );
+ m_checkBoxCustom->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( MessageDlgGenerated::OnCheckBoxClick ), NULL, this );
m_buttonCustom1->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MessageDlgGenerated::OnButton1 ), NULL, this );
m_buttonCustom2->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MessageDlgGenerated::OnButton2 ), NULL, this );
m_buttonCancel->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( MessageDlgGenerated::OnCancel ), NULL, this );
@@ -2653,14 +2660,14 @@ DeleteDlgGenerated::DeleteDlgGenerated( wxWindow* parent, wxWindowID id, const w
bSizer41 = new wxBoxSizer( wxHORIZONTAL );
m_bitmapDeleteType = new wxStaticBitmap( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), 0 );
- bSizer41->Add( m_bitmapDeleteType, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 );
+ bSizer41->Add( m_bitmapDeleteType, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
m_staticTextHeader = new wxStaticText( this, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticTextHeader->Wrap( -1 );
bSizer41->Add( m_staticTextHeader, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
- bSizer24->Add( bSizer41, 0, wxALIGN_CENTER_HORIZONTAL|wxALL, 5 );
+ bSizer24->Add( bSizer41, 0, 0, 5 );
m_staticline91 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
bSizer24->Add( m_staticline91, 0, wxEXPAND, 5 );
@@ -2682,6 +2689,7 @@ DeleteDlgGenerated::DeleteDlgGenerated( wxWindow* parent, wxWindowID id, const w
bSizer99->Add( m_checkBoxUseRecycler, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND|wxALL, 5 );
m_checkBoxDeleteBothSides = new wxCheckBox( this, wxID_ANY, _("Delete on both sides"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_checkBoxDeleteBothSides->Hide();
m_checkBoxDeleteBothSides->SetToolTip( _("Delete on both sides even if the file is selected on one side only") );
bSizer99->Add( m_checkBoxDeleteBothSides, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 );
diff --git a/ui/gui_generated.h b/ui/gui_generated.h
index 1f034dc2..4a7857fa 100644
--- a/ui/gui_generated.h
+++ b/ui/gui_generated.h
@@ -161,6 +161,7 @@ protected:
wxStaticText* m_staticTextCreateRight;
wxPanel* m_panelViewFilter;
wxBoxSizer* bSizerViewFilter;
+ ToggleButton* m_bpButtonViewTypeSyncAction;
ToggleButton* m_bpButtonShowCreateLeft;
ToggleButton* m_bpButtonShowUpdateLeft;
ToggleButton* m_bpButtonShowDeleteLeft;
@@ -206,6 +207,7 @@ protected:
virtual void OnConfigureFilter( wxCommandEvent& event ) { event.Skip(); }
virtual void OnGlobalFilterContext( wxMouseEvent& event ) { event.Skip(); }
virtual void OnShowExcluded( wxCommandEvent& event ) { event.Skip(); }
+ virtual void OnToggleViewType( wxCommandEvent& event ) { event.Skip(); }
virtual void OnToggleViewButton( wxCommandEvent& event ) { event.Skip(); }
virtual void OnViewButtonRightClick( wxMouseEvent& event ) { event.Skip(); }
@@ -331,7 +333,7 @@ protected:
wxButton* m_buttonAbort;
// Virtual event handlers, overide them in your derived class
- virtual void OnCloseBtn( wxCloseEvent& event ) { event.Skip(); }
+ virtual void OnClose( wxCloseEvent& event ) { event.Skip(); }
virtual void OnIconize( wxIconizeEvent& event ) { event.Skip(); }
virtual void OnOkay( wxCommandEvent& event ) { event.Skip(); }
virtual void OnPause( wxCommandEvent& event ) { event.Skip(); }
@@ -651,6 +653,7 @@ protected:
// Virtual event handlers, overide them in your derived class
virtual void OnClose( wxCloseEvent& event ) { event.Skip(); }
+ virtual void OnCheckBoxClick( wxCommandEvent& event ) { event.Skip(); }
virtual void OnButton1( wxCommandEvent& event ) { event.Skip(); }
virtual void OnButton2( wxCommandEvent& event ) { event.Skip(); }
virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); }
diff --git a/ui/gui_status_handler.cpp b/ui/gui_status_handler.cpp
index fa327136..c740bd09 100644
--- a/ui/gui_status_handler.cpp
+++ b/ui/gui_status_handler.cpp
@@ -25,39 +25,29 @@ CompareStatusHandler::CompareStatusHandler(MainDialog& dlg) :
{
wxWindowUpdateLocker dummy(&mainDlg); //avoid display distortion
- //prevent user input during "compare", do not disable maindialog since abort-button would also be disabled
- mainDlg.disableAllElements(true);
-
//display status panel during compare
mainDlg.compareStatus->init(*this); //clear old values before showing panel
mainDlg.auiMgr.GetPane(mainDlg.compareStatus->getAsWindow()).Show();
mainDlg.auiMgr.Update();
-
- //register keys
- mainDlg.Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(CompareStatusHandler::OnKeyPressed), nullptr, this);
- mainDlg.m_buttonCancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(CompareStatusHandler::OnAbortCompare), nullptr, this);
}
+
mainDlg.Update(); //don't wait until idle event!
+
+ //register keys
+ mainDlg.Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(CompareStatusHandler::OnKeyPressed), nullptr, this);
+ mainDlg.m_buttonCancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(CompareStatusHandler::OnAbortCompare), nullptr, this);
}
CompareStatusHandler::~CompareStatusHandler()
{
- updateUiNow(); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks
-
- //reenable complete main dialog
- mainDlg.enableAllElements();
-
- mainDlg.compareStatus->finalize();
- mainDlg.auiMgr.GetPane(mainDlg.compareStatus->getAsWindow()).Hide();
- mainDlg.auiMgr.Update();
-
//unregister keys
mainDlg.Disconnect(wxEVT_CHAR_HOOK, wxKeyEventHandler(CompareStatusHandler::OnKeyPressed), nullptr, this);
mainDlg.m_buttonCancel->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(CompareStatusHandler::OnAbortCompare), nullptr, this);
- if (abortIsRequested())
- mainDlg.flashStatusInformation(_("Operation aborted!"));
+ mainDlg.compareStatus->finalize();
+ mainDlg.auiMgr.GetPane(mainDlg.compareStatus->getAsWindow()).Hide();
+ mainDlg.auiMgr.Update();
}
@@ -179,17 +169,17 @@ void CompareStatusHandler::abortThisProcess()
//########################################################################################################
-SyncStatusHandler::SyncStatusHandler(MainDialog* parentDlg,
+SyncStatusHandler::SyncStatusHandler(wxTopLevelWindow* 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)
+ progressDlg(createProgressDialog(*this, [this] { this->onProgressDialogTerminate(); }, *this, parentDlg, true, jobName, execWhenFinished, execFinishedHistory)),
+ lastSyncsLogFileSizeMax_(lastSyncsLogFileSizeMax),
+ handleError_(handleError),
+ jobName_(jobName)
{
totalTime.Start(); //measure total time
}
@@ -241,35 +231,46 @@ SyncStatusHandler::~SyncStatusHandler()
}
catch (FileError&) {}
- bool showFinalResults = true;
-
- //execute "on completion" command (even in case of ignored errors)
- if (!abortIsRequested()) //if aborted (manually), we don't execute the command
+ if (progressDlg)
{
- const std::wstring finalCommand = syncStatusFrame.getExecWhenFinishedCommand(); //final value (after possible user modification)
- if (isCloseProgressDlgCommand(finalCommand))
- showFinalResults = false; //take precedence over current visibility status
- else if (!finalCommand.empty())
+ bool showFinalResults = true;
+ //execute "on completion" command (even in case of ignored errors)
+ if (!abortIsRequested()) //if aborted (manually), we don't execute the command
{
- auto cmdexp = expandMacros(utfCvrtTo<Zstring>(finalCommand));
- shellExecute(cmdexp);
+ const std::wstring finalCommand = progressDlg->getExecWhenFinishedCommand(); //final value (after possible user modification)
+ if (isCloseProgressDlgCommand(finalCommand))
+ showFinalResults = false; //take precedence over current visibility status
+ else if (!finalCommand.empty())
+ {
+ auto cmdexp = expandMacros(utfCvrtTo<Zstring>(finalCommand));
+ shellExecute(cmdexp);
+ }
}
- }
- //notify to syncStatusFrame that current process has ended
- if (showFinalResults)
- {
- if (abortIsRequested())
- syncStatusFrame.processHasFinished(SyncProgressDialog::RESULT_ABORTED, errorLog); //enable okay and close events
- else if (totalErrors > 0)
- syncStatusFrame.processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_ERROR, errorLog);
- else if (totalWarnings > 0)
- syncStatusFrame.processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_WARNINGS, errorLog);
+ //notify to progressDlg that current process has ended
+ if (showFinalResults)
+ {
+ if (abortIsRequested())
+ progressDlg->processHasFinished(SyncProgressDialog::RESULT_ABORTED, errorLog); //enable okay and close events
+ else if (totalErrors > 0)
+ progressDlg->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_ERROR, errorLog);
+ else if (totalWarnings > 0)
+ progressDlg->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_WARNINGS, errorLog);
+ else
+ progressDlg->processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_SUCCESS, errorLog);
+ }
else
- syncStatusFrame.processHasFinished(SyncProgressDialog::RESULT_FINISHED_WITH_SUCCESS, errorLog);
+ progressDlg->closeWindowDirectly();
+
+ //wait until progress dialog notified shutdown via onProgressDialogTerminate()
+ //-> required since it has our "this" pointer captured in lambda "notifyWindowTerminate"!
+ //-> nicely manages dialog lifetime
+ while (progressDlg)
+ {
+ boost::this_thread::sleep(boost::posix_time::milliseconds(UI_UPDATE_INTERVAL));
+ updateUiNow();
+ }
}
- else
- syncStatusFrame.closeWindowDirectly(); //syncStatusFrame is main window => program will quit directly
}
@@ -277,14 +278,16 @@ void SyncStatusHandler::initNewPhase(int objectsTotal, Int64 dataTotal, Phase ph
{
assert(phaseID == PHASE_SYNCHRONIZING);
StatusHandler::initNewPhase(objectsTotal, dataTotal, phaseID);
- syncStatusFrame.initNewPhase(); //call after "StatusHandler::initNewPhase"
+ if (progressDlg)
+ progressDlg->initNewPhase(); //call after "StatusHandler::initNewPhase"
}
void SyncStatusHandler::updateProcessedData(int objectsDelta, Int64 dataDelta)
{
StatusHandler::updateProcessedData(objectsDelta, dataDelta);
- syncStatusFrame.notifyProgressChange(); //noexcept
+ if (progressDlg)
+ progressDlg->notifyProgressChange(); //noexcept
//note: this method should NOT throw in order to properly allow undoing setting of statistics!
}
@@ -304,7 +307,8 @@ ProcessCallback::Response SyncStatusHandler::reportError(const std::wstring& err
{
case ON_GUIERROR_POPUP:
{
- PauseTimers dummy(syncStatusFrame);
+ if (!progressDlg) abortThisProcess();
+ PauseTimers dummy(*progressDlg);
forceUiRefresh();
bool ignoreNextErrors = false;
@@ -345,7 +349,8 @@ void SyncStatusHandler::reportFatalError(const std::wstring& errorMessage)
{
case ON_GUIERROR_POPUP:
{
- PauseTimers dummy(syncStatusFrame);
+ if (!progressDlg) abortThisProcess();
+ PauseTimers dummy(*progressDlg);
forceUiRefresh();
bool ignoreNextErrors = false;
@@ -382,7 +387,8 @@ void SyncStatusHandler::reportWarning(const std::wstring& warningMessage, bool&
{
case ON_GUIERROR_POPUP:
{
- PauseTimers dummy(syncStatusFrame);
+ if (!progressDlg) abortThisProcess();
+ PauseTimers dummy(*progressDlg);
forceUiRefresh();
bool dontWarnAgain = false;
@@ -411,12 +417,20 @@ void SyncStatusHandler::reportWarning(const std::wstring& warningMessage, bool&
void SyncStatusHandler::forceUiRefresh()
{
- syncStatusFrame.updateGui();
+ if (progressDlg)
+ progressDlg->updateGui();
}
void SyncStatusHandler::abortThisProcess()
{
requestAbortion(); //just make sure...
- throw GuiAbortProcess(); //abort can be triggered by syncStatusFrame
+ throw GuiAbortProcess(); //abort can be triggered by progressDlg
+}
+
+
+void SyncStatusHandler::onProgressDialogTerminate()
+{
+ //it's responsibility of "progressDlg" to call requestAbortion() when closing dialog
+ progressDlg = nullptr;
}
diff --git a/ui/gui_status_handler.h b/ui/gui_status_handler.h
index 312206b6..f7b58f05 100644
--- a/ui/gui_status_handler.h
+++ b/ui/gui_status_handler.h
@@ -20,13 +20,15 @@
class GuiAbortProcess {};
//classes handling sync and compare error as well as status information
+
+//CompareStatusHandler(CompareProgressDialog) will internally process Window messages! disable GUI controls to avoid unexpected callbacks!
class CompareStatusHandler : private wxEvtHandler, public zen::StatusHandler //throw GuiAbortProcess
{
public:
CompareStatusHandler(MainDialog& dlg);
~CompareStatusHandler();
- virtual void initNewPhase (int objectsTotal, zen::Int64 dataTotal, Phase phaseID);
+ virtual void initNewPhase(int objectsTotal, zen::Int64 dataTotal, Phase phaseID);
virtual void forceUiRefresh();
virtual Response reportError(const std::wstring& text);
@@ -43,10 +45,11 @@ private:
};
+//SyncStatusHandler(SyncProgressDialog) will internally process Window messages! disable GUI controls to avoid unexpected callbacks!
class SyncStatusHandler : public zen::StatusHandler
{
public:
- SyncStatusHandler(MainDialog* parentDlg,
+ SyncStatusHandler(wxTopLevelWindow* parentDlg,
size_t lastSyncsLogFileSizeMax,
xmlAccess::OnGuiError handleError,
const std::wstring& jobName,
@@ -54,7 +57,7 @@ public:
std::vector<std::wstring>& execFinishedHistory);
~SyncStatusHandler();
- virtual void initNewPhase (int objectsTotal, zen::Int64 dataTotal, Phase phaseID);
+ virtual void initNewPhase (int objectsTotal, zen::Int64 dataTotal, Phase phaseID);
virtual void updateProcessedData(int objectsDelta, zen::Int64 dataDelta);
virtual void reportInfo(const std::wstring& text);
virtual void forceUiRefresh();
@@ -65,9 +68,10 @@ public:
private:
virtual void abortThisProcess(); //throw GuiAbortProcess
+ void onProgressDialogTerminate();
- MainDialog* parentDlg_;
- SyncProgressDialog syncStatusFrame; //the window managed by SyncStatus has longer lifetime than this handler!
+ wxTopLevelWindow* parentDlg_;
+ SyncProgressDialog* progressDlg; //managed to have shorter lifetime than this handler!
const size_t lastSyncsLogFileSizeMax_;
xmlAccess::OnGuiError handleError_;
zen::ErrorLog errorLog;
diff --git a/ui/main_dlg.cpp b/ui/main_dlg.cpp
index cb05e086..bb1e0c86 100644
--- a/ui/main_dlg.cpp
+++ b/ui/main_dlg.cpp
@@ -5,16 +5,18 @@
// **************************************************************************
#include "main_dlg.h"
-#include <wx/clipbrd.h>
-#include <wx/wupdlock.h>
-#include <wx/msgdlg.h>
-#include <wx/sound.h>
-#include <wx/filedlg.h>
#include <zen/format_unit.h>
#include <zen/file_handling.h>
#include <zen/serialize.h>
//#include <zen/file_id.h>
+//#include <zen/perf.h>
#include <zen/thread.h>
+#include <wx/clipbrd.h>
+#include <wx/wupdlock.h>
+#include <wx/msgdlg.h>
+#include <wx/sound.h>
+#include <wx/filedlg.h>
+#include <wx/display.h>
#include <wx+/context_menu.h>
#include <wx+/string_conv.h>
#include <wx+/button.h>
@@ -44,7 +46,6 @@
#include "../lib/help_provider.h"
#include "../lib/lock_holder.h"
#include "../lib/localization.h"
-#include <zen/perf.h>
using namespace zen;
using namespace std::rel_ops;
@@ -52,10 +53,15 @@ using namespace std::rel_ops;
namespace
{
-struct wxClientHistoryData: public wxClientData //we need a wxClientData derived class to tell wxWidgets to take object ownership!
+struct wxClientHistoryData : public wxClientData //we need a wxClientData derived class to tell wxWidgets to take object ownership!
{
wxClientHistoryData(const Zstring& cfgFile, int lastUseIndex) : cfgFile_(cfgFile), lastUseIndex_(lastUseIndex) {}
+ //~wxClientHistoryData()
+ //{
+ // std::cerr << cfgFile_.c_str() << "\n";
+ //}
+
Zstring cfgFile_;
int lastUseIndex_; //support sorting history by last usage, the higher the index the more recent the usage
};
@@ -434,7 +440,6 @@ MainDialog::MainDialog(const xmlAccess::XmlGuiConfig& guiCfg,
const xmlAccess::XmlGlobalSettings& globalSettings,
bool startComparison) :
MainDialogGenerated(nullptr),
- showSyncAction_(false),
folderHistoryLeft (std::make_shared<FolderHistory>()), //make sure it is always bound
folderHistoryRight(std::make_shared<FolderHistory>()) //
{
@@ -465,36 +470,36 @@ MainDialog::MainDialog(const xmlAccess::XmlGuiConfig& guiCfg,
auiMgr.SetManagedWindow(this);
auiMgr.SetFlags(wxAUI_MGR_DEFAULT | wxAUI_MGR_LIVE_RESIZE);
- //caption required for all panes that can be manipulated by the users => used by context menu
- auiMgr.AddPane(m_panelTopButtons,
- 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(L"Panel9").Layer(4).Top().Row(1).CaptionVisible(false).PaneBorder(false).Hide()); //name "CmpStatus" used by context menu
-
- auiMgr.AddPane(m_panelDirectoryPairs,
- wxAuiPaneInfo().Name(L"Panel2").Layer(2).Top().Row(2).Caption(_("Folder pairs")).CaptionVisible(false).PaneBorder(false).Gripper());
-
+ //caption required for all panes that can be manipulated by the users => used by context menu
auiMgr.AddPane(m_panelCenter,
wxAuiPaneInfo().Name(L"Panel3").CenterPane().PaneBorder(false));
+ auiMgr.AddPane(m_panelDirectoryPairs,
+ wxAuiPaneInfo().Name(L"Panel2").Layer(2).Top().Caption(_("Folder pairs")).CaptionVisible(false).PaneBorder(false).Gripper());
+
auiMgr.AddPane(m_gridNavi,
- wxAuiPaneInfo().Name(L"Panel10").Left().Layer(3).Caption(_("Overview")).MinSize(300, m_gridNavi->GetSize().GetHeight())); //MinSize(): just default size, see comment below
+ wxAuiPaneInfo().Name(L"Panel10").Layer(3).Left().Caption(_("Overview")).MinSize(300, m_gridNavi->GetSize().GetHeight())); //MinSize(): just default size, see comment below
auiMgr.AddPane(m_panelConfig,
- wxAuiPaneInfo().Name(L"Panel4").Layer(4).Bottom().Row(1).Position(0).Caption(_("Configuration")).MinSize(m_listBoxHistory->GetSize().GetWidth(), m_panelConfig->GetSize().GetHeight()));
+ wxAuiPaneInfo().Name(L"Panel4").Layer(3).Left().Caption(_("Configuration")).MinSize(m_listBoxHistory->GetSize().GetWidth(), m_panelConfig->GetSize().GetHeight()));
+
+ auiMgr.AddPane(m_panelTopButtons,
+ wxAuiPaneInfo().Name(L"Panel1").Layer(4).Top().Row(1).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
+
+ auiMgr.AddPane(compareStatus->getAsWindow(),
+ wxAuiPaneInfo().Name(L"Panel9").Layer(4).Top().Row(2).CaptionVisible(false).PaneBorder(false).Hide());
auiMgr.AddPane(m_panelFilter,
- wxAuiPaneInfo().Name(L"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().Position(1).Caption(_("Filter files")).MinSize(m_bpButtonFilter->GetSize().GetWidth(), m_panelFilter->GetSize().GetHeight()));
auiMgr.AddPane(m_panelViewFilter,
- wxAuiPaneInfo().Name(L"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().Position(2).Caption(_("Select view")).MinSize(m_bpButtonShowDoNothing->GetSize().GetWidth(), m_panelViewFilter->GetSize().GetHeight()));
auiMgr.AddPane(m_panelStatistics,
- 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()));
+ wxAuiPaneInfo().Name(L"Panel7").Layer(4).Bottom().Position(3).Caption(_("Statistics")).MinSize(m_bitmapData->GetSize().GetWidth() + m_staticTextData->GetSize().GetWidth(), m_panelStatistics->GetSize().GetHeight()));
auiMgr.Update();
@@ -560,7 +565,7 @@ MainDialog::MainDialog(const xmlAccess::XmlGuiConfig& guiCfg,
new PanelMoveWindow(*this); //allow moving main dialog by clicking (nearly) anywhere... //ownership passed to "this"
#endif
- SetIcon(GlobalResources::instance().programIcon); //set application icon
+ SetIcon(GlobalResources::instance().programIconFFS); //set application icon
//notify about (logical) application main window => program won't quit, but stay on this dialog
zen::setMainWindow(this);
@@ -766,6 +771,14 @@ MainDialog::~MainDialog()
wxTheApp->Disconnect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::OnGlobalKeyEvent), nullptr, this);
wxTheApp->Disconnect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::OnGlobalKeyEvent), nullptr, this);
+#ifdef FFS_MAC
+ //more (non-portable) wxWidgets crap: wxListBox leaks wxClientData, both of the following functions fail to clean up:
+ // src/common/ctrlsub.cpp:: wxItemContainer::~wxItemContainer() -> empty function body!!!
+ // src/osx/listbox_osx.cpp: wxListBox::~wxListBox()
+ //=> finally a manual wxItemContainer::Clear() will render itself useful:
+ m_listBoxHistory->Clear();
+#endif
+
auiMgr.UnInit();
//no need for wxEventHandler::Disconnect() here; event sources are components of this window and are destroyed, too
@@ -790,14 +803,31 @@ void MainDialog::setGlobalCfgOnInit(const xmlAccess::XmlGlobalSettings& globalSe
//caveat set/get language asymmmetry! setLanguage(globalSettings.programLanguage); //throw FileError
//we need to set langugabe before creating this class!
- //set dialog size and position: test ALL parameters at once, since width/height are invalid if the window is minimized (eg x,y == -32000; height = 28, width = 160)
- //note: negative values for x and y are possible when using multiple monitors!
+ //set dialog size and position:
+ // - width/height are invalid if the window is minimized (eg x,y == -32000; height = 28, width = 160)
+ // - multi-monitor setups: dialog may be placed on second monitor which is currently turned off
if (globalSettings.gui.dlgSize.GetWidth () > 0 &&
- globalSettings.gui.dlgSize.GetHeight() > 0 &&
- globalSettings.gui.dlgPos.x >= -3360 &&
- globalSettings.gui.dlgPos.y >= -200)
- //wxDisplay::GetFromPoint(globalSettings.gui.dlgPos) != wxNOT_FOUND) //make sure upper left corner is in visible view -> not required
- SetSize(wxRect(globalSettings.gui.dlgPos, globalSettings.gui.dlgSize));
+ globalSettings.gui.dlgSize.GetHeight() > 0)
+ {
+ //calculate how much of the dialog will be visible on screen
+ const int dialogAreaTotal = globalSettings.gui.dlgSize.GetWidth() * globalSettings.gui.dlgSize.GetHeight();
+ int dialogAreaVisible = 0;
+
+ const int monitorCount = wxDisplay::GetCount();
+ for (int i = 0; i < monitorCount; ++i)
+ {
+ wxRect intersection = wxDisplay(i).GetClientArea().Intersect(wxRect(globalSettings.gui.dlgPos, globalSettings.gui.dlgSize));
+ dialogAreaVisible = std::max(dialogAreaVisible, intersection.GetWidth() * intersection.GetHeight());
+ }
+
+ if (dialogAreaVisible > 0.1 * dialogAreaTotal) //at least 10% of the dialog should be visible!
+ SetSize(wxRect(globalSettings.gui.dlgPos, globalSettings.gui.dlgSize));
+ else
+ {
+ SetSize(wxRect(globalSettings.gui.dlgSize));
+ Centre();
+ }
+ }
else
Centre();
@@ -1333,9 +1363,9 @@ void MainDialog::setStatusBarFileStatistics(size_t filesOnLeftView,
wxString statusMiddleNew;
if (gridDataView->rowsTotal() > 0)
{
- statusMiddleNew = _P("%x of 1 row in view", "%x of %y rows in view", gridDataView->rowsTotal());
- replace(statusMiddleNew, L"%x", toGuiString(gridDataView->rowsOnView()), false);
- replace(statusMiddleNew, L"%y", toGuiString(gridDataView->rowsTotal ()), false);
+ statusMiddleNew = _P("%y of 1 row in view", "%y of %x rows in view", gridDataView->rowsTotal());
+ replace(statusMiddleNew, L"%x", toGuiString(gridDataView->rowsTotal ()), false); //%x is required to be the plural form placeholder!
+ replace(statusMiddleNew, L"%y", toGuiString(gridDataView->rowsOnView()), false); //%y is secondary placeholder
}
bSizerStatusRightDirectories->Show(foldersOnRightView > 0);
@@ -1380,9 +1410,8 @@ void MainDialog::flashStatusInformation(const wxString& text)
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();
+ processAsync2([] { boost::this_thread::sleep(boost::posix_time::millisec(2500)); },
+ [this] { this->restoreStatusInformation(); });
}
@@ -1416,7 +1445,7 @@ void MainDialog::disableAllElements(bool enableAbort)
{
//when changing consider: comparison, synchronization, manual deletion
- EnableCloseButton(false); //not allowed for synchronization! progress indicator is top window!
+ EnableCloseButton(false); //not allowed for synchronization! progress indicator is top window! -> not honored on OS X!
//disables all elements (except abort button) that might receive user input during long-running processes: comparison, deletion
m_panelViewFilter ->Disable();
@@ -1764,7 +1793,7 @@ void MainDialog::OnGlobalKeyEvent(wxKeyEvent& event) //process key events withou
return; //-> swallow event!
case WXK_F8: //F8
- showSyncAction(!showSyncAction_);
+ setViewTypeSyncAction(!m_bpButtonViewTypeSyncAction->isActive());
return; //-> swallow event!
//redirect certain (unhandled) keys directly to grid!
@@ -1878,7 +1907,7 @@ void MainDialog::onNaviGridContext(GridClickEvent& event)
ContextMenu menu;
//----------------------------------------------------------------------------------------------------
- if (showSyncAction_ && !selection.empty())
+ if (!selection.empty())
//std::any_of(selection.begin(), selection.end(), [](const FileSystemObject* fsObj){ return fsObj->getSyncOperation() != SO_EQUAL; })) -> doesn't consider directories
{
auto getImage = [&](SyncDirection dir, SyncOperation soDefault)
@@ -1971,7 +2000,7 @@ void MainDialog::onMainGridContextRim(bool leftSide)
const auto& selection = getGridSelection(); //referenced by lambdas!
ContextMenu menu;
- if (showSyncAction_ && !selection.empty())
+ if (!selection.empty())
{
auto getImage = [&](SyncDirection dir, SyncOperation soDefault)
{
@@ -2158,8 +2187,13 @@ void MainDialog::excludeItems(const std::vector<FileSystemObject*>& selection)
void MainDialog::onGridLabelContextC(GridClickEvent& event)
{
ContextMenu menu;
- menu.addItem(_("Category") + L"\tF8", [&] { showSyncAction(false); }, showSyncAction_ ? nullptr : &getResourceImage(L"compareSmall"));
- menu.addItem(_("Action"), [&] { showSyncAction(true ); }, showSyncAction_ ? &getResourceImage(L"syncSmall") : nullptr);
+
+ const bool actionView = m_bpButtonViewTypeSyncAction->isActive();
+ menu.addRadio(_("Category") + (actionView ? L"\tF8" : L""), [&] { setViewTypeSyncAction(false); }, !actionView);
+ menu.addRadio(_("Action") + (!actionView ? L"\tF8" : L""), [&] { setViewTypeSyncAction(true ); }, actionView);
+
+ //menu.addItem(_("Category") + L"\tF8", [&] { setViewTypeSyncAction(false); }, m_bpButtonViewTypeSyncAction->isActive() ? nullptr : &getResourceImage(L"compareSmall"));
+ //menu.addItem(_("Action"), [&] { setViewTypeSyncAction(true ); }, m_bpButtonViewTypeSyncAction->isActive() ? &getResourceImage(L"syncSmall") : nullptr);
menu.popup(*this);
}
@@ -2450,9 +2484,7 @@ void MainDialog::removeObsoleteCfgHistoryItems(const std::vector<Zstring>& filen
return missingFiles;
};
- asyncTasks.add(getMissingFilesAsync,
- [this](const std::vector<Zstring>& files) { removeCfgHistoryItems(files); });
- startProcessingAsyncTasks();
+ processAsync(getMissingFilesAsync, [this](const std::vector<Zstring>& files) { removeCfgHistoryItems(files); });
}
@@ -2605,7 +2637,7 @@ bool MainDialog::trySaveConfig(const Zstring* fileNameGui) //return true if save
bool MainDialog::trySaveBatchConfig(const Zstring* fileNameBatch)
{
- //essentially behave like trySaveConfig(): the collateral damage of not saving GUI-only settings "hideExcludedItems, showSyncAction" is negliable
+ //essentially behave like trySaveConfig(): the collateral damage of not saving GUI-only settings "hideExcludedItems, m_bpButtonViewTypeSyncAction" is negliable
const xmlAccess::XmlGuiConfig guiCfg = getConfig();
@@ -2681,7 +2713,7 @@ bool MainDialog::saveOldConfig() //return false on user abort
QuestConfig().setCaption(toWx(activeCfgFilename)).
setLabelYes(_("&Save")).
setLabelNo(_("Do&n't save")).
- showCheckBox(neverSave, _("Never save changes"))))
+ showCheckBox(neverSave, _("Never save changes"), ReturnQuestionDlg::BUTTON_YES)))
{
case ReturnQuestionDlg::BUTTON_YES:
@@ -2983,7 +3015,7 @@ void MainDialog::setConfig(const xmlAccess::XmlGuiConfig& newGuiCfg, const std::
//read GUI layout
m_checkBoxHideExcluded->SetValue(currentCfg.hideExcludedItems);
- showSyncAction(currentCfg.showSyncAction);
+ setViewTypeSyncAction(currentCfg.highlightSyncAction);
//###########################################################
//update compare variant name
@@ -3029,7 +3061,7 @@ xmlAccess::XmlGuiConfig MainDialog::getConfig() const
std::back_inserter(guiCfg.mainCfg.additionalPairs), getEnhancedPair);
//sync preview
- guiCfg.showSyncAction = showSyncAction_;
+ guiCfg.highlightSyncAction = m_bpButtonViewTypeSyncAction->isActive();
return guiCfg;
}
@@ -3113,6 +3145,12 @@ void MainDialog::OnGlobalFilterContext(wxMouseEvent& event)
}
+void MainDialog::OnToggleViewType(wxCommandEvent& event)
+{
+ setViewTypeSyncAction(!m_bpButtonViewTypeSyncAction->isActive()); //toggle view
+}
+
+
void MainDialog::OnToggleViewButton(wxCommandEvent& event)
{
if (auto button = dynamic_cast<ToggleButton*>(event.GetEventObject()))
@@ -3147,63 +3185,28 @@ wxBitmap buttonReleased(const std::string& name)
void MainDialog::initViewFilterButtons()
{
- //compare result buttons
- m_bpButtonShowLeftOnly->init(buttonPressed("leftOnly"),
- buttonReleased("leftOnly"),
- _("Show files that exist on left side only"));
-
- m_bpButtonShowRightOnly->init(buttonPressed("rightOnly"),
- buttonReleased("rightOnly"),
- _("Show files that exist on right side only"));
-
- m_bpButtonShowLeftNewer->init(buttonPressed("leftNewer"),
- buttonReleased("leftNewer"),
- _("Show files that are newer on left"));
-
- m_bpButtonShowRightNewer->init(buttonPressed("rightNewer"),
- buttonReleased("rightNewer"),
- _("Show files that are newer on right"));
+ m_bpButtonViewTypeSyncAction->init(getResourceImage(L"viewTypeSyncAction"), getResourceImage(L"viewTypeCmpResult"));
+ //tooltip is updated dynamically in setViewTypeSyncAction()
- m_bpButtonShowEqual->init(buttonPressed("equal"),
- buttonReleased("equal"),
- _("Show files that are equal"));
+ auto initButton = [](ToggleButton& btn, const char* imgName, const wxString& tooltip) { btn.init(buttonPressed(imgName), buttonReleased(imgName)); btn.SetToolTip(tooltip); };
- m_bpButtonShowDifferent->init(buttonPressed("different"),
- buttonReleased("different"),
- _("Show files that are different"));
-
- m_bpButtonShowConflict->init(buttonPressed("conflict"),
- buttonReleased("conflict"),
- _("Show conflicts"));
+ //compare result buttons
+ initButton(*m_bpButtonShowLeftOnly, "leftOnly", _("Show files that exist on left side only"));
+ initButton(*m_bpButtonShowRightOnly, "rightOnly", _("Show files that exist on right side only"));
+ initButton(*m_bpButtonShowLeftNewer, "leftNewer", _("Show files that are newer on left"));
+ initButton(*m_bpButtonShowRightNewer, "rightNewer", _("Show files that are newer on right"));
+ initButton(*m_bpButtonShowEqual, "equal", _("Show files that are equal"));
+ initButton(*m_bpButtonShowDifferent, "different", _("Show files that are different"));
+ initButton(*m_bpButtonShowConflict, "conflict", _("Show conflicts"));
//sync preview buttons
- m_bpButtonShowCreateLeft->init(buttonPressed("createLeft"),
- buttonReleased("createLeft"),
- _("Show files that will be created on the left side"));
-
- m_bpButtonShowCreateRight->init(buttonPressed("createRight"),
- buttonReleased("createRight"),
- _("Show files that will be created on the right side"));
-
- m_bpButtonShowDeleteLeft->init(buttonPressed("deleteLeft"),
- buttonReleased("deleteLeft"),
- _("Show files that will be deleted on the left side"));
-
- m_bpButtonShowDeleteRight->init(buttonPressed("deleteRight"),
- buttonReleased("deleteRight"),
- _("Show files that will be deleted on the right side"));
-
- m_bpButtonShowUpdateLeft->init(buttonPressed("updateLeft"),
- buttonReleased("updateLeft"),
- _("Show files that will be overwritten on left side"));
-
- m_bpButtonShowUpdateRight->init(buttonPressed("updateRight"),
- buttonReleased("updateRight"),
- _("Show files that will be overwritten on right side"));
-
- m_bpButtonShowDoNothing->init(buttonPressed("none"),
- buttonReleased("none"),
- _("Show files that won't be copied"));
+ initButton(*m_bpButtonShowCreateLeft, "createLeft", _("Show files that will be created on the left side"));
+ initButton(*m_bpButtonShowCreateRight, "createRight", _("Show files that will be created on the right side"));
+ initButton(*m_bpButtonShowDeleteLeft, "deleteLeft", _("Show files that will be deleted on the left side"));
+ initButton(*m_bpButtonShowDeleteRight, "deleteRight", _("Show files that will be deleted on the right side"));
+ initButton(*m_bpButtonShowUpdateLeft, "updateLeft", _("Show files that will be overwritten on left side"));
+ initButton(*m_bpButtonShowUpdateRight, "updateRight", _("Show files that will be overwritten on right side"));
+ initButton(*m_bpButtonShowDoNothing, "none", _("Show files that won't be copied"));
}
@@ -3299,6 +3302,9 @@ void MainDialog::OnCompare(wxCommandEvent& event)
clearGrid(); //avoid memory peak by clearing old data first
+ disableAllElements(true); //CompareStatusHandler will internally process Window messages, so avoid unexpected callbacks!
+ ZEN_ON_SCOPE_EXIT(updateUiNow(); enableAllElements()); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks
+
try
{
//class handling status display and error messages
@@ -3322,6 +3328,7 @@ void MainDialog::OnCompare(wxCommandEvent& event)
}
catch (GuiAbortProcess&)
{
+ flashStatusInformation(_("Operation aborted!"));
// if (m_buttonCompare->IsShownOnScreen()) m_buttonCompare->SetFocus();
updateGui(); //refresh grid in ANY case! (also on abort)
return;
@@ -3337,9 +3344,9 @@ void MainDialog::OnCompare(wxCommandEvent& event)
m_gridNavi->clearSelection();
//play (optional) sound notification after sync has completed (GUI and batch mode)
- const Zstring soundFile = zen::getResourceDir() + Zstr("Compare_Complete.wav");
- if (fileExists(soundFile))
- wxSound::Play(toWx(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()));
@@ -3451,10 +3458,10 @@ void MainDialog::applyCompareConfig(bool switchMiddleGrid)
switch (currentCfg.mainCfg.cmpConfig.compareVar)
{
case CMP_BY_TIME_SIZE:
- showSyncAction(true);
+ setViewTypeSyncAction(true);
break;
case CMP_BY_CONTENT:
- showSyncAction(false);
+ setViewTypeSyncAction(false);
break;
}
}
@@ -3515,6 +3522,9 @@ void MainDialog::OnStartSync(wxCommandEvent& event)
const auto& guiCfg = getConfig();
+ disableAllElements(false); //SyncStatusHandler will internally process Window messages, so avoid unexpected callbacks!
+ ZEN_ON_SCOPE_EXIT(enableAllElements());
+
//class handling status updates and error messages
SyncStatusHandler statusHandler(this, //throw GuiAbortProcess
globalCfg.lastSyncsLogFileSizeMax,
@@ -3560,7 +3570,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event)
//do NOT disable the sync button: user might want to try to sync the REMAINING rows
} //enableSynchronization(false);
- //remove rows that empty: just a beautification, invalid rows shouldn't cause issues
+ //remove empty rows: just a beautification, invalid rows shouldn't cause issues
gridDataView->removeInvalidRows();
updateGui();
@@ -3616,7 +3626,7 @@ void MainDialog::onGridLabelLeftClickR(GridClickEvent& event)
void MainDialog::onGridLabelLeftClickC(GridClickEvent& event)
{
//sorting middle grid is more or less useless: therefore let's toggle view instead!
- showSyncAction(!showSyncAction_); //toggle view
+ setViewTypeSyncAction(!m_bpButtonViewTypeSyncAction->isActive()); //toggle view
}
@@ -3696,7 +3706,7 @@ void MainDialog::updateGridViewData()
m_bpButtonShowUpdateRight->Show(false);
m_bpButtonShowDoNothing ->Show(false);
- if (showSyncAction_)
+ if (m_bpButtonViewTypeSyncAction->isActive())
{
const GridView::StatusSyncPreview result = gridDataView->updateSyncPreview(currentCfg.hideExcludedItems,
m_bpButtonShowCreateLeft ->isActive(),
@@ -3727,22 +3737,24 @@ void MainDialog::updateGridViewData()
m_bpButtonShowEqual ->Show(result.existsSyncEqual);
m_bpButtonShowConflict ->Show(result.existsConflict);
- if (m_bpButtonShowCreateLeft ->IsShown() ||
- m_bpButtonShowCreateRight->IsShown() ||
- m_bpButtonShowDeleteLeft ->IsShown() ||
- m_bpButtonShowDeleteRight->IsShown() ||
- m_bpButtonShowUpdateLeft ->IsShown() ||
- m_bpButtonShowUpdateRight->IsShown() ||
- m_bpButtonShowDoNothing ->IsShown() ||
- m_bpButtonShowEqual ->IsShown() ||
- m_bpButtonShowConflict ->IsShown())
+ const bool anyViewFilterButtonShown = m_bpButtonShowCreateLeft ->IsShown() ||
+ m_bpButtonShowCreateRight->IsShown() ||
+ m_bpButtonShowDeleteLeft ->IsShown() ||
+ m_bpButtonShowDeleteRight->IsShown() ||
+ m_bpButtonShowUpdateLeft ->IsShown() ||
+ m_bpButtonShowUpdateRight->IsShown() ||
+ m_bpButtonShowDoNothing ->IsShown() ||
+ m_bpButtonShowEqual ->IsShown() ||
+ m_bpButtonShowConflict ->IsShown();
+ m_bpButtonViewTypeSyncAction->Show(anyViewFilterButtonShown);
+
+ if (anyViewFilterButtonShown)
{
m_panelViewFilter->Show();
m_panelViewFilter->Layout();
}
else
m_panelViewFilter->Hide();
-
}
else
{
@@ -3770,13 +3782,16 @@ void MainDialog::updateGridViewData()
m_bpButtonShowEqual ->Show(result.existsEqual);
m_bpButtonShowConflict ->Show(result.existsConflict);
- if (m_bpButtonShowLeftOnly ->IsShown() ||
- m_bpButtonShowRightOnly ->IsShown() ||
- m_bpButtonShowLeftNewer ->IsShown() ||
- m_bpButtonShowRightNewer->IsShown() ||
- m_bpButtonShowDifferent ->IsShown() ||
- m_bpButtonShowEqual ->IsShown() ||
- m_bpButtonShowConflict ->IsShown())
+ const bool anyViewFilterButtonShown = m_bpButtonShowLeftOnly ->IsShown() ||
+ m_bpButtonShowRightOnly ->IsShown() ||
+ m_bpButtonShowLeftNewer ->IsShown() ||
+ m_bpButtonShowRightNewer->IsShown() ||
+ m_bpButtonShowDifferent ->IsShown() ||
+ m_bpButtonShowEqual ->IsShown() ||
+ m_bpButtonShowConflict ->IsShown();
+ m_bpButtonViewTypeSyncAction->Show(anyViewFilterButtonShown);
+
+ if (anyViewFilterButtonShown)
{
m_panelViewFilter->Show();
m_panelViewFilter->Layout();
@@ -3788,7 +3803,7 @@ void MainDialog::updateGridViewData()
gridview::refresh(*m_gridMainL, *m_gridMainC, *m_gridMainR);
//navigation tree
- if (showSyncAction_)
+ if (m_bpButtonViewTypeSyncAction->isActive())
treeDataView->updateSyncPreview(currentCfg.hideExcludedItems,
m_bpButtonShowCreateLeft ->isActive(),
m_bpButtonShowCreateRight->isActive(),
@@ -3894,11 +3909,11 @@ void MainDialog::OnRemoveFolderPair(wxCommandEvent& event)
wxWindowUpdateLocker dummy(this); //avoid display distortion
const wxObject* const eventObj = event.GetEventObject(); //find folder pair originating the event
- for (std::vector<DirectoryPair*>::const_iterator it = additionalFolderPairs.begin(); it != additionalFolderPairs.end(); ++it)
+ for (auto it = additionalFolderPairs.begin(); it != additionalFolderPairs.end(); ++it)
if (eventObj == (*it)->m_bpButtonRemovePair)
{
removeAddFolderPair(it - additionalFolderPairs.begin());
- return;
+ break;
}
}
@@ -3998,10 +4013,10 @@ void MainDialog::addFolderPair(const std::vector<FolderPairEnh>& newPairs, bool
newPair->m_bpButtonRemovePair->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainDialog::OnRemoveFolderPair), nullptr, this);
});
- //wxComboBox screws up miserably if width/height is smaller than the magic number 4! Problem occurs when trying to set tooltip
- //so we have to update window sizes before setting configuration
updateGuiForFolderPair();
+ //wxComboBox screws up miserably if width/height is smaller than the magic number 4! Problem occurs when trying to set tooltip
+ //so we have to update window sizes before setting configuration:
for (auto it = newPairs.begin(); it != newPairs.end(); ++it)//set alternate configuration
newEntries[it - newPairs.begin()]->setValues(it->leftDirectory,
it->rightDirectory,
@@ -4024,8 +4039,12 @@ void MainDialog::removeAddFolderPair(size_t pos)
//const int pairHeight = pairToDelete->GetSize().GetHeight();
bSizerAddFolderPairs->Detach(pairToDelete); //Remove() does not work on Window*, so do it manually
- pairToDelete->Destroy(); //
additionalFolderPairs.erase(additionalFolderPairs.begin() + pos); //remove element from vector
+ //more (non-portable) wxWidgets bullshit: on OS X wxWindow::Destroy() screws up and calls "operator delete" directly rather than
+ //the deferred deletion it is expected to do (and which is implemented correctly on Windows and Linux)
+ //http://bb10.com/python-wxpython-devel/2012-09/msg00004.html
+ //=> since we're in a mouse button callback of a sub-component of "pairToDelete" we need to delay deletion ourselves:
+ processAsync2([] {}, [pairToDelete] { pairToDelete->Destroy(); });
//set size of scrolled window
//const size_t additionalRows = additionalFolderPairs.size();
@@ -4083,7 +4102,7 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event)
//http://en.wikipedia.org/wiki/Comma-separated_values
const lconv* localInfo = ::localeconv(); //always bound according to doc
- const bool haveCommaAsDecimalSep = strcmp(localInfo->decimal_point, ",") == 0;
+ const bool haveCommaAsDecimalSep = std::string(localInfo->decimal_point) == ",";
const char CSV_SEP = haveCommaAsDecimalSep ? ';' : ',';
@@ -4100,32 +4119,6 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event)
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
- header += fmtValue(_("Legend")) + '\n';
- if (showSyncAction_)
- {
- header += fmtValue(getSyncOpDescription(SO_EQUAL)) + CSV_SEP + fmtValue(getSymbol(SO_EQUAL)) + '\n';
- header += fmtValue(getSyncOpDescription(SO_CREATE_NEW_LEFT)) + CSV_SEP + fmtValue(getSymbol(SO_CREATE_NEW_LEFT)) + '\n';
- header += fmtValue(getSyncOpDescription(SO_CREATE_NEW_RIGHT)) + CSV_SEP + fmtValue(getSymbol(SO_CREATE_NEW_RIGHT)) + '\n';
- header += fmtValue(getSyncOpDescription(SO_OVERWRITE_LEFT)) + CSV_SEP + fmtValue(getSymbol(SO_OVERWRITE_LEFT)) + '\n';
- header += fmtValue(getSyncOpDescription(SO_OVERWRITE_RIGHT)) + CSV_SEP + fmtValue(getSymbol(SO_OVERWRITE_RIGHT)) + '\n';
- header += fmtValue(getSyncOpDescription(SO_DELETE_LEFT)) + CSV_SEP + fmtValue(getSymbol(SO_DELETE_LEFT)) + '\n';
- header += fmtValue(getSyncOpDescription(SO_DELETE_RIGHT)) + CSV_SEP + fmtValue(getSymbol(SO_DELETE_RIGHT)) + '\n';
- header += fmtValue(getSyncOpDescription(SO_DO_NOTHING)) + CSV_SEP + fmtValue(getSymbol(SO_DO_NOTHING)) + '\n';
- header += fmtValue(getSyncOpDescription(SO_UNRESOLVED_CONFLICT)) + CSV_SEP + fmtValue(getSymbol(SO_UNRESOLVED_CONFLICT)) + '\n';
- }
- else
- {
- header += fmtValue(getCategoryDescription(FILE_EQUAL)) + CSV_SEP + fmtValue(getSymbol(FILE_EQUAL)) + '\n';
- header += fmtValue(getCategoryDescription(FILE_DIFFERENT)) + CSV_SEP + fmtValue(getSymbol(FILE_DIFFERENT)) + '\n';
- header += fmtValue(getCategoryDescription(FILE_LEFT_SIDE_ONLY)) + CSV_SEP + fmtValue(getSymbol(FILE_LEFT_SIDE_ONLY)) + '\n';
- header += fmtValue(getCategoryDescription(FILE_RIGHT_SIDE_ONLY)) + CSV_SEP + fmtValue(getSymbol(FILE_RIGHT_SIDE_ONLY)) + '\n';
- header += fmtValue(getCategoryDescription(FILE_LEFT_NEWER)) + CSV_SEP + fmtValue(getSymbol(FILE_LEFT_NEWER)) + '\n';
- header += fmtValue(getCategoryDescription(FILE_RIGHT_NEWER)) + CSV_SEP + fmtValue(getSymbol(FILE_RIGHT_NEWER)) + '\n';
- header += fmtValue(getCategoryDescription(FILE_CONFLICT)) + CSV_SEP + fmtValue(getSymbol(FILE_CONFLICT)) + '\n';
- }
- header += '\n';
-
//base folders
header += fmtValue(_("Folder pairs")) + '\n' ;
std::for_each(begin(folderCmp), end(folderCmp),
@@ -4146,7 +4139,7 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event)
auto colAttrRight = m_gridMainR->getColumnConfig();
vector_remove_if(colAttrLeft , [](const Grid::ColumnAttribute& ca) { return !ca.visible_; });
- vector_remove_if(colAttrMiddle, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; });
+ vector_remove_if(colAttrMiddle, [](const Grid::ColumnAttribute& ca) { return !ca.visible_ || static_cast<ColumnTypeMiddle>(ca.type_) == COL_TYPE_CHECKBOX; });
vector_remove_if(colAttrRight , [](const Grid::ColumnAttribute& ca) { return !ca.visible_; });
if (provLeft && provMiddle && provRight)
@@ -4206,16 +4199,12 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event)
tmp += fmtValue(provMiddle->getValue(row, ca.type_));
tmp += CSV_SEP;
});
- if (!colAttrRight.empty())
+ std::for_each(colAttrRight.begin(), colAttrRight.end(),
+ [&](const Grid::ColumnAttribute& ca)
{
- std::for_each(colAttrRight.begin(), colAttrRight.end() - 1,
- [&](const Grid::ColumnAttribute& ca)
- {
- tmp += fmtValue(provRight->getValue(row, ca.type_));
- tmp += CSV_SEP;
- });
- tmp += fmtValue(provRight->getValue(row, colAttrRight.back().type_));
- }
+ tmp += fmtValue(provRight->getValue(row, ca.type_));
+ tmp += CSV_SEP;
+ });
tmp += '\n';
replace(tmp, '\n', LINE_BREAK);
@@ -4295,6 +4284,10 @@ void MainDialog::switchProgramLanguage(int langID)
//show new dialog, then delete old one
MainDialog::create(getConfig(), activeConfigFiles, newGlobalCfg, false);
+
+ //we don't use Close():
+ //1. we don't want to show the prompt to save current config in OnClose()
+ //2. after getGlobalCfgBeforeExit() the old main dialog is invalid so we want to force deletion
Destroy();
}
@@ -4308,12 +4301,15 @@ void MainDialog::OnMenuLanguageSwitch(wxCommandEvent& event)
//#########################################################################################################
-void MainDialog::showSyncAction(bool value)
+void MainDialog::setViewTypeSyncAction(bool value)
{
- showSyncAction_ = value;
+ //if (m_bpButtonViewTypeSyncAction->isActive() == value) return; support polling -> what about initialization?
+
+ m_bpButtonViewTypeSyncAction->setActive(value);
+ m_bpButtonViewTypeSyncAction->SetToolTip((value ? _("Action") : _("Category")) + L" (F8)");
//toggle display of sync preview in middle grid
- gridview::showSyncAction(*m_gridMainC, value);
+ gridview::highlightSyncAction(*m_gridMainC, value);
updateGui();
}
diff --git a/ui/main_dlg.h b/ui/main_dlg.h
index 70f78e93..2d132814 100644
--- a/ui/main_dlg.h
+++ b/ui/main_dlg.h
@@ -127,9 +127,13 @@ private:
void openExternalApplication(const wxString& commandline, const std::vector<zen::FileSystemObject*>& selection, bool leftSide); //selection may be empty
//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);
+ template <class Fun, class Fun2>
+ void processAsync(Fun doAsync, Fun2 evalOnGui) { asyncTasks.add(doAsync, evalOnGui); timerForAsyncTasks.Start(50); /*timer interval in [ms] */ }
+ template <class Fun, class Fun2>
+ void processAsync2(Fun doAsync, Fun2 evalOnGui) { asyncTasks.add2(doAsync, evalOnGui); timerForAsyncTasks.Start(50); /*timer interval in [ms] */ }
+
//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);
@@ -186,6 +190,7 @@ private:
void onGridLabelContextR(zen::GridClickEvent& event);
void onGridLabelContext(zen::Grid& grid, zen::ColumnTypeRim type, const std::vector<zen::ColumnAttributeRim>& defaultColumnAttributes);
+ void OnToggleViewType (wxCommandEvent& event);
void OnToggleViewButton(wxCommandEvent& event);
void OnConfigNew (wxCommandEvent& event);
@@ -279,9 +284,10 @@ private:
bool processingGlobalKeyEvent; //indicator to notify recursion in OnGlobalKeyEvent()
- bool showSyncAction_; //toggle to display configuration preview instead of comparison result
- //use this methods when changing values!
- void showSyncAction(bool value);
+ //toggle to display configuration preview instead of comparison result:
+ //for read access use: m_bpButtonViewTypeSyncAction->isActive()
+ //when changing value use:
+ void setViewTypeSyncAction(bool value);
wxAuiManager auiMgr; //implement dockable GUI design
diff --git a/ui/msg_popup.cpp b/ui/msg_popup.cpp
index 940163af..59908cf9 100644
--- a/ui/msg_popup.cpp
+++ b/ui/msg_popup.cpp
@@ -35,6 +35,8 @@ private:
void OnCancel(wxCommandEvent& event) { EndModal(ReturnErrorDlg::BUTTON_CANCEL); }
void OnButton1(wxCommandEvent& event);
void OnButton2(wxCommandEvent& event);
+ void OnCheckBoxClick(wxCommandEvent& event) { updateGui(); event.Skip(); }
+ void updateGui();
bool* ignoreErrors;
wxButton& buttonRetry; //
@@ -87,9 +89,16 @@ ErrorDlg::ErrorDlg(wxWindow* parent, int activeButtons, const wxString& messageT
else if (activeButtons & ReturnErrorDlg::BUTTON_CANCEL)
setAsStandard(*m_buttonCancel);
+ updateGui();
Fit(); //child-element widths have changed: image was set
}
+void ErrorDlg::updateGui()
+{
+ //button doesn't make sense when checkbox is set!
+ buttonRetry.Enable(!checkBoxIgnoreErrors.GetValue());
+}
+
void ErrorDlg::OnButton1(wxCommandEvent& event) //retry
{
@@ -135,6 +144,8 @@ private:
void OnCancel(wxCommandEvent& event) { EndModal(ReturnWarningDlg::BUTTON_CANCEL); }
void OnButton1(wxCommandEvent& event);
void OnButton2(wxCommandEvent& event);
+ void OnCheckBoxClick(wxCommandEvent& event) { updateGui(); event.Skip(); }
+ void updateGui();
bool& dontShowAgain;
wxButton& buttonIgnore; //
@@ -182,10 +193,18 @@ WarningDlg::WarningDlg(wxWindow* parent, int activeButtons, const wxString& mes
else if (activeButtons & ReturnWarningDlg::BUTTON_CANCEL)
setAsStandard(*m_buttonCancel);
+ updateGui();
Fit(); //child-element widths have changed: image was set
}
+void WarningDlg::updateGui()
+{
+ //button doesn't make sense when checkbox is set!
+ buttonSwitch.Enable(!checkBoxDontShowAgain.GetValue());
+}
+
+
void WarningDlg::OnButton1(wxCommandEvent& event) //ignore
{
dontShowAgain = checkBoxDontShowAgain.GetValue();
@@ -212,49 +231,49 @@ ReturnWarningDlg::ButtonPressed zen::showWarningDlg(wxWindow* parent, int active
class QuestionDlg : public MessageDlgGenerated
{
public:
- QuestionDlg(wxWindow* parent, int activeButtons, const wxString& messageText, const wxString& caption, const wxString& labelYes, const wxString& labelNo, bool* checkBoxValue, const wxString& checkBoxLabel);
+ QuestionDlg(wxWindow* parent, int activeButtons, const wxString& messageText, const QuestConfig& cfg);
private:
void OnClose (wxCloseEvent& event) { EndModal(ReturnQuestionDlg::BUTTON_CANCEL); }
void OnCancel(wxCommandEvent& event) { EndModal(ReturnQuestionDlg::BUTTON_CANCEL); }
void OnButton1(wxCommandEvent& event);
void OnButton2(wxCommandEvent& event);
+ void OnCheckBoxClick(wxCommandEvent& event) { updateGui(); event.Skip(); }
+ void updateGui();
- bool* checkBoxValue_; //optional
wxButton& buttonYes; // map generic controls
wxButton& buttonNo; //
+
+ bool* const checkBoxValue_; //optional
+ const int disabledButtonsWhenChecked_;
};
QuestionDlg::QuestionDlg(wxWindow* parent,
int activeButtons,
const wxString& messageText,
- const wxString& caption, //optional
- const wxString& labelYes,
- const wxString& labelNo,
- //optional checkbox:
- bool* checkBoxValue,
- const wxString& checkBoxLabel) :
+ const QuestConfig& cfg) :
MessageDlgGenerated(parent),
- checkBoxValue_(checkBoxValue),
buttonYes(*m_buttonCustom1),
- buttonNo (*m_buttonCustom2)
+ buttonNo (*m_buttonCustom2),
+ checkBoxValue_(cfg.checkBoxValue),
+ disabledButtonsWhenChecked_(cfg.disabledButtonsWhenChecked_)
{
#ifdef FFS_WIN
new zen::MouseMoveWindow(*this); //allow moving main dialog by clicking (nearly) anywhere...; ownership passed to "this"
#endif
- SetTitle(!caption.empty()? caption : _("Question"));
+ SetTitle(!cfg.caption.empty()? cfg.caption : _("Question"));
m_bitmapMsgType->SetBitmap(getResourceImage(L"msg_question"));
m_textCtrlMessage->SetValue(messageText);
- buttonYes.SetLabel(!labelYes.empty() ? labelYes : _("&Yes"));
- buttonNo .SetLabel(!labelNo .empty() ? labelNo : _("&No"));
+ buttonYes.SetLabel(!cfg.labelYes.empty() ? cfg.labelYes : _("&Yes"));
+ buttonNo .SetLabel(!cfg.labelNo .empty() ? cfg.labelNo : _("&No"));
//buttonYes.SetId(wxID_YES); -> see comment in ErrorDlg
//buttonNo .SetId(wxID_NO);
- if (checkBoxValue)
+ if (cfg.checkBoxValue)
{
- m_checkBoxCustom->SetValue(*checkBoxValue);
- m_checkBoxCustom->SetLabel(checkBoxLabel);
+ m_checkBoxCustom->SetValue(*cfg.checkBoxValue);
+ m_checkBoxCustom->SetLabel(cfg.checkBoxLabel);
}
else
m_checkBoxCustom->Hide();
@@ -276,9 +295,24 @@ QuestionDlg::QuestionDlg(wxWindow* parent,
else if (activeButtons & ReturnQuestionDlg::BUTTON_CANCEL)
setAsStandard(*m_buttonCancel);
+ updateGui();
Fit(); //child-element widths have changed: image was set
}
+
+void QuestionDlg::updateGui()
+{
+ auto updateEnabledStatus = [&](wxButton& btn, ReturnQuestionDlg::ButtonPressed btnId)
+ {
+ if (disabledButtonsWhenChecked_ & btnId)
+ btn.Enable(!m_checkBoxCustom->GetValue());
+ };
+ updateEnabledStatus(buttonYes, ReturnQuestionDlg::BUTTON_YES);
+ updateEnabledStatus(buttonNo, ReturnQuestionDlg::BUTTON_NO);
+ updateEnabledStatus(*m_buttonCancel, ReturnQuestionDlg::BUTTON_CANCEL);
+}
+
+
void QuestionDlg::OnButton1(wxCommandEvent& event) //yes
{
if (checkBoxValue_)
@@ -299,7 +333,7 @@ ReturnQuestionDlg::ButtonPressed zen::showQuestionDlg(wxWindow* parent,
const wxString& messageText,
const QuestConfig& cfg)
{
- QuestionDlg qtnDlg(parent, activeButtons, messageText, cfg.caption, cfg.labelYes, cfg.labelNo, cfg.checkBoxValue, cfg.checkBoxLabel);
+ QuestionDlg qtnDlg(parent, activeButtons, messageText, cfg);
qtnDlg.Raise();
return static_cast<ReturnQuestionDlg::ButtonPressed>(qtnDlg.ShowModal());
}
diff --git a/ui/msg_popup.h b/ui/msg_popup.h
index 5e59eb6b..5e7ea97f 100644
--- a/ui/msg_popup.h
+++ b/ui/msg_popup.h
@@ -4,12 +4,13 @@
// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
// **************************************************************************
-#ifndef MESSAGEPOPUP_H_INCLUDED
-#define MESSAGEPOPUP_H_INCLUDED
+#ifndef MESSAGEPOPUP_H_820780154723456
+#define MESSAGEPOPUP_H_820780154723456
#include <wx/window.h>
#include <wx/string.h>
+class QuestionDlg;
namespace zen
{
@@ -60,20 +61,17 @@ struct ReturnQuestionDlg
};
};
-class QuestConfig;
-ReturnQuestionDlg::ButtonPressed showQuestionDlg(wxWindow* parent, int activeButtons, const wxString& messageText, const QuestConfig& cfg);
-
class QuestConfig
{
public:
- QuestConfig() : checkBoxValue() {}
+ QuestConfig() : checkBoxValue(), disabledButtonsWhenChecked_() {}
QuestConfig& setCaption (const wxString& label) { caption = label; return *this; }
QuestConfig& setLabelYes(const wxString& label) { labelYes = label; return *this; }
QuestConfig& setLabelNo (const wxString& label) { labelNo = label; return *this; }
- QuestConfig& showCheckBox(bool& value, const wxString& label) { checkBoxLabel = label; checkBoxValue = &value; return *this; }
+ QuestConfig& showCheckBox(bool& value, const wxString& label, int disabledButtonsWhenChecked) { checkBoxValue = &value; checkBoxLabel = label; disabledButtonsWhenChecked_ = disabledButtonsWhenChecked; return *this; }
private:
- friend ReturnQuestionDlg::ButtonPressed showQuestionDlg(wxWindow* parent, int activeButtons, const wxString& messageText, const QuestConfig& cfg);
+ friend class ::QuestionDlg;
wxString caption;
wxString labelYes; //overwrite default "Yes, No" labels
@@ -81,9 +79,10 @@ private:
//optional checkbox:
bool* checkBoxValue; //in/out
wxString checkBoxLabel; //in
+ int disabledButtonsWhenChecked_;
};
ReturnQuestionDlg::ButtonPressed showQuestionDlg(wxWindow* parent, int activeButtons, const wxString& messageText, const QuestConfig& cfg = QuestConfig());
}
-#endif // MESSAGEPOPUP_H_INCLUDED
+#endif //MESSAGEPOPUP_H_820780154723456
diff --git a/ui/progress_indicator.cpp b/ui/progress_indicator.cpp
index d23de61a..37237861 100644
--- a/ui/progress_indicator.cpp
+++ b/ui/progress_indicator.cpp
@@ -12,8 +12,11 @@
#include <wx/sound.h>
#include <wx/clipbrd.h>
#include <wx/msgdlg.h>
+#include <wx/dataobj.h> //wxTextDataObject
#include <zen/basic_math.h>
#include <zen/format_unit.h>
+#include <zen/scope_guard.h>
+#include <wx+/grid.h>
#include <wx+/mouse_move_dlg.h>
#include <wx+/toggle_button.h>
#include <wx+/image_tools.h>
@@ -38,7 +41,7 @@ namespace
const int GAUGE_FULL_RANGE = 50000;
//window size used for statistics in milliseconds
-const int WINDOW_REMAINING_TIME = 60000; //some use cases have dropouts of 40 seconds -> 60 sec. window size handles them well
+const int WINDOW_REMAINING_TIME = 60000; //some scenarios have dropouts of 40 seconds -> 60 sec. window size handles them well
const int WINDOW_BYTES_PER_SEC = 5000; //
}
@@ -59,6 +62,7 @@ private:
wxString titleTextBackup;
wxStopWatch timeElapsed;
+ long binCompStartMs; //begin of binary comparison phase in [ms]
const Statistics* syncStat_; //only bound while sync is running
@@ -72,8 +76,9 @@ private:
CompareProgressDialog::Pimpl::Pimpl(wxTopLevelWindow& parentWindow) :
CompareProgressDlgGenerated(&parentWindow),
parentWindow_(parentWindow),
+ binCompStartMs(0),
syncStat_(nullptr),
- lastStatCallSpeed (-1000000) //some big number
+ lastStatCallSpeed(-1000000) //some big number
{
//init(); -> needed?
}
@@ -125,6 +130,8 @@ void CompareProgressDialog::Pimpl::switchToCompareBytewise()
perf = make_unique<PerfCheck>(WINDOW_REMAINING_TIME, WINDOW_BYTES_PER_SEC);
lastStatCallSpeed = -1000000; //some big number
+ binCompStartMs = timeElapsed.Time();
+
//show status for comparing bytewise
bSizerFilesFound ->Show(false);
bSizerFilesRemaining->Show(true);
@@ -153,6 +160,7 @@ void CompareProgressDialog::Pimpl::updateStatusPanelNow()
};
bool layoutChanged = false; //avoid screen flicker by calling layout() only if necessary
+ const long timeNow = timeElapsed.Time();
//status texts
setText(*m_textCtrlStatus, replaceCpy(syncStat_->currentStatusText(), L'\n', L' ')); //no layout update for status texts!
@@ -194,22 +202,22 @@ void CompareProgressDialog::Pimpl::updateStatusPanelNow()
setText(*m_staticTextDataRemaining, L"(" + filesizeToShortString(dataTotal - dataCurrent) + L")", &layoutChanged);
//remaining time and speed: only visible during binary comparison
+ assert(perf);
if (perf)
- {
- const long timeNow = timeElapsed.Time();
if (numeric::dist(lastStatCallSpeed, timeNow) >= 500)
{
lastStatCallSpeed = timeNow;
- perf->addSample(objectsCurrent, to<double>(dataCurrent), timeNow);
+ if (numeric::dist(binCompStartMs, timeNow) >= 1000) //discard stats for first second: probably messy
+ perf->addSample(objectsCurrent, to<double>(dataCurrent), timeNow);
- //current speed -> Win 7 copy uses 1 sec update interval
+ //current speed -> Win 7 copy uses 1 sec update interval instead
setText(*m_staticTextSpeed, perf->getBytesPerSecond(), &layoutChanged);
- }
- //remaining time
- setText(*m_staticTextRemTime, perf->getRemainingTime(to<double>(dataTotal - dataCurrent)), &layoutChanged);
- }
+ //remaining time: display with relative error of 10% - based on samples taken every 0.5 sec only
+ //-> call more often than once per second to correctly show last few seconds countdown, but don't call too often to avoid occasional jitter
+ setText(*m_staticTextRemTime, perf->getRemainingTime(to<double>(dataTotal - dataCurrent)), &layoutChanged);
+ }
}
break;
@@ -222,7 +230,7 @@ void CompareProgressDialog::Pimpl::updateStatusPanelNow()
setText(*m_staticTextScanned, scannedObjects, &layoutChanged);
//time elapsed
- const long timeElapSec = timeElapsed.Time() / 1000;
+ const long timeElapSec = timeNow / 1000;
setText(*m_staticTextTimeElapsed,
timeElapSec < 3600 ?
wxTimeSpan::Seconds(timeElapSec).Format( L"%M:%S") :
@@ -565,9 +573,11 @@ public:
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));
- m_bpButtonInfo ->init(buttonPressed ("msg_info" ), buttonReleased("msg_info" ), _("Info" ) + wxString::Format(L" (%d)", infoCount ));
+ auto initButton = [](ToggleButton& btn, const char* imgName, const wxString& tooltip) { btn.init(buttonPressed(imgName), buttonReleased(imgName)); btn.SetToolTip(tooltip); };
+
+ initButton(*m_bpButtonErrors, "msg_error", _("Error" ) + wxString::Format(L" (%d)", errorCount ));
+ initButton(*m_bpButtonWarnings, "msg_warning", _("Warning") + wxString::Format(L" (%d)", warningCount));
+ initButton(*m_bpButtonInfo, "msg_info", _("Info" ) + wxString::Format(L" (%d)", infoCount ));
m_bpButtonErrors ->setActive(true);
m_bpButtonWarnings->setActive(true);
@@ -832,41 +842,45 @@ struct LabelFormatterTimeElapsed : public LabelFormatter
}
-class SyncProgressDialog::Pimpl : public SyncProgressDlgGenerated
+class SyncProgressDialogImpl : private SyncProgressDlgGenerated, public SyncProgressDialog
{
public:
- Pimpl(AbortCallback& abortCb,
- const Statistics& syncStat,
- MainDialog* parentWindow,
- const wxString& jobName,
- const std::wstring& execWhenFinished,
- std::vector<std::wstring>& execFinishedHistory);
- ~Pimpl();
-
- void initNewPhase();
- void notifyProgressChange();
- void updateGui(bool allowYield = true);
+ SyncProgressDialogImpl(AbortCallback& abortCb,
+ const std::function<void()>& notifyWindowTerminate,
+ const Statistics& syncStat,
+ wxTopLevelWindow* parentWindow,
+ bool showProgress,
+ const wxString& jobName,
+ const std::wstring& execWhenFinished,
+ std::vector<std::wstring>& execFinishedHistory);
+ ~SyncProgressDialogImpl();
//call this in StatusUpdater derived class destructor at the LATEST(!) to prevent access to currentStatusUpdater
- void processHasFinished(SyncResult resultId, const ErrorLog& log);
- void closeWindowDirectly();
+ virtual void processHasFinished(SyncResult resultId, const ErrorLog& log);
+ virtual void closeWindowDirectly();
+
+ virtual wxWindow* getAsWindow() { return this; }
+ virtual void initNewPhase();
+ virtual void notifyProgressChange();
+ virtual void updateGui() { updateGuiInt(true); }
- std::wstring getExecWhenFinishedCommand() const;
+ virtual std::wstring getExecWhenFinishedCommand() const;
- void stopTimer() //halt all internal counters!
+ virtual void stopTimer() //halt all internal counters!
{
m_animationControl1->Stop();
timeElapsed.Pause ();
}
- void resumeTimer()
+ virtual void resumeTimer()
{
m_animationControl1->Play();
timeElapsed.Resume();
}
+private:
+ void updateGuiInt(bool allowYield);
void minimizeToTray();
-private:
void OnKeyPressed(wxKeyEvent& event);
virtual void OnOkay (wxCommandEvent& event);
virtual void OnPause (wxCommandEvent& event);
@@ -884,11 +898,13 @@ private:
const wxString jobName_;
wxStopWatch timeElapsed;
- MainDialog* mainDialog; //optional
+ wxTopLevelWindow* mainDialog; //optional
+
+ std::function<void()> notifyWindowTerminate_; //call once in OnClose(), NOT in destructor which is called far too late somewhere in wxWidgets main loop!
//status variables
- AbortCallback* abortCb_; //temporarily bound while sync is running
- const Statistics* syncStat_; //
+ const Statistics* syncStat_; //
+ AbortCallback* abortCb_; //valid only while sync is running
bool paused_; //valid only while sync is running
SyncResult finalResult; //set after sync
@@ -909,12 +925,14 @@ private:
};
-SyncProgressDialog::Pimpl::Pimpl(AbortCallback& abortCb,
- const Statistics& syncStat,
- MainDialog* parentWindow,
- const wxString& jobName,
- const std::wstring& execWhenFinished,
- std::vector<std::wstring>& execFinishedHistory) :
+SyncProgressDialogImpl::SyncProgressDialogImpl(AbortCallback& abortCb,
+ const std::function<void()>& notifyWindowTerminate,
+ const Statistics& syncStat,
+ wxTopLevelWindow* parentWindow,
+ bool showProgress,
+ const wxString& jobName,
+ const std::wstring& execWhenFinished,
+ std::vector<std::wstring>& execFinishedHistory) :
SyncProgressDlgGenerated(parentWindow,
wxID_ANY,
parentWindow ? wxString() : (wxString(L"FreeFileSync - ") + _("Folder Comparison and Synchronization")),
@@ -924,8 +942,9 @@ SyncProgressDialog::Pimpl::Pimpl(AbortCallback& abortCb,
wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL),
jobName_ (jobName),
mainDialog(parentWindow),
- abortCb_ (&abortCb),
+ notifyWindowTerminate_(notifyWindowTerminate),
syncStat_ (&syncStat),
+ abortCb_ (&abortCb),
paused_ (false),
finalResult(RESULT_ABORTED), //dummy value
isZombie(false),
@@ -941,23 +960,18 @@ SyncProgressDialog::Pimpl::Pimpl(AbortCallback& abortCb,
setRelativeFontSize(*m_staticTextPhase, 1.5);
if (mainDialog)
- {
titelTextBackup = mainDialog->GetTitle(); //save old title (will be used as progress indicator)
- mainDialog->disableAllElements(false); //disable all child elements
- }
m_animationControl1->SetAnimation(GlobalResources::instance().aniSync);
m_animationControl1->Play();
- SetIcon(GlobalResources::instance().programIcon);
+ SetIcon(GlobalResources::instance().programIconFFS);
//initialize gauge
m_gauge1->SetRange(GAUGE_FULL_RANGE);
m_gauge1->SetValue(0);
- warn_static("not honored on osx")
-
- EnableCloseButton(false);
+ EnableCloseButton(false); //this is NOT honored on OS X or during system shutdown on Windows!
if (IsShown()) //don't steal focus when starting in sys-tray!
m_buttonAbort->SetFocus();
@@ -978,7 +992,7 @@ SyncProgressDialog::Pimpl::Pimpl(AbortCallback& abortCb,
Layout();
//register key event
- Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(Pimpl::OnKeyPressed), nullptr, this);
+ Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(SyncProgressDialogImpl::OnKeyPressed), nullptr, this);
//init graph
graphDataBytes = std::make_shared<GraphDataBytes>();
@@ -1003,14 +1017,22 @@ SyncProgressDialog::Pimpl::Pimpl(AbortCallback& abortCb,
updateDialogStatus(); //null-status will be shown while waiting for dir locks (if at all)
Fit();
+
+ if (showProgress)
+ {
+ Show();
+ //clear gui flicker, remove dummy texts: window must be visible to make this work!
+ updateGuiInt(true); //at least on OS X a real Yield() is required to flush pending GUI updates; Update() is not enough
+ }
+ else
+ minimizeToTray();
}
-SyncProgressDialog::Pimpl::~Pimpl()
+SyncProgressDialogImpl::~SyncProgressDialogImpl()
{
if (mainDialog)
{
- mainDialog->enableAllElements();
mainDialog->SetTitle(titelTextBackup); //restore title text
//make sure main dialog is shown again if still "minimized to systray"! see SyncProgressDialog::closeWindowDirectly()
@@ -1019,10 +1041,12 @@ SyncProgressDialog::Pimpl::~Pimpl()
mainDialog->Show();
}
+
+ //our client is NOT expecting a second call vianotifyWindowTerminate_()!
}
-void SyncProgressDialog::Pimpl::OnKeyPressed(wxKeyEvent& event)
+void SyncProgressDialogImpl::OnKeyPressed(wxKeyEvent& event)
{
const int keyCode = event.GetKeyCode();
if (keyCode == WXK_ESCAPE)
@@ -1048,7 +1072,7 @@ void SyncProgressDialog::Pimpl::OnKeyPressed(wxKeyEvent& event)
}
-void SyncProgressDialog::Pimpl::initNewPhase()
+void SyncProgressDialogImpl::initNewPhase()
{
updateDialogStatus(); //evaluates "syncStat_->currentPhase()"
@@ -1066,14 +1090,13 @@ void SyncProgressDialog::Pimpl::initNewPhase()
//so give updateGui() a chance to set a different value
m_gauge1->SetValue(0);
- updateGui(false);
+ updateGuiInt(false);
}
-void SyncProgressDialog::Pimpl::notifyProgressChange() //noexcept!
+void SyncProgressDialogImpl::notifyProgressChange() //noexcept!
{
- if (syncStat_)
- {
+ if (syncStat_) //sync running
switch (syncStat_->currentPhase())
{
case ProcessCallback::PHASE_NONE:
@@ -1090,7 +1113,6 @@ void SyncProgressDialog::Pimpl::notifyProgressChange() //noexcept!
}
break;
}
- }
}
@@ -1157,7 +1179,7 @@ std::wstring getDialogPhaseText(const Statistics* syncStat, bool paused, SyncPro
}
-void SyncProgressDialog::Pimpl::setExternalStatus(const wxString& status, const wxString& progress) //progress may be empty!
+void SyncProgressDialogImpl::setExternalStatus(const wxString& status, const wxString& progress) //progress may be empty!
{
//sys tray: order "top-down": jobname, status, progress
wxString newTrayInfo = jobName_.empty() ? status : L"\"" + jobName_ + L"\"\n" + status;
@@ -1186,15 +1208,15 @@ void SyncProgressDialog::Pimpl::setExternalStatus(const wxString& status, const
}
-void SyncProgressDialog::Pimpl::updateGui(bool allowYield)
+void SyncProgressDialogImpl::updateGuiInt(bool allowYield)
{
- assert(syncStat_);
- if (!syncStat_) //no sync running!!
+ if (!syncStat_) //sync not running
return;
//wxWindowUpdateLocker dummy(this); -> not needed
bool layoutChanged = false; //avoid screen flicker by calling layout() only if necessary
+ const long timeNow = timeElapsed.Time();
//sync status text
setText(*m_staticTextStatus, replaceCpy(syncStat_->currentStatusText(), L'\n', L' ')); //no layout update for status texts!
@@ -1256,20 +1278,20 @@ void SyncProgressDialog::Pimpl::updateGui(bool allowYield)
//remaining time and speed
assert(perf);
if (perf)
- {
- const long timeNow = timeElapsed.Time();
if (numeric::dist(lastStatCallSpeed, timeNow) >= 500)
{
lastStatCallSpeed = timeNow;
- perf->addSample(objectsCurrent, to<double>(dataCurrent), timeNow);
+ if (numeric::dist(phaseStartMs, timeNow) >= 1000) //discard stats for first second: probably messy
+ perf->addSample(objectsCurrent, to<double>(dataCurrent), timeNow);
- //current speed -> Win 7 copy uses 1 sec update interval
+ //current speed -> Win 7 copy uses 1 sec update interval instead
setText(*m_staticTextSpeed, perf->getBytesPerSecond(), &layoutChanged);
+
+ //remaining time: display with relative error of 10% - based on samples taken every 0.5 sec only
+ //-> call more often than once per second to correctly show last few seconds countdown, but don't call too often to avoid occasional jitter
+ setText(*m_staticTextRemTime, perf->getRemainingTime(to<double>(dataTotal - dataCurrent)), &layoutChanged);
}
- //remaining time: display with relative error of 10% - based on samples taken every 0.5 sec only - accuracy of prediction grows with time
- setText(*m_staticTextRemTime, perf->getRemainingTime(to<double>(dataTotal - dataCurrent)), &layoutChanged);
- }
}
break;
}
@@ -1279,7 +1301,7 @@ void SyncProgressDialog::Pimpl::updateGui(bool allowYield)
m_panelGraph->Refresh();
//time elapsed
- const long timeElapSec = timeElapsed.Time() / 1000;
+ const long timeElapSec = timeNow / 1000;
setText(*m_staticTextTimeElapsed,
timeElapSec < 3600 ?
wxTimeSpan::Seconds(timeElapSec).Format( L"%M:%S") :
@@ -1336,13 +1358,13 @@ void SyncProgressDialog::Pimpl::updateGui(bool allowYield)
}
-std::wstring SyncProgressDialog::Pimpl::getExecWhenFinishedCommand() const
+std::wstring SyncProgressDialogImpl::getExecWhenFinishedCommand() const
{
return m_comboBoxExecFinished->getValue();
}
-void SyncProgressDialog::Pimpl::updateDialogStatus() //depends on "syncStat_, paused_, finalResult"
+void SyncProgressDialogImpl::updateDialogStatus() //depends on "syncStat_, paused_, finalResult"
{
const wxString dlgStatusTxt = getDialogPhaseText(syncStat_, paused_, finalResult);
@@ -1449,25 +1471,20 @@ void SyncProgressDialog::Pimpl::updateDialogStatus() //depends on "syncStat_, pa
warn_static("osx: minimize to systray?")
-void SyncProgressDialog::Pimpl::closeWindowDirectly() //this should really be called: do not call back + schedule deletion
+void SyncProgressDialogImpl::closeWindowDirectly() //this should really be called: do not call back + schedule deletion
{
paused_ = false; //you never know?
-
- //ATTENTION: dialog may live a little longer, so cut callbacks!
+ //ATTENTION: dialog may live a little longer, so watch callbacks!
//e.g. wxGTK calls OnIconize after wxWindow::Close() (better not ask why) and before physical destruction! => indirectly calls updateDialogStatus(), which reads syncStat_!!!
+ syncStat_ = nullptr;
+ abortCb_ = nullptr;
+ //resumeFromSystray(); -> NO, instead ~SyncProgressDialogImpl() makes sure that main dialog is shown again!
- //------- change class state -------
- abortCb_ = nullptr; //avoid callback to (maybe) deleted parent process
- syncStat_ = nullptr; //set *after* last call to "updateGui"
- //----------------------------------
-
- //resumeFromSystray(); -> NO, instead ~Pimpl() makes sure that main dialog is shown again!
-
- Close();
+ Close(); //generate close event: do NOT destroy window unconditionally!
}
-void SyncProgressDialog::Pimpl::processHasFinished(SyncResult resultId, const ErrorLog& log) //essential to call this in StatusHandler derived class destructor
+void SyncProgressDialogImpl::processHasFinished(SyncResult resultId, const ErrorLog& log) //essential to call this in StatusHandler derived class destructor
{
//at the LATEST(!) to prevent access to currentStatusHandler
//enable okay and close events; may be set in this method ONLY
@@ -1478,7 +1495,7 @@ void SyncProgressDialog::Pimpl::processHasFinished(SyncResult resultId, const Er
//update numbers one last time (as if sync were still running)
notifyProgressChange(); //make one last graph entry at the *current* time
- updateGui(false);
+ updateGuiInt(false);
switch (syncStat_->currentPhase()) //no matter if paused or not
{
@@ -1526,9 +1543,10 @@ void SyncProgressDialog::Pimpl::processHasFinished(SyncResult resultId, const Er
}
//------- change class state -------
- abortCb_ = nullptr; //avoid callback to (maybe) deleted parent process
- syncStat_ = nullptr; //set *after* last call to "updateGui"
finalResult = resultId;
+
+ syncStat_ = nullptr;
+ abortCb_ = nullptr;
//----------------------------------
updateDialogStatus();
@@ -1573,7 +1591,7 @@ void SyncProgressDialog::Pimpl::processHasFinished(SyncResult resultId, const Er
//workaround wxListBox bug on Windows XP: labels are drawn on top of each other
assert(m_listbookResult->GetImageList()); //make sure listbook keeps *any* image list
//due to some crazy reasons that aren't worth debugging, this needs to be done directly in wxFormBuilder,
- //the following call is *not* sufficient: m_listbookResult->AssignImageList(new wxImageList(180, 1));
+ //the following call is *not* sufficient: m_listbookResult->AssignImageList(new wxImageList(170, 1));
//note: alternative solutions involving wxLC_LIST, wxLC_REPORT and SetWindowStyleFlag() do not work portably! wxListBook using wxLC_ICON is obviously a class invariant!
//1. re-arrange graph into results listbook
@@ -1595,7 +1613,6 @@ void SyncProgressDialog::Pimpl::processHasFinished(SyncResult resultId, const Er
m_listbookResult->ChangeSelection(posLog);
Layout();
-
//play (optional) sound notification after sync has completed -> only play when waiting on results dialog, seems to be pointless otherwise!
switch (finalResult)
{
@@ -1616,14 +1633,14 @@ void SyncProgressDialog::Pimpl::processHasFinished(SyncResult resultId, const Er
}
-void SyncProgressDialog::Pimpl::OnOkay(wxCommandEvent& event)
+void SyncProgressDialogImpl::OnOkay(wxCommandEvent& event)
{
- isZombie = true; //on Fedora the iconize event is executed *before* entering SyncProgressDialog::Pimpl::OnClose()!!!
+ isZombie = true; //on Fedora an iconize event is issued *before* entering SyncProgressDialogImpl::OnClose()!!!
Close(); //generate close event: do NOT destroy window unconditionally!
}
-void SyncProgressDialog::Pimpl::OnAbort(wxCommandEvent& event)
+void SyncProgressDialogImpl::OnAbort(wxCommandEvent& event)
{
paused_ = false;
updateDialogStatus(); //update status + pause button
@@ -1634,27 +1651,36 @@ void SyncProgressDialog::Pimpl::OnAbort(wxCommandEvent& event)
}
-void SyncProgressDialog::Pimpl::OnPause(wxCommandEvent& event)
+void SyncProgressDialogImpl::OnPause(wxCommandEvent& event)
{
paused_ = !paused_;
updateDialogStatus(); //update status + pause button
}
-void SyncProgressDialog::Pimpl::OnClose(wxCloseEvent& event)
+void SyncProgressDialogImpl::OnClose(wxCloseEvent& event)
{
- //this event handler may be called due to a system shutdown DURING synchronization!
+ //this event handler may be called *during* sync, e.g. due to a system shutdown (Windows), anytime (OS X)
//try to stop sync gracefully and cross fingers:
if (abortCb_)
abortCb_->requestAbortion();
- //Note: we must NOT veto dialog destruction, else we will cancel system shutdown if this dialog is application main window (as in batch mode)
+ //Note: we must NOT veto dialog destruction, else we will cancel system shutdown if this dialog is application main window (like in batch mode)
+
+ notifyWindowTerminate_(); //don't wait until delayed "Destroy()" finally calls destructor -> avoid calls to processHasFinished()/closeWindowDirectly()
+
+ paused_ = false; //[!] we could be pausing here!
+
+ //now that we notified window termination prematurely, and since processHasFinished()/closeWindowDirectly() won't be called, make sure we don't call back, too!
+ //e.g. the second notifyWindowTerminate_() in ~SyncProgressDialogImpl()!!!
+ syncStat_ = nullptr;
+ abortCb_ = nullptr;
isZombie = true; //it "lives" until cleanup in next idle event
Destroy();
}
-void SyncProgressDialog::Pimpl::OnIconize(wxIconizeEvent& event)
+void SyncProgressDialogImpl::OnIconize(wxIconizeEvent& event)
{
if (isZombie) return; //wxGTK sends iconize event *after* wxWindow::Destroy, sigh...
@@ -1665,23 +1691,22 @@ void SyncProgressDialog::Pimpl::OnIconize(wxIconizeEvent& event)
}
-void SyncProgressDialog::Pimpl::OnResumeFromTray(wxCommandEvent& event)
+void SyncProgressDialogImpl::OnResumeFromTray(wxCommandEvent& event)
{
resumeFromSystray();
}
-void SyncProgressDialog::Pimpl::minimizeToTray()
+void SyncProgressDialogImpl::minimizeToTray()
{
if (!trayIcon.get())
{
trayIcon = make_unique<FfsTrayIcon>();
- trayIcon->Connect(FFS_REQUEST_RESUME_TRAY_EVENT, wxCommandEventHandler(SyncProgressDialog::Pimpl::OnResumeFromTray), nullptr, this);
+ trayIcon->Connect(FFS_REQUEST_RESUME_TRAY_EVENT, wxCommandEventHandler(SyncProgressDialogImpl::OnResumeFromTray), nullptr, this);
//tray icon has shorter lifetime than this => no need to disconnect event later
}
- if (syncStat_)
- updateGui(false); //set tray tooltip + progress: e.g. no updates while paused
+ updateGuiInt(false); //set tray tooltip + progress: e.g. no updates while paused
Hide();
if (mainDialog)
@@ -1689,7 +1714,7 @@ void SyncProgressDialog::Pimpl::minimizeToTray()
}
-void SyncProgressDialog::Pimpl::resumeFromSystray()
+void SyncProgressDialogImpl::resumeFromSystray()
{
trayIcon.reset();
@@ -1708,80 +1733,19 @@ void SyncProgressDialog::Pimpl::resumeFromSystray()
SetFocus();
updateDialogStatus(); //restore Windows 7 task bar status (e.g. required in pause mode)
- if (syncStat_)
- updateGui(false); //restore Windows 7 task bar progress (e.g. required in pause mode)
+ updateGuiInt(false); //restore Windows 7 task bar progress (e.g. required in pause mode)
}
-
//########################################################################################
-//redirect to implementation
-SyncProgressDialog::SyncProgressDialog(AbortCallback& abortCb,
- const Statistics& syncStat,
- MainDialog* parentWindow,
- bool showProgress,
- const wxString& jobName,
- const std::wstring& execWhenFinished,
- std::vector<std::wstring>& execFinishedHistory) :
- pimpl(new Pimpl(abortCb, syncStat, parentWindow, jobName, execWhenFinished, execFinishedHistory))
-{
- if (showProgress)
- {
- pimpl->Show();
- 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();
-}
-
-SyncProgressDialog::~SyncProgressDialog()
-{
- //DON'T delete pimpl! it will be deleted by the user clicking "OK/Cancel" -> (wxWindow::Destroy())
-}
-
-wxWindow* SyncProgressDialog::getAsWindow()
-{
- return pimpl;
-}
-
-void SyncProgressDialog::initNewPhase()
-{
- pimpl->initNewPhase();
-}
-
-void SyncProgressDialog::notifyProgressChange()
-{
- pimpl->notifyProgressChange();
-}
-
-void SyncProgressDialog::updateGui()
-{
- pimpl->updateGui();
-}
-
-std::wstring SyncProgressDialog::getExecWhenFinishedCommand() const
-{
- return pimpl->getExecWhenFinishedCommand();
-}
-
-void SyncProgressDialog::stopTimer()
-{
- return pimpl->stopTimer();
-}
-
-void SyncProgressDialog::resumeTimer()
-{
- return pimpl->resumeTimer();
-}
-
-void SyncProgressDialog::processHasFinished(SyncResult resultId, const ErrorLog& log)
-{
- pimpl->processHasFinished(resultId, log);
-}
-
-void SyncProgressDialog::closeWindowDirectly() //don't wait for user (silent mode)
+SyncProgressDialog* createProgressDialog(zen::AbortCallback& abortCb,
+ const std::function<void()>& notifyWindowTerminate, //note: user closing window cannot be prevented on OS X! (And neither on Windows during system shutdown!)
+ const zen::Statistics& syncStat,
+ wxTopLevelWindow* parentWindow, //may be nullptr
+ bool showProgress,
+ const wxString& jobName,
+ const std::wstring& execWhenFinished,
+ std::vector<std::wstring>& execFinishedHistory)
{
- pimpl->closeWindowDirectly();
+ return new SyncProgressDialogImpl(abortCb, notifyWindowTerminate, syncStat, parentWindow, showProgress, jobName, execWhenFinished, execFinishedHistory);
}
diff --git a/ui/progress_indicator.h b/ui/progress_indicator.h
index d659af6e..0789ca80 100644
--- a/ui/progress_indicator.h
+++ b/ui/progress_indicator.h
@@ -7,11 +7,12 @@
#ifndef PROGRESSINDICATOR_H_INCLUDED
#define PROGRESSINDICATOR_H_INCLUDED
+#include <functional>
#include <zen/error_log.h>
-#include <zen/zstring.h>
+//#include <zen/zstring.h>
#include <wx/toplevel.h>
#include "../lib/status_handler.h"
-#include "main_dlg.h"
+//#include "main_dlg.h"
class CompareProgressDialog
@@ -33,30 +34,10 @@ private:
};
-class SyncProgressDialog
-{
-public:
- SyncProgressDialog(zen::AbortCallback& abortCb,
- const zen::Statistics& syncStat,
- MainDialog* parentWindow, //may be nullptr
- bool showProgress,
- const wxString& jobName,
- const std::wstring& execWhenFinished,
- std::vector<std::wstring>& execFinishedHistory); //changing parameter!
- ~SyncProgressDialog();
-
- wxWindow* getAsWindow(); //convenience! don't abuse!
-
- void initNewPhase(); //call after "StatusHandler::initNewPhase"
-
- void notifyProgressChange(); //throw (), required by graph!
- void updateGui();
-
- std::wstring getExecWhenFinishedCommand() const; //final value (after possible user modification)
-
- void stopTimer(); //halt all internal counters!
- void resumeTimer(); //
+//SyncStatusHandler will internally process Window messages => disable GUI controls to avoid unexpected callbacks!
+struct SyncProgressDialog
+{
enum SyncResult
{
RESULT_ABORTED,
@@ -64,17 +45,40 @@ public:
RESULT_FINISHED_WITH_WARNINGS,
RESULT_FINISHED_WITH_SUCCESS
};
- //essential to call one of these two methods in StatusUpdater derived class destructor at the LATEST(!)
+ //essential to call one of these two methods in StatusUpdater derived class' destructor at the LATEST(!)
//to prevent access to callback to updater (e.g. request abort)
- void processHasFinished(SyncResult resultId, const zen::ErrorLog& log); //sync finished, still dialog may live on
- void closeWindowDirectly(); //don't wait for user
+ virtual void processHasFinished(SyncResult resultId, const zen::ErrorLog& log) = 0; //sync finished, still dialog may live on
+ virtual void closeWindowDirectly() = 0; //don't wait for user
-private:
- class Pimpl;
- Pimpl* const pimpl;
+ //---------------------------------------------------------------------------
+
+ virtual wxWindow* getAsWindow() = 0; //convenience! don't abuse!
+
+ virtual void initNewPhase() = 0; //call after "StatusHandler::initNewPhase"
+ virtual void notifyProgressChange() = 0; //throw (), required by graph!
+ virtual void updateGui() = 0; //update GUI and process Window messages
+
+ virtual std::wstring getExecWhenFinishedCommand() const = 0; //final value (after possible user modification)
+
+ virtual void stopTimer() = 0; //halt all internal timers!
+ virtual void resumeTimer() = 0; //
+
+protected:
+ ~SyncProgressDialog() {}
};
+SyncProgressDialog* createProgressDialog(zen::AbortCallback& abortCb,
+ const std::function<void()>& notifyWindowTerminate, //note: user closing window cannot be prevented on OS X! (And neither on Windows during system shutdown!)
+ const zen::Statistics& syncStat,
+ wxTopLevelWindow* parentWindow, //may be nullptr
+ bool showProgress,
+ const wxString& jobName,
+ const std::wstring& execWhenFinished,
+ std::vector<std::wstring>& execFinishedHistory); //changing parameter!
+//DON'T delete the pointer! it will be deleted by the user clicking "OK/Cancel"/wxWindow::Destroy() after processHasFinished() or closeWindowDirectly()
+
+
class PauseTimers
{
public:
diff --git a/ui/small_dlgs.cpp b/ui/small_dlgs.cpp
index 3827c837..32bb3e85 100644
--- a/ui/small_dlgs.cpp
+++ b/ui/small_dlgs.cpp
@@ -8,6 +8,7 @@
#include <wx/wupdlock.h>
#include <zen/format_unit.h>
#include <zen/build_info.h>
+#include <zen/tick_count.h>
#include <zen/stl_tools.h>
#include <wx+/choice_enum.h>
#include <wx+/button.h>
@@ -306,8 +307,8 @@ ReturnSmallDlg::ButtonPressed zen::showFilterDialog(wxWindow* parent, bool isGlo
filter);
return static_cast<ReturnSmallDlg::ButtonPressed>(filterDlg.ShowModal());
}
-//########################################################################################
+//########################################################################################
class DeleteDialog : public DeleteDlgGenerated
{
@@ -331,6 +332,8 @@ private:
const std::vector<zen::FileSystemObject*>& rowsToDeleteOnRight;
bool& outRefdeleteOnBothSides;
bool& outRefuseRecycleBin;
+ const TickVal tickCountStartup;
+ const std::int64_t ticksPerSec_;
};
@@ -343,7 +346,9 @@ DeleteDialog::DeleteDialog(wxWindow* parent,
rowsToDeleteOnLeft(rowsOnLeft),
rowsToDeleteOnRight(rowsOnRight),
outRefdeleteOnBothSides(deleteOnBothSides),
- outRefuseRecycleBin(useRecycleBin)
+ outRefuseRecycleBin(useRecycleBin),
+ tickCountStartup(getTicks()),
+ ticksPerSec_(ticksPerSec())
{
#ifdef FFS_WIN
new zen::MouseMoveWindow(*this); //allow moving main dialog by clicking (nearly) anywhere...; ownership passed to "this"
@@ -403,6 +408,12 @@ void DeleteDialog::updateGui()
void DeleteDialog::OnOK(wxCommandEvent& event)
{
+ //additional safety net, similar to Windows Explorer: time delta between DEL and ENTER must be at least 50ms to avoid accidental deletion!
+ const TickVal now = getTicks(); //0 on error
+ if (now.isValid() && tickCountStartup.isValid() && ticksPerSec_ != 0)
+ if (dist(tickCountStartup, now) * 1000 / ticksPerSec_ < 50)
+ return;
+
outRefuseRecycleBin = m_checkBoxUseRecycler->GetValue();
if (rowsToDeleteOnLeft != rowsToDeleteOnRight)
outRefdeleteOnBothSides = m_checkBoxDeleteBothSides->GetValue();
@@ -746,7 +757,7 @@ void GlobalSettingsDlg::OnOkay(wxCommandEvent& event)
void GlobalSettingsDlg::OnResetDialogs(wxCommandEvent& event)
{
if (showQuestionDlg(this, ReturnQuestionDlg::BUTTON_YES | ReturnQuestionDlg::BUTTON_CANCEL,
- _("Make hidden warnings and dialogs visible again?")) == ReturnQuestionDlg::BUTTON_YES)
+ _("Make hidden warnings and dialogs visible again?"), QuestConfig().setLabelYes(_("&Restore"))) == ReturnQuestionDlg::BUTTON_YES)
settings.optDialogs.resetDialogs();
}
diff --git a/ui/sorting.h b/ui/sorting.h
index 75b2593a..28d2f931 100644
--- a/ui/sorting.h
+++ b/ui/sorting.h
@@ -20,7 +20,7 @@ struct CompileTimeReminder : public FSObjectVisitor
virtual void visit(const FileMapping& fileObj) {}
virtual void visit(const SymLinkMapping& linkObj) {}
virtual void visit(const DirMapping& dirObj ) {}
-} checkDymanicCasts; //just a compile-time reminder to check dynamic casts in this file
+} checkDymanicCasts; //just a compile-time reminder to manually check dynamic casts in this file when needed
}
inline
diff --git a/ui/sync_cfg.cpp b/ui/sync_cfg.cpp
index dbef3619..dc62b04a 100644
--- a/ui/sync_cfg.cpp
+++ b/ui/sync_cfg.cpp
@@ -235,8 +235,8 @@ SyncCfgDialog::SyncCfgDialog(wxWindow* parent,
setRelativeFontSize(*m_toggleBtnCustom, 1.25);
enumVersioningStyle.
- 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"));
+ 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"));
//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 07f7a942..cf866a71 100644
--- a/ui/tree_view.cpp
+++ b/ui/tree_view.cpp
@@ -664,6 +664,70 @@ std::unique_ptr<TreeView::Node> TreeView::getLine(size_t row) const
namespace
{
+#ifdef _MSC_VER
+#pragma warning(disable:4428) // VC wrongly issues warning C4428: universal-character-name encountered in source
+#endif
+
+wxString getShortDisplayNameForFolderPair(const Zstring& dirLeftPf, const Zstring& dirRightPf) //post-fixed with separator
+{
+ assert(endsWith(dirLeftPf, FILE_NAME_SEPARATOR) || dirLeftPf .empty());
+ assert(endsWith(dirRightPf, FILE_NAME_SEPARATOR) || dirRightPf.empty());
+
+ auto itL = dirLeftPf .end();
+ auto itR = dirRightPf.end();
+
+ for (;;)
+ {
+ auto itLPrev = find_last(dirLeftPf .begin(), itL, FILE_NAME_SEPARATOR);
+ auto itRPrev = find_last(dirRightPf.begin(), itR, FILE_NAME_SEPARATOR);
+
+ if (itLPrev == itL ||
+ itRPrev == itR)
+ {
+ if (itLPrev == itL)
+ itLPrev = dirLeftPf.begin();
+ else
+ ++itLPrev; //skip separator
+ if (itRPrev == itR)
+ itRPrev = dirRightPf.begin();
+ else
+ ++itRPrev;
+
+ if (equal(itLPrev, itL, itRPrev, itR))
+ {
+ itL = itLPrev;
+ itR = itRPrev;
+ }
+ break;
+ }
+
+ if (!equal(itLPrev, itL, itRPrev, itR))
+ break;
+ itL = itLPrev;
+ itR = itRPrev;
+ }
+
+ Zstring commonPostfix(itL, dirLeftPf.end());
+ if (startsWith(commonPostfix, FILE_NAME_SEPARATOR))
+ commonPostfix = afterFirst(commonPostfix, FILE_NAME_SEPARATOR);
+ if (endsWith(commonPostfix, FILE_NAME_SEPARATOR))
+ commonPostfix.resize(commonPostfix.size() - 1);
+
+ if (commonPostfix.empty())
+ {
+ auto getLastComponent = [](const Zstring& dirPf) { return utfCvrtTo<wxString>(afterLast(beforeLast(dirPf, FILE_NAME_SEPARATOR), FILE_NAME_SEPARATOR)); }; //returns the whole string if term not found
+ if (dirLeftPf.empty())
+ return getLastComponent(dirRightPf);
+ else if (dirRightPf.empty())
+ return getLastComponent(dirLeftPf);
+ else
+ return getLastComponent(dirLeftPf) + L" \u2212 " + //= unicode minus
+ getLastComponent(dirRightPf);
+ }
+ return utfCvrtTo<wxString>(commonPostfix);
+}
+
+
const wxColour COLOR_LEVEL0(0xcc, 0xcc, 0xff);
const wxColour COLOR_LEVEL1(0xcc, 0xff, 0xcc);
const wxColour COLOR_LEVEL2(0xff, 0xff, 0x99);
@@ -716,6 +780,32 @@ public:
private:
virtual size_t getRowCount() const { return treeDataView_ ? treeDataView_->linesTotal() : 0; }
+ virtual wxString getToolTip(size_t row, ColumnType colType) const
+ {
+ switch (static_cast<ColumnTypeNavi>(colType))
+ {
+ case COL_TYPE_NAVI_BYTES:
+ case COL_TYPE_NAVI_ITEM_COUNT:
+ break;
+
+ case COL_TYPE_NAVI_DIRECTORY:
+ if (treeDataView_)
+ if (std::unique_ptr<TreeView::Node> node = treeDataView_->getLine(row))
+ if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get()))
+ {
+ const wxString& dirLeft = utfCvrtTo<wxString>(root->baseMap_.getBaseDirPf<LEFT_SIDE >());
+ const wxString& dirRight = utfCvrtTo<wxString>(root->baseMap_.getBaseDirPf<RIGHT_SIDE>());
+ if (dirLeft.empty())
+ return dirRight;
+ else if (dirRight.empty())
+ return dirLeft;
+ return dirLeft + L" \u2212 \n" + dirRight; //\u2212 = unicode minus
+ }
+ break;
+ }
+ return wxString();
+ }
+
virtual wxString getValue(size_t row, ColumnType colType) const
{
if (treeDataView_)
@@ -728,17 +818,8 @@ private:
case COL_TYPE_NAVI_DIRECTORY:
if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get()))
- {
- const wxString dirLeft = utfCvrtTo<wxString>(beforeLast(root->baseMap_.getBaseDirPf<LEFT_SIDE >(), FILE_NAME_SEPARATOR));
- const wxString dirRight = utfCvrtTo<wxString>(beforeLast(root->baseMap_.getBaseDirPf<RIGHT_SIDE>(), FILE_NAME_SEPARATOR));
-
- if (dirLeft.empty())
- return dirRight;
- else if (dirRight.empty())
- return dirLeft;
- else
- return dirLeft + L" \x2212 " + dirRight; //\x2212 = unicode minus
- }
+ return getShortDisplayNameForFolderPair(root->baseMap_.getBaseDirPf<LEFT_SIDE >(),
+ root->baseMap_.getBaseDirPf<RIGHT_SIDE>());
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()))
@@ -749,7 +830,7 @@ private:
return toGuiString(node->itemCount_);
}
}
- return wxEmptyString;
+ return wxString();
}
virtual void renderColumnLabel(Grid& tree, wxDC& dc, const wxRect& rect, ColumnType colType, bool highlighted)
bgstack15