diff options
Diffstat (limited to 'ui/progress_indicator.cpp')
-rw-r--r-- | ui/progress_indicator.cpp | 2004 |
1 files changed, 0 insertions, 2004 deletions
diff --git a/ui/progress_indicator.cpp b/ui/progress_indicator.cpp deleted file mode 100644 index efbb62f0..00000000 --- a/ui/progress_indicator.cpp +++ /dev/null @@ -1,2004 +0,0 @@ -// ************************************************************************** -// * This file is part of the FreeFileSync project. It is distributed under * -// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * -// ************************************************************************** - -#include "progress_indicator.h" -#include <memory> -#include <wx/imaglist.h> -#include <wx/stopwatch.h> -#include <wx/wupdlock.h> -#include <wx/sound.h> -#include <wx/clipbrd.h> -#include <wx/dcclient.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> -#include <wx+/graph.h> -#include <wx+/context_menu.h> -#include <wx+/no_flicker.h> -#include <wx+/font_size.h> -#include <wx+/std_button_order.h> -#include <wx+/popup_dlg.h> -#include <wx+/image_resources.h> -#include <zen/file_handling.h> -#include <zen/thread.h> -#include "gui_generated.h" -#include "../lib/ffs_paths.h" -#include "../lib/perf_check.h" -#include "tray_icon.h" -#include "taskbar.h" -#include "exec_finished_box.h" -#include "app_icon.h" -#ifdef ZEN_MAC -#include <ApplicationServices/ApplicationServices.h> -#endif - -using namespace zen; - - -namespace -{ -const int GAUGE_FULL_RANGE = 50000; - -//window size used for statistics in milliseconds -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; // -} - - -class CompareProgressDialog::Pimpl : public CompareProgressDlgGenerated -{ -public: - Pimpl(wxFrame& parentWindow); - - void init(const Statistics& syncStat); //constructor/destructor semantics, but underlying Window is reused - void finalize(); // - - void switchToCompareBytewise(); - void updateStatusPanelNow(); - -private: - wxFrame& parentWindow_; - wxString titleTextBackup; - - wxStopWatch timeElapsed; - long binCompStartMs; //begin of binary comparison phase in [ms] - - const Statistics* syncStat_; //only bound while sync is running - - std::unique_ptr<Taskbar> taskbar_; - std::unique_ptr<PerfCheck> perf; //estimate remaining time - - long lastStatCallSpeed; //used for calculating intervals between showing and collecting perf samples -}; - - -CompareProgressDialog::Pimpl::Pimpl(wxFrame& parentWindow) : - CompareProgressDlgGenerated(&parentWindow), - parentWindow_(parentWindow), - binCompStartMs(0), - syncStat_(nullptr), - lastStatCallSpeed(-1000000) //some big number -{ - //init(); -> needed? -} - - -void CompareProgressDialog::Pimpl::init(const Statistics& syncStat) -{ - syncStat_ = &syncStat; - titleTextBackup = parentWindow_.GetTitle(); - - try //try to get access to Windows 7/Ubuntu taskbar - { - taskbar_ = make_unique<Taskbar>(parentWindow_); - } - catch (const TaskbarNotAvailable&) {} - - //initialize gauge - m_gauge2->SetRange(GAUGE_FULL_RANGE); - m_gauge2->SetValue(0); - - perf.reset(); - timeElapsed.Start(); //measure total time - - //initially hide status that's relevant for comparing bytewise only - bSizerFilesFound ->Show(true); - bSizerFilesRemaining->Show(false); - sSizerSpeed ->Show(false); - sSizerTimeRemaining ->Show(false); - - updateStatusPanelNow(); - - m_gauge2->Hide(); - bSizer42->Layout(); - Layout(); -} - - -void CompareProgressDialog::Pimpl::finalize() -{ - syncStat_ = nullptr; - parentWindow_.SetTitle(titleTextBackup); - taskbar_.reset(); -} - - -void CompareProgressDialog::Pimpl::switchToCompareBytewise() -{ - //start to measure perf - 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); - sSizerSpeed ->Show(true); - sSizerTimeRemaining ->Show(true); - - m_gauge2->Show(); - bSizer42->Layout(); - Layout(); -} - - -void CompareProgressDialog::Pimpl::updateStatusPanelNow() -{ - if (!syncStat_) //no comparison running!! - return; - - const wxString& scannedObjects = toGuiString(syncStat_->getObjectsCurrent(ProcessCallback::PHASE_SCANNING)); - - auto setTitle = [&](const wxString& title) - { - if (parentWindow_.GetTitle() != title) - parentWindow_.SetTitle(title); - }; - - 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! - - //write status information to taskbar, parent title ect. - switch (syncStat_->currentPhase()) - { - case ProcessCallback::PHASE_NONE: - case ProcessCallback::PHASE_SCANNING: - //dialog caption, taskbar - setTitle(scannedObjects + L" - " + _("Scanning...")); - if (taskbar_.get()) //support Windows 7 taskbar - taskbar_->setStatus(Taskbar::STATUS_INDETERMINATE); - break; - - case ProcessCallback::PHASE_COMPARING_CONTENT: - { - auto objectsCurrent = syncStat_->getObjectsCurrent(ProcessCallback::PHASE_COMPARING_CONTENT); - auto objectsTotal = syncStat_->getObjectsTotal (ProcessCallback::PHASE_COMPARING_CONTENT); - auto dataCurrent = syncStat_->getDataCurrent (ProcessCallback::PHASE_COMPARING_CONTENT); - auto dataTotal = syncStat_->getDataTotal (ProcessCallback::PHASE_COMPARING_CONTENT); - - //add both data + obj-count, to handle "deletion-only" cases - const double fraction = dataTotal + objectsTotal == 0 ? 0 : std::max(0.0, to<double>(dataCurrent + objectsCurrent) / to<double>(dataTotal + objectsTotal)); - - //dialog caption, taskbar - setTitle(fractionToString(fraction) + wxT(" - ") + _("Comparing content...")); - if (taskbar_.get()) - { - taskbar_->setProgress(fraction); - taskbar_->setStatus(Taskbar::STATUS_NORMAL); - } - - //progress indicator, shown for binary comparison only - m_gauge2->SetValue(numeric::round(fraction * GAUGE_FULL_RANGE)); - - //remaining objects and bytes for file comparison - setText(*m_staticTextFilesRemaining, toGuiString(objectsTotal - objectsCurrent), &layoutChanged); - setText(*m_staticTextDataRemaining, L"(" + filesizeToShortString(dataTotal - dataCurrent) + L")", &layoutChanged); - - //remaining time and speed: only visible during binary comparison - assert(perf); - if (perf) - if (numeric::dist(lastStatCallSpeed, timeNow) >= 500) - { - lastStatCallSpeed = 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 instead - Opt<std::wstring> bps = perf->getBytesPerSecond(); - setText(*m_staticTextSpeed, bps ? *bps : L"-", &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 - Opt<std::wstring> rt = perf->getRemainingTime(to<double>(dataTotal - dataCurrent)); - setText(*m_staticTextRemTime, rt ? *rt : L"-", &layoutChanged); - } - } - break; - - case ProcessCallback::PHASE_SYNCHRONIZING: - assert(false); - break; - } - - //nr of scanned objects - setText(*m_staticTextScanned, scannedObjects, &layoutChanged); - - //time elapsed - const long timeElapSec = timeNow / 1000; - setText(*m_staticTextTimeElapsed, - timeElapSec < 3600 ? - wxTimeSpan::Seconds(timeElapSec).Format( L"%M:%S") : - wxTimeSpan::Seconds(timeElapSec).Format(L"%H:%M:%S"), &layoutChanged); - - if (layoutChanged) - bSizer42->Layout(); - - //do the ui update - wxTheApp->Yield(); -} - -//######################################################################################## - -//redirect to implementation -CompareProgressDialog::CompareProgressDialog(wxFrame& parentWindow) : - pimpl(new Pimpl(parentWindow)) {} //owned by parentWindow - -wxWindow* CompareProgressDialog::getAsWindow() -{ - return pimpl; -} - -void CompareProgressDialog::init(const Statistics& syncStat) -{ - pimpl->init(syncStat); -} - -void CompareProgressDialog::finalize() -{ - pimpl->finalize(); -} - -void CompareProgressDialog::switchToCompareBytewise() -{ - pimpl->switchToCompareBytewise(); -} - -void CompareProgressDialog::updateStatusPanelNow() -{ - pimpl->updateStatusPanelNow(); -} -//######################################################################################## - -namespace -{ -inline -wxBitmap buttonPressed(const std::string& name) -{ - wxBitmap background = getResourceImage(L"log button pressed"); - return layOver(getResourceImage(utfCvrtTo<wxString>(name)), background); -} - - -inline -wxBitmap buttonReleased(const std::string& name) -{ - wxImage output = greyScale(getResourceImage(utfCvrtTo<wxString>(name))).ConvertToImage(); - //getResourceImage(utfCvrtTo<wxString>(name)).ConvertToImage().ConvertToGreyscale(1.0/3, 1.0/3, 1.0/3); //treat all channels equally! - //brighten(output, 30); - - //zen::moveImage(output, 1, 0); //move image right one pixel - return output; -} - - -//a vector-view on ErrorLog considering multi-line messages: prepare consumption by Grid -class MessageView -{ -public: - MessageView(const ErrorLog& log) : log_(log) {} - - size_t rowsOnView() const { return viewRef.size(); } - - struct LogEntryView - { - time_t time; - MessageType type; - MsgString messageLine; - bool firstLine; //if LogEntry::message spans multiple rows - }; - bool getEntry(size_t row, LogEntryView& out) const - { - if (row < viewRef.size()) - { - const Line& line = viewRef[row]; - out.time = line.entry_->time; - out.type = line.entry_->type; - out.messageLine = extractLine(line.entry_->message, line.rowNumber_); - out.firstLine = line.rowNumber_ == 0; //this is virtually always correct, unless first line of the original message is empty! - return true; - } - return false; - } - - void updateView(int includedTypes) //TYPE_INFO | TYPE_WARNING, ect. see error_log.h - { - viewRef.clear(); - - for (auto it = log_.begin(); it != log_.end(); ++it) - if (it->type & includedTypes) - { - assert_static((IsSameType<GetCharType<MsgString>::Type, wchar_t>::value)); - assert(!startsWith(it->message, L'\n')); - - size_t rowNumber = 0; - bool lastCharNewline = true; - for (const wchar_t c : it->message) - if (c == L'\n') - { - if (!lastCharNewline) //do not reference empty lines! - viewRef.push_back(Line(&*it, rowNumber)); - ++rowNumber; - lastCharNewline = true; - } - else - lastCharNewline = false; - - if (!lastCharNewline) - viewRef.push_back(Line(&*it, rowNumber)); - } - } - -private: - static MsgString extractLine(const MsgString& message, size_t textRow) - { - auto iter1 = message.begin(); - for (;;) - { - auto iter2 = std::find_if(iter1, message.end(), [](wchar_t c) { return c == L'\n'; }); - if (textRow == 0) - return iter1 == message.end() ? MsgString() : MsgString(&*iter1, iter2 - iter1); //must not dereference iterator pointing to "end"! - - if (iter2 == message.end()) - { - assert(false); - return MsgString(); - } - - iter1 = iter2 + 1; //skip newline - --textRow; - } - } - - struct Line - { - Line(const LogEntry* entry, size_t rowNumber) : entry_(entry), rowNumber_(rowNumber) {} - const LogEntry* entry_; //always bound! - size_t rowNumber_; //LogEntry::message may span multiple rows - }; - - std::vector<Line> viewRef; //partial view on log_ - /* /|\ - | updateView() - | */ - const ErrorLog log_; -}; - -//----------------------------------------------------------------------------- - -enum ColumnTypeMsg -{ - COL_TYPE_MSG_TIME, - COL_TYPE_MSG_CATEGORY, - COL_TYPE_MSG_TEXT, -}; - -//Grid data implementation referencing MessageView -class GridDataMessages : public GridData -{ -public: - GridDataMessages(const std::shared_ptr<MessageView>& msgView) : msgView_(msgView) {} - - virtual size_t getRowCount() const { return msgView_ ? msgView_->rowsOnView() : 0; } - - virtual wxString getValue(size_t row, ColumnType colType) const - { - MessageView::LogEntryView entry = {}; - if (msgView_ && msgView_->getEntry(row, entry)) - switch (static_cast<ColumnTypeMsg>(colType)) - { - case COL_TYPE_MSG_TIME: - if (entry.firstLine) - return formatTime<wxString>(FORMAT_TIME, localTime(entry.time)); - break; - - case COL_TYPE_MSG_CATEGORY: - if (entry.firstLine) - switch (entry.type) - { - case TYPE_INFO: - return _("Info"); - case TYPE_WARNING: - return _("Warning"); - case TYPE_ERROR: - return _("Error"); - case TYPE_FATAL_ERROR: - return _("Serious Error"); - } - break; - - case COL_TYPE_MSG_TEXT: - return copyStringTo<wxString>(entry.messageLine); - } - return wxEmptyString; - } - - virtual void renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool selected) override - { - wxRect rectTmp = rect; - - const wxColor colorGridLine = wxColour(192, 192, 192); //light grey - - wxDCPenChanger dummy2(dc, wxPen(colorGridLine, 1, wxSOLID)); - const bool drawBottomLine = [&]() -> bool //don't separate multi-line messages - { - MessageView::LogEntryView nextEntry = {}; - if (msgView_ && msgView_->getEntry(row + 1, nextEntry)) - return nextEntry.firstLine; - return true; - }(); - - if (drawBottomLine) - { - dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); - --rectTmp.height; - } - - //-------------------------------------------------------- - - MessageView::LogEntryView entry = {}; - if (msgView_ && msgView_->getEntry(row, entry)) - switch (static_cast<ColumnTypeMsg>(colType)) - { - case COL_TYPE_MSG_TIME: - drawCellText(dc, rectTmp, getValue(row, colType), true, wxALIGN_CENTER); - break; - - case COL_TYPE_MSG_CATEGORY: - if (entry.firstLine) - switch (entry.type) - { - case TYPE_INFO: - dc.DrawLabel(wxString(), getResourceImage(L"msg_info_small"), rectTmp, wxALIGN_CENTER); - break; - case TYPE_WARNING: - dc.DrawLabel(wxString(), getResourceImage(L"msg_warning_small"), rectTmp, wxALIGN_CENTER); - break; - case TYPE_ERROR: - case TYPE_FATAL_ERROR: - dc.DrawLabel(wxString(), getResourceImage(L"msg_error_small"), rectTmp, wxALIGN_CENTER); - break; - } - break; - - case COL_TYPE_MSG_TEXT: - { - rectTmp.x += COLUMN_GAP_LEFT; - rectTmp.width -= COLUMN_GAP_LEFT; - drawCellText(dc, rectTmp, getValue(row, colType), true); - } - break; - } - } - - virtual int getBestSize(wxDC& dc, size_t row, ColumnType colType) override - { - // -> synchronize renderCell() <-> getBestSize() - - MessageView::LogEntryView entry = {}; - if (msgView_ && msgView_->getEntry(row, entry)) - switch (static_cast<ColumnTypeMsg>(colType)) - { - case COL_TYPE_MSG_TIME: - return 2 * COLUMN_GAP_LEFT + dc.GetTextExtent(getValue(row, colType)).GetWidth(); - - case COL_TYPE_MSG_CATEGORY: - return getResourceImage(L"msg_info_small").GetWidth(); - - case COL_TYPE_MSG_TEXT: - return COLUMN_GAP_LEFT + dc.GetTextExtent(getValue(row, colType)).GetWidth(); - } - return 0; - } - - static int getColumnTimeDefaultWidth(Grid& grid) - { - wxClientDC dc(&grid.getMainWin()); - dc.SetFont(grid.getMainWin().GetFont()); - return 2 * COLUMN_GAP_LEFT + dc.GetTextExtent(formatTime<wxString>(FORMAT_TIME)).GetWidth(); - } - - static int getColumnCategoryDefaultWidth() - { - return getResourceImage(L"msg_info_small").GetWidth(); - } - - static int getRowDefaultHeight(const Grid& grid) - { - return std::max(getResourceImage(L"msg_info_small").GetHeight(), grid.getMainWin().GetCharHeight() + 2) + 1; //+ some space + bottom border - } - - virtual wxString getToolTip(size_t row, ColumnType colType) const override - { - MessageView::LogEntryView entry = {}; - if (msgView_ && msgView_->getEntry(row, entry)) - switch (static_cast<ColumnTypeMsg>(colType)) - { - case COL_TYPE_MSG_TIME: - case COL_TYPE_MSG_TEXT: - break; - - case COL_TYPE_MSG_CATEGORY: - return getValue(row, colType); - } - return wxEmptyString; - } - - virtual wxString getColumnLabel(ColumnType colType) const { return wxEmptyString; } - -private: - const std::shared_ptr<MessageView> msgView_; -}; -} - - -class LogPanel : public LogPanelGenerated -{ -public: - LogPanel(wxWindow* parent, const ErrorLog& log) : LogPanelGenerated(parent), - msgView(std::make_shared<MessageView>(log)) - { - const int errorCount = log.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR); - const int warningCount = log.getItemCount(TYPE_WARNING); - const int infoCount = log.getItemCount(TYPE_INFO); - - 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); - m_bpButtonInfo ->setActive(errorCount + warningCount == 0); - - m_bpButtonErrors ->Show(errorCount != 0); - m_bpButtonWarnings->Show(warningCount != 0); - m_bpButtonInfo ->Show(infoCount != 0); - - //init grid, determine default sizes - const int rowHeight = GridDataMessages::getRowDefaultHeight(*m_gridMessages); - const int colMsgTimeWidth = GridDataMessages::getColumnTimeDefaultWidth(*m_gridMessages); - const int colMsgCategoryWidth = GridDataMessages::getColumnCategoryDefaultWidth(); - - m_gridMessages->setDataProvider(std::make_shared<GridDataMessages>(msgView)); - m_gridMessages->setColumnLabelHeight(0); - m_gridMessages->showRowLabel(false); - m_gridMessages->setRowHeight(rowHeight); - std::vector<Grid::ColumnAttribute> attr; - attr.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_MSG_TIME ), colMsgTimeWidth, 0)); - attr.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_MSG_CATEGORY), colMsgCategoryWidth, 0)); - attr.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_MSG_TEXT ), -colMsgTimeWidth - colMsgCategoryWidth, 1)); - m_gridMessages->setColumnConfig(attr); - - //support for CTRL + C - m_gridMessages->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(LogPanel::onGridButtonEvent), nullptr, this); - - m_gridMessages->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(LogPanel::onMsgGridContext), nullptr, this); - - updateGrid(); - } - -private: - virtual void OnErrors(wxCommandEvent& event) - { - m_bpButtonErrors->toggle(); - updateGrid(); - } - - virtual void OnWarnings(wxCommandEvent& event) - { - m_bpButtonWarnings->toggle(); - updateGrid(); - } - - virtual void OnInfo(wxCommandEvent& event) - { - m_bpButtonInfo->toggle(); - updateGrid(); - } - - void updateGrid() - { - int includedTypes = 0; - if (m_bpButtonErrors->isActive()) - includedTypes |= TYPE_ERROR | TYPE_FATAL_ERROR; - - if (m_bpButtonWarnings->isActive()) - includedTypes |= TYPE_WARNING; - - if (m_bpButtonInfo->isActive()) - includedTypes |= TYPE_INFO; - - msgView->updateView(includedTypes); //update MVC "model" - m_gridMessages->Refresh(); //update MVC "view" - } - - void onGridButtonEvent(wxKeyEvent& event) - { - int keyCode = event.GetKeyCode(); - - if (event.ControlDown()) - switch (keyCode) - { - case 'C': - case WXK_INSERT: //CTRL + C || CTRL + INS - copySelectionToClipboard(); - return; // -> swallow event! don't allow default grid commands! - } - - event.Skip(); //unknown keypress: propagate - } - - void onMsgGridContext(GridClickEvent& event) - { - const std::vector<size_t> selection = m_gridMessages->getSelectedRows(); - - ContextMenu menu; - menu.addItem(_("Copy") + L"\tCtrl+C", [this] { copySelectionToClipboard(); }, nullptr, !selection.empty()); - menu.popup(*this); - } - - void copySelectionToClipboard() - { - try - { - typedef Zbase<wchar_t> zxString; //guaranteed exponential growth, unlike wxString - zxString clipboardString; - - if (auto prov = m_gridMessages->getDataProvider()) - { - std::vector<Grid::ColumnAttribute> colAttr = m_gridMessages->getColumnConfig(); - vector_remove_if(colAttr, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); - if (!colAttr.empty()) - { - const std::vector<size_t> selection = m_gridMessages->getSelectedRows(); - std::for_each(selection.begin(), selection.end(), - [&](size_t row) - { -#ifdef _MSC_VER - typedef zxString zxString; //workaround MSVC compiler bug! -#endif - std::for_each(colAttr.begin(), --colAttr.end(), - [&](const Grid::ColumnAttribute& ca) - { - clipboardString += copyStringTo<zxString>(prov->getValue(row, ca.type_)); - clipboardString += L'\t'; - }); - clipboardString += copyStringTo<zxString>(prov->getValue(row, colAttr.back().type_)); - clipboardString += L'\n'; - }); - } - } - - //finally write to clipboard - if (!clipboardString.empty()) - if (wxClipboard::Get()->Open()) - { - ZEN_ON_SCOPE_EXIT(wxClipboard::Get()->Close()); - wxClipboard::Get()->SetData(new wxTextDataObject(copyStringTo<wxString>(clipboardString))); //ownership passed - } - } - catch (const std::bad_alloc& e) - { - showNotificationDialog(nullptr, DialogInfoType::ERROR2, PopupDialogCfg().setMainInstructions(_("Out of memory.") + L" " + utfCvrtTo<std::wstring>(e.what()))); - } - } - - std::shared_ptr<MessageView> msgView; //bound! -}; - -//######################################################################################## - -namespace -{ -class CurveDataStatistics : public SparseCurveData -{ -public: - CurveDataStatistics() : SparseCurveData(true), /*true: add steps*/ timeNow(0) {} - - void clear() { samples.clear(); timeNow = 0; } - - void addRecord(long timeNowMs, double value) - { - //samples.clear(); - //samples.insert(std::make_pair(-1000, 0)); - //samples.insert(std::make_pair(0, 0)); - //samples.insert(std::make_pair(1, 1)); - //samples.insert(std::make_pair(1000, 0)); - //return; - - timeNow = timeNowMs; - - //don't allow for more samples per second than there are UI updates (handles duplicate inserts, too!) - if (!samples.empty() && timeNowMs / UI_UPDATE_INTERVAL == samples.rbegin()->first / UI_UPDATE_INTERVAL) - { - samples.rbegin()->second = value; - return; - } - - samples.insert(samples.end(), std::make_pair(timeNowMs, value)); //time is "expected" to be monotonously ascending - //documentation differs about whether "hint" should be before or after the to be inserted element! - //however "std::map<>::end()" is interpreted correctly by GCC and VS2010 - - if (samples.size() > MAX_BUFFER_SIZE) //limit buffer size - samples.erase(samples.begin()); - } - -private: - virtual std::pair<double, double> getRangeX() const override - { - if (samples.empty()) return std::make_pair(0.0, 0.0); - - double upperEndMs = std::max(timeNow, samples.rbegin()->first); - - //report some additional width by 5% elapsed time to make graph recalibrate before hitting the right border - //caveat: graph for batch mode binary comparison does NOT start at elapsed time 0!! PHASE_COMPARING_CONTENT and PHASE_SYNCHRONIZING! - //=> consider width of current sample set! - upperEndMs += 0.05 *(upperEndMs - samples.begin()->first); - - return std::make_pair(samples.begin()->first / 1000.0, //need not start with 0, e.g. "binary comparison, graph reset, followed by sync" - upperEndMs / 1000.0); - } - - virtual Opt<CurvePoint> getLessEq(double x) const override //x: seconds since begin - { - const long timex = std::floor(x * 1000); - //------ add artifical last sample value ------- - if (!samples.empty() && samples.rbegin()->first < timeNow) - if (timeNow <= timex) - return CurvePoint(timeNow / 1000.0, samples.rbegin()->second); - //-------------------------------------------------- - - //find first key > x, then go one step back: => samples must be a std::map, NOT std::multimap!!! - auto it = samples.upper_bound(timex); - if (it == samples.begin()) - return NoValue(); - //=> samples not empty in this context - --it; - return CurvePoint(it->first / 1000.0, it->second); - } - - virtual Opt<CurvePoint> getGreaterEq(double x) const override - { - const long timex = std::ceil(x * 1000); - //------ add artifical last sample value ------- - if (!samples.empty() && samples.rbegin()->first < timeNow) - if (samples.rbegin()->first < timex && timex <= timeNow) - return CurvePoint(timeNow / 1000.0, samples.rbegin()->second); - //-------------------------------------------------- - - auto it = samples.lower_bound(timex); - if (it == samples.end()) - return NoValue(); - return CurvePoint(it->first / 1000.0, it->second); - } - - static const size_t MAX_BUFFER_SIZE = 2500000; //sizeof(single node) worst case ~ 3 * 8 byte ptr + 16 byte key/value = 40 byte - - std::map<long, double> samples; //time, unit: [ms] !don't use std::multimap, see getLessEq() - long timeNow; //help create an artificial record at the end of samples to visualize current time! -}; - - -class CurveDataCurrentValue : public CurveData -{ -public: - CurveDataCurrentValue() : x(0), yCurrent_(0), yTotal_(0) {} - - void setValue(long xTimeNowMs, double yCurrent, double yTotal) { x = xTimeNowMs / 1000.0; yCurrent_ = yCurrent; yTotal_ = yTotal; } - -private: - virtual std::pair<double, double> getRangeX() const override { return std::make_pair(x, x); } //conceptually just a vertical line! - - virtual void getPoints(double minX, double maxX, int pixelWidth, std::vector<CurvePoint>& points) const override - { - //points.push_back(CurvePoint(-1, 0)); - //points.push_back(CurvePoint(0, 0)); - //points.push_back(CurvePoint(0.0001, 1)); - //points.push_back(CurvePoint(1, 0)); - //return; - - if (x <= maxX) - { - points.push_back(CurvePoint(x, 0)); - points.push_back(CurvePoint(x, yCurrent_)); - points.push_back(CurvePoint(maxX, yCurrent_)); - points.push_back(CurvePoint(x, yCurrent_)); - points.push_back(CurvePoint(x, yTotal_)); - } - } - - double x; //time elapsed in seconds - double yCurrent_; //current bytes/items - double yTotal_; -}; - - -class CurveDataTotalValue : public CurveData -{ -public: - CurveDataTotalValue () : x(0), yTotal_(0) {} - - void setValue(long xTimeNowMs, double yTotal) { x = xTimeNowMs / 1000.0; yTotal_ = yTotal; } - -private: - virtual std::pair<double, double> getRangeX() const override { return std::make_pair(x, x); } //conceptually just a vertical line! - - virtual void getPoints(double minX, double maxX, int pixelWidth, std::vector<CurvePoint>& points) const override - { - if (x <= maxX) - { - points.push_back(CurvePoint(x, yTotal_)); - points.push_back(CurvePoint(maxX, yTotal_)); - } - } - - double x; //time elapsed in seconds - double yTotal_; -}; - - -const double stretchDefaultBlockSize = 1.4; //enlarge block default size - - -struct LabelFormatterBytes : public LabelFormatter -{ - virtual double getOptimalBlockSize(double bytesProposed) const - { - bytesProposed *= stretchDefaultBlockSize; //enlarge block default size - - if (bytesProposed <= 1) //never smaller than 1 byte - return 1; - - //round to next number which is a convenient to read block size - const double k = std::floor(std::log(bytesProposed) / std::log(2.0)); - const double e = std::pow(2.0, k); - if (numeric::isNull(e)) - return 0; - const double a = bytesProposed / e; //bytesProposed = a * 2^k with a in [1, 2) - assert(1 <= a && a < 2); - const double steps[] = { 1, 2 }; - return e * numeric::nearMatch(a, std::begin(steps), std::end(steps)); - } - - virtual wxString formatText(double value, double optimalBlockSize) const { return filesizeToShortString(Int64(value)); }; -}; - - -struct LabelFormatterItemCount : public LabelFormatter -{ - virtual double getOptimalBlockSize(double itemsProposed) const - { - itemsProposed *= stretchDefaultBlockSize; //enlarge block default size - - const double steps[] = { 1, 2, 5, 10 }; - if (itemsProposed <= 10) - return numeric::nearMatch(itemsProposed, std::begin(steps), std::end(steps)); //similar to nextNiceNumber(), but without the 2.5 step! - return nextNiceNumber(itemsProposed); - } - - virtual wxString formatText(double value, double optimalBlockSize) const - { - return toGuiString(numeric::round(value)); //not enough room for a "%x items" representation - }; -}; - - -struct LabelFormatterTimeElapsed : public LabelFormatter -{ - LabelFormatterTimeElapsed(bool drawLabel) : drawLabel_(drawLabel) {} - - virtual double getOptimalBlockSize(double secProposed) const - { - const double stepsSec[] = { 20, 30, 60 }; //20 sec: minimum block size; (no 15: avoid flicker between 10<->15<->20 sec blocks) - if (secProposed <= 60) - return numeric::nearMatch(secProposed, std::begin(stepsSec), std::end(stepsSec)); - - const double stepsMin[] = { 1, 2, 5, 10, 15, 20, 30, 60 }; //nice numbers for minutes - if (secProposed <= 3600) - return 60.0 * numeric::nearMatch(secProposed / 60, std::begin(stepsMin), std::end(stepsMin)); - - if (secProposed <= 3600 * 24) - return nextNiceNumber(secProposed / 3600) * 3600; //round up to full hours - - return nextNiceNumber(secProposed / (24 * 3600)) * 24 * 3600; //round to full days - } - - virtual wxString formatText(double timeElapsed, double optimalBlockSize) const - { - if (!drawLabel_) return wxString(); - return timeElapsed < 60 ? - _P("1 sec", "%x sec", numeric::round(timeElapsed)) : - timeElapsed < 3600 ? - wxTimeSpan::Seconds(timeElapsed).Format( L"%M:%S") : - wxTimeSpan::Seconds(timeElapsed).Format(L"%H:%M:%S"); - } - -private: - bool drawLabel_; -}; -} - - -template <class TopLevelDialog> //can be a wxFrame or wxDialog -class SyncProgressDialogImpl : public TopLevelDialog, public SyncProgressDialog -/*we need derivation, not composition! - 1. SyncProgressDialogImpl IS a wxFrame/wxDialog - 2. implement virtual ~wxFrame() - 3. event handling below assumes lifetime is larger-equal than wxFrame's -*/ -{ -public: - SyncProgressDialogImpl(long style, //wxFrame/wxDialog style - const std::function<wxFrame*(TopLevelDialog& progDlg)>& getTaskbarFrame, - AbortCallback& abortCb, - const std::function<void()>& notifyWindowTerminate, - const Statistics& syncStat, - wxFrame* parentFrame, - bool showProgress, - const wxString& jobName, - const std::wstring& execWhenFinished, - std::vector<std::wstring>& execFinishedHistory); - virtual ~SyncProgressDialogImpl(); - - //call this in StatusUpdater derived class destructor at the LATEST(!) to prevent access to currentStatusUpdater - virtual void processHasFinished(SyncResult resultId, const ErrorLog& log); - virtual void closeWindowDirectly(); - - virtual wxWindow* getWindowIfVisible() { return this->IsShown() ? this : nullptr; } - //workaround OS X bug: if "this" is used as parent window for a modal dialog then this dialog will erroneously un-hide its parent! - - virtual void initNewPhase(); - virtual void notifyProgressChange(); - virtual void updateGui() { updateGuiInt(true); } - - virtual std::wstring getExecWhenFinishedCommand() const { return pnl.m_comboBoxExecFinished->getValue(); } - - virtual void stopTimer() //halt all internal counters! - { - pnl.m_animCtrlSyncing->Stop(); - timeElapsed.Pause (); - } - virtual void resumeTimer() - { - pnl.m_animCtrlSyncing->Play(); - timeElapsed.Resume(); - } - -private: - void updateGuiInt(bool allowYield); - - void OnKeyPressed(wxKeyEvent& event); - void OnOkay (wxCommandEvent& event); - void OnPause (wxCommandEvent& event); - void OnCancel (wxCommandEvent& event); - void OnClose (wxCloseEvent& event); - void OnIconize(wxIconizeEvent& event); - void OnMinimizeToTray(wxCommandEvent& event) { minimizeToTray(); } - - void minimizeToTray(); - void resumeFromSystray(); - - void updateDialogStatus(); - void setExternalStatus(const wxString& status, const wxString& progress); //progress may be empty! - - SyncProgressPanelGenerated& pnl; //wxPanel containing the GUI controls of *this - - const wxString jobName_; - wxStopWatch timeElapsed; - - wxFrame* parentFrame_; //optional - - std::function<void()> notifyWindowTerminate_; //call once in OnClose(), NOT in destructor which is called far too late somewhere in wxWidgets main loop! - - bool wereDead; //after wxWindow::Delete, which is equal to "delete this" on OS X! - - //status variables - const Statistics* syncStat_; // - AbortCallback* abortCb_; //valid only while sync is running - bool paused_; //valid only while sync is running - SyncResult finalResult; //set after sync - - //remaining time - std::unique_ptr<PerfCheck> perf; - long lastStatCallSpeed; //used for calculating intervals between collecting perf samples - //help calculate total speed - long phaseStartMs; //begin of current phase in [ms] - - std::shared_ptr<CurveDataStatistics> curveDataBytes; - std::shared_ptr<CurveDataStatistics> curveDataItems; - - std::shared_ptr<CurveDataTotalValue > curveDataBytesTotal; - std::shared_ptr<CurveDataCurrentValue> curveDataBytesCurrent; - std::shared_ptr<CurveDataTotalValue > curveDataItemsTotal; - std::shared_ptr<CurveDataCurrentValue> curveDataItemsCurrent; - - wxString parentFrameTitleBackup; - std::unique_ptr<FfsTrayIcon> trayIcon; //optional: if filled all other windows should be hidden and conversely - std::unique_ptr<Taskbar> taskbar_; -}; - - -template <class TopLevelDialog> -SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxFrame/wxDialog style - const std::function<wxFrame*(TopLevelDialog& progDlg)>& getTaskbarFrame, - AbortCallback& abortCb, - const std::function<void()>& notifyWindowTerminate, - const Statistics& syncStat, - wxFrame* parentFrame, - bool showProgress, - const wxString& jobName, - const std::wstring& execWhenFinished, - std::vector<std::wstring>& execFinishedHistory) : - TopLevelDialog(parentFrame, wxID_ANY, wxString(), wxDefaultPosition, wxDefaultSize, style), //title is overwritten anyway in setExternalStatus() - pnl(*new SyncProgressPanelGenerated(this)), //ownership passed to "this" - jobName_ (jobName), - parentFrame_(parentFrame), - notifyWindowTerminate_(notifyWindowTerminate), - wereDead(false), - syncStat_ (&syncStat), - abortCb_ (&abortCb), - paused_ (false), - finalResult(RESULT_ABORTED), //dummy value - lastStatCallSpeed(-1000000), //some big number - phaseStartMs(0) -{ - assert_static((IsSameType<TopLevelDialog, wxFrame >::value || - IsSameType<TopLevelDialog, wxDialog>::value)); - assert((IsSameType<TopLevelDialog, wxFrame>::value == !parentFrame)); - - //finish construction of this dialog: - this->SetMinSize(wxSize(470, 280)); //== minimum size! no idea why SetMinSize() is not used... - wxBoxSizer* bSizer170 = new wxBoxSizer(wxVERTICAL); - bSizer170->Add(&pnl, 1, wxEXPAND); - this->SetSizer(bSizer170); //pass ownership - //this->Layout(); - //bSizer170->Fit(this); - this->Centre(wxBOTH); - - //lifetime of event sources is subset of this instance's lifetime => no wxEvtHandler::Disconnect() needed - this->Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler (SyncProgressDialogImpl<TopLevelDialog>::OnClose)); - this->Connect(wxEVT_ICONIZE, wxIconizeEventHandler(SyncProgressDialogImpl<TopLevelDialog>::OnIconize)); - this->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(SyncProgressDialogImpl::OnKeyPressed), nullptr, this); - pnl.m_buttonClose->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnOkay ), NULL, this); - pnl.m_buttonPause->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnPause ), NULL, this); - pnl.m_buttonStop ->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnCancel), NULL, this); - pnl.m_bpButtonMinimizeToTray->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SyncProgressDialogImpl::OnMinimizeToTray), NULL, this); - -#ifdef ZEN_WIN - new MouseMoveWindow(*this); //allow moving main dialog by clicking (nearly) anywhere...; ownership passed to "this" -#endif - - assert(pnl.m_buttonClose->GetId() == wxID_OK); //we cannot use wxID_CLOSE else Esc key won't work: yet another wxWidgets bug?? - - setRelativeFontSize(*pnl.m_staticTextPhase, 1.5); - - if (parentFrame_) - parentFrameTitleBackup = parentFrame_->GetTitle(); //save old title (will be used as progress indicator) - - pnl.m_animCtrlSyncing->SetAnimation(getResourceAnimation(L"working")); - pnl.m_animCtrlSyncing->Play(); - - this->EnableCloseButton(false); //this is NOT honored on OS X or during system shutdown on Windows! - - timeElapsed.Start(); //measure total time - - if (wxFrame* frame = getTaskbarFrame(*this)) - try //try to get access to Windows 7/Ubuntu taskbar - { - taskbar_ = make_unique<Taskbar>(*frame); //throw TaskbarNotAvailable - } - catch (const TaskbarNotAvailable&) {} - - //hide "processed" statistics until end of process - pnl.m_notebookResult ->Hide(); - pnl.m_panelItemsProcessed->Hide(); - pnl.m_buttonClose ->Show(false); - //set std order after button visibility was set - setStandardButtonOrder(*pnl.bSizerStdButtons, StdButtons().setAffirmative(pnl.m_buttonPause).setCancel(pnl.m_buttonStop)); - - pnl.m_bpButtonMinimizeToTray->SetBitmapLabel(getResourceImage(L"minimize_to_tray")); - - //init graph - curveDataBytes = std::make_shared<CurveDataStatistics>(); - curveDataItems = std::make_shared<CurveDataStatistics>(); - curveDataBytesTotal = std::make_shared<CurveDataTotalValue>(); - curveDataBytesCurrent = std::make_shared<CurveDataCurrentValue>(); - curveDataItemsTotal = std::make_shared<CurveDataTotalValue>(); - curveDataItemsCurrent = std::make_shared<CurveDataCurrentValue>(); - - const int xLabelHeight = 18; //we need to use the same height for both graphs to make sure they stretch evenly - const int yLabelWidth = 70; - pnl.m_panelGraphBytes->setAttributes(Graph2D::MainAttributes(). - setLabelX(Graph2D::X_LABEL_BOTTOM, xLabelHeight, std::make_shared<LabelFormatterTimeElapsed>(true)). - setLabelY(Graph2D::Y_LABEL_RIGHT, yLabelWidth, std::make_shared<LabelFormatterBytes>()). - setSelectionMode(Graph2D::SELECT_NONE)); - - pnl.m_panelGraphItems->setAttributes(Graph2D::MainAttributes(). - setLabelX(Graph2D::X_LABEL_BOTTOM, xLabelHeight, std::make_shared<LabelFormatterTimeElapsed>(false)). - setLabelY(Graph2D::Y_LABEL_RIGHT, yLabelWidth, std::make_shared<LabelFormatterItemCount>()). - setSelectionMode(Graph2D::SELECT_NONE)); - - //pnl.m_panelGraphBytes->setAttributes(Graph2D::MainAttributes().setMinX(-1).setMaxX(1).setMinY(-1).setMaxY(1)); - - pnl.m_panelGraphBytes->setCurve(curveDataBytes, Graph2D::CurveAttributes().setLineWidth(2). - fillCurveArea(wxColor(205, 255, 202)). //faint green - setColor (wxColor( 20, 200, 0))); //medium green - - pnl.m_panelGraphItems->setCurve(curveDataItems, Graph2D::CurveAttributes().setLineWidth(2). - fillCurveArea(wxColor(198, 206, 255)). //faint blue - setColor (wxColor( 90, 120, 255))); //medium blue - - pnl.m_panelGraphBytes->addCurve(curveDataBytesTotal, Graph2D::CurveAttributes().setLineWidth(2).setColor(wxColor(12, 128, 0))); //dark green - pnl.m_panelGraphBytes->addCurve(curveDataBytesCurrent, Graph2D::CurveAttributes().setLineWidth(1).setColor(wxColor(12, 128, 0))); // - - pnl.m_panelGraphItems->addCurve(curveDataItemsTotal, Graph2D::CurveAttributes().setLineWidth(2).setColor(wxColor(53, 25, 255))); //dark blue - pnl.m_panelGraphItems->addCurve(curveDataItemsCurrent, Graph2D::CurveAttributes().setLineWidth(1).setColor(wxColor(53, 25, 255))); // - - //allow changing on completion command - pnl.m_comboBoxExecFinished->initHistory(execFinishedHistory, execFinishedHistory.size()); //-> we won't use addItemHistory() later - pnl.m_comboBoxExecFinished->setValue(execWhenFinished); - - updateDialogStatus(); //null-status will be shown while waiting for dir locks - - this->GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() - pnl.Layout(); - - if (showProgress) - { - this->Show(); -#ifdef ZEN_MAC - ProcessSerialNumber psn = { 0, kCurrentProcess }; - ::SetFrontProcess(&psn); //call before TransformProcessType() so that OSX menu is updated correctly - ::TransformProcessType(&psn, kProcessTransformToForegroundApplication); //show dock icon (consider non-silent batch mode) -#endif - - pnl.m_buttonStop->SetFocus(); //don't steal focus when starting in sys-tray! - - //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(); -} - - -template <class TopLevelDialog> -SyncProgressDialogImpl<TopLevelDialog>::~SyncProgressDialogImpl() -{ - if (parentFrame_) - { - parentFrame_->SetTitle(parentFrameTitleBackup); //restore title text - - //make sure main dialog is shown again if still "minimized to systray"! see SyncProgressDialog::closeWindowDirectly() - parentFrame_->Show(); -#ifdef ZEN_MAC - ProcessSerialNumber psn = { 0, kCurrentProcess }; - ::SetFrontProcess(&psn); //call before TransformProcessType() so that OSX menu is updated correctly - //why isn't this covered by wxWindows::Raise()?? - ::TransformProcessType(&psn, kProcessTransformToForegroundApplication); //show dock icon (consider GUI mode with "close progress dialog") -#endif - //if (parentFrame_->IsIconized()) //caveat: if window is maximized calling Iconize(false) will erroneously un-maximize! - // parentFrame_->Iconize(false); - } - - //our client is NOT expecting a second call via notifyWindowTerminate_()! -} - - -template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::OnKeyPressed(wxKeyEvent& event) -{ - const int keyCode = event.GetKeyCode(); - if (keyCode == WXK_ESCAPE) - { - wxCommandEvent dummy(wxEVT_COMMAND_BUTTON_CLICKED); - - //simulate click on abort button - if (pnl.m_buttonStop->IsShown()) //delegate to "cancel" button if available - { - if (wxEvtHandler* handler = pnl.m_buttonStop->GetEventHandler()) - handler->ProcessEvent(dummy); - return; - } - else if (pnl.m_buttonClose->IsShown()) - { - if (wxEvtHandler* handler = pnl.m_buttonClose->GetEventHandler()) - handler->ProcessEvent(dummy); - return; - } - } - - event.Skip(); -} - - -template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::initNewPhase() -{ - updateDialogStatus(); //evaluates "syncStat_->currentPhase()" - - //reset graphs (e.g. after binary comparison) - curveDataBytesTotal ->setValue(0, 0); - curveDataBytesCurrent->setValue(0, 0, 0); - curveDataItemsTotal ->setValue(0, 0); - curveDataItemsCurrent->setValue(0, 0, 0); - curveDataBytes->clear(); - curveDataItems->clear(); - - notifyProgressChange(); - - //start new measurement - perf = make_unique<PerfCheck>(WINDOW_REMAINING_TIME, WINDOW_BYTES_PER_SEC); - lastStatCallSpeed = -1000000; //some big number - - phaseStartMs = timeElapsed.Time(); - - updateGuiInt(false); -} - - -template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::notifyProgressChange() //noexcept! -{ - if (syncStat_) //sync running - switch (syncStat_->currentPhase()) - { - case ProcessCallback::PHASE_NONE: - assert(false); - case ProcessCallback::PHASE_SCANNING: - break; - case ProcessCallback::PHASE_COMPARING_CONTENT: - case ProcessCallback::PHASE_SYNCHRONIZING: - curveDataBytes->addRecord(timeElapsed.Time(), to<double>(syncStat_->getDataCurrent (syncStat_->currentPhase()))); - curveDataItems->addRecord(timeElapsed.Time(), syncStat_->getObjectsCurrent(syncStat_->currentPhase())); - break; - } -} - - -namespace -{ -#ifdef ZEN_WIN -enum Zorder -{ - ZORDER_CORRECT, - ZORDER_WRONG, - ZORDER_INDEFINITE, -}; - -Zorder evaluateZorder(const wxWindow& top, const wxWindow& bottom) -{ - HWND hTop = static_cast<HWND>(top.GetHWND()); - HWND hBottom = static_cast<HWND>(bottom.GetHWND()); - assert(hTop && hBottom); - - for (HWND hAbove = hBottom; hAbove; hAbove = ::GetNextWindow(hAbove, GW_HWNDPREV)) //GW_HWNDPREV means "to foreground" - if (hAbove == hTop) - return ZORDER_CORRECT; - - for (HWND hAbove = hTop; hAbove; hAbove = ::GetNextWindow(hAbove, GW_HWNDPREV)) - if (hAbove == hBottom) - return ZORDER_WRONG; - - return ZORDER_INDEFINITE; -} -#endif - - -std::wstring getDialogPhaseText(const Statistics* syncStat, bool paused, SyncProgressDialog::SyncResult finalResult) -{ - if (syncStat) //sync running - { - if (paused) - return _("Paused"); - else - switch (syncStat->currentPhase()) - { - case ProcessCallback::PHASE_NONE: - return _("Initializing..."); //dialog is shown *before* sync starts, so this text may be visible! - case ProcessCallback::PHASE_SCANNING: - return _("Scanning..."); - case ProcessCallback::PHASE_COMPARING_CONTENT: - return _("Comparing content..."); - case ProcessCallback::PHASE_SYNCHRONIZING: - return _("Synchronizing..."); - } - } - else //sync finished - switch (finalResult) - { - case SyncProgressDialog::RESULT_ABORTED: - return _("Stopped"); - case SyncProgressDialog::RESULT_FINISHED_WITH_ERROR: - case SyncProgressDialog::RESULT_FINISHED_WITH_WARNINGS: - case SyncProgressDialog::RESULT_FINISHED_WITH_SUCCESS: - return _("Completed"); - } - return std::wstring(); -} -} - - -template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::setExternalStatus(const wxString& status, const wxString& progress) //progress may be empty! -{ - //sys tray: order "top-down": jobname, status, progress - wxString systrayTooltip = jobName_.empty() ? status : L"\"" + jobName_ + L"\"\n" + status; - if (!progress.empty()) - systrayTooltip += L" " + progress; - - //window caption/taskbar; inverse order: progress, status, jobname - wxString title = progress.empty() ? status : progress + L" - " + status; - if (!jobName_.empty()) - title += L" - \"" + jobName_ + L"\""; - - //systray tooltip, if window is minimized - if (trayIcon.get()) - trayIcon->setToolTip(systrayTooltip); - - //show text in dialog title (and at the same time in taskbar) - if (parentFrame_) - { - if (parentFrame_->GetTitle() != title) - parentFrame_->SetTitle(title); - } - - //always set a title: we don't wxGTK to show "nameless window" instead - if (this->GetTitle() != title) - this->SetTitle(title); -} - - -template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::updateGuiInt(bool allowYield) -{ - if (!syncStat_) //sync not running - return; - - bool layoutChanged = false; //avoid screen flicker by calling layout() only if necessary - const long timeNow = timeElapsed.Time(); - - //sync status text - setText(*pnl.m_staticTextStatus, replaceCpy(syncStat_->currentStatusText(), L'\n', L' ')); //no layout update for status texts! - - switch (syncStat_->currentPhase()) //no matter if paused or not - { - case ProcessCallback::PHASE_NONE: - case ProcessCallback::PHASE_SCANNING: - //dialog caption, taskbar, systray tooltip - setExternalStatus(getDialogPhaseText(syncStat_, paused_, finalResult), toGuiString(syncStat_->getObjectsCurrent(ProcessCallback::PHASE_SCANNING))); //status text may be "paused"! - - //progress indicators - if (trayIcon.get()) trayIcon->setProgress(1); //100% = regular FFS logo - - //ignore graphs: should already have been cleared in initNewPhase() - - //remaining objects and data - setText(*pnl.m_staticTextRemainingObj , L"-", &layoutChanged); - setText(*pnl.m_staticTextDataRemaining, L"", &layoutChanged); - - //remaining time and speed - setText(*pnl.m_staticTextRemTime, L"-", &layoutChanged); - pnl.m_panelGraphBytes->setAttributes(pnl.m_panelGraphBytes->getAttributes().setCornerText(wxString(), Graph2D::CORNER_TOP_LEFT)); - pnl.m_panelGraphItems->setAttributes(pnl.m_panelGraphItems->getAttributes().setCornerText(wxString(), Graph2D::CORNER_TOP_LEFT)); - break; - - case ProcessCallback::PHASE_COMPARING_CONTENT: - case ProcessCallback::PHASE_SYNCHRONIZING: - { - const int itemsCurrent = syncStat_->getObjectsCurrent(syncStat_->currentPhase()); - const int itemsTotal = syncStat_->getObjectsTotal (syncStat_->currentPhase()); - const Int64 dataCurrent = syncStat_->getDataCurrent (syncStat_->currentPhase()); - const Int64 dataTotal = syncStat_->getDataTotal (syncStat_->currentPhase()); - - //add both data + obj-count, to handle "deletion-only" cases - const double fraction = dataTotal + itemsTotal == 0 ? 1 : std::max(0.0, to<double>(dataCurrent + itemsCurrent) / to<double>(dataTotal + itemsTotal)); - //yes, this may legitimately become < 0: failed rename operation falls-back to copy + delete, reducing "dataCurrent" to potentially < 0! - //---------------------------------------------------------------------------------------------------- - - //dialog caption, taskbar, systray tooltip - setExternalStatus(getDialogPhaseText(syncStat_, paused_, finalResult), fractionToString(fraction)); //status text may be "paused"! - - //progress indicators - if (trayIcon.get()) trayIcon->setProgress(fraction); - if (taskbar_.get()) taskbar_->setProgress(fraction); - - //constant line graph - curveDataBytesTotal ->setValue(timeNow, to<double>(dataTotal)); - curveDataBytesCurrent->setValue(timeNow, to<double>(dataCurrent), to<double>(dataTotal)); - curveDataItemsTotal ->setValue(timeNow, itemsTotal); - curveDataItemsCurrent->setValue(timeNow, itemsCurrent, itemsTotal); - //even though notifyProgressChange() already set the latest data, let's add another sample to have all curves consider "timeNow" - //no problem with adding too many records: CurveDataStatistics will remove duplicate entries! - curveDataBytes->addRecord(timeNow, to<double>(dataCurrent)); - curveDataItems->addRecord(timeNow, itemsCurrent); - - //remaining objects and data - setText(*pnl.m_staticTextRemainingObj, toGuiString(itemsTotal - itemsCurrent), &layoutChanged); - setText(*pnl.m_staticTextDataRemaining, L"(" + filesizeToShortString(dataTotal - dataCurrent) + L")", &layoutChanged); - //it's possible data remaining becomes shortly negative if last file synced has ADS data and the dataTotal was not yet corrected! - - //remaining time and speed - assert(perf); - if (perf) - if (numeric::dist(lastStatCallSpeed, timeNow) >= 500) - { - lastStatCallSpeed = timeNow; - - if (numeric::dist(phaseStartMs, timeNow) >= 1000) //discard stats for first second: probably messy - perf->addSample(itemsCurrent, to<double>(dataCurrent), timeNow); - - //current speed -> Win 7 copy uses 1 sec update interval instead - Opt<std::wstring> bps = perf->getBytesPerSecond(); - Opt<std::wstring> ips = perf->getItemsPerSecond(); - pnl.m_panelGraphBytes->setAttributes(pnl.m_panelGraphBytes->getAttributes().setCornerText(bps ? *bps : L"", Graph2D::CORNER_TOP_LEFT)); - pnl.m_panelGraphItems->setAttributes(pnl.m_panelGraphItems->getAttributes().setCornerText(ips ? *ips : L"", Graph2D::CORNER_TOP_LEFT)); - //setText(*pnl.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 - Opt<std::wstring> rt = perf->getRemainingTime(to<double>(dataTotal - dataCurrent)); - setText(*pnl.m_staticTextRemTime, rt ? *rt : L"-", &layoutChanged); - } - } - break; - } - - pnl.m_panelGraphBytes->Refresh(); - pnl.m_panelGraphItems->Refresh(); - - //time elapsed - const long timeElapSec = timeNow / 1000; - setText(*pnl.m_staticTextTimeElapsed, - timeElapSec < 3600 ? - wxTimeSpan::Seconds(timeElapSec).Format( L"%M:%S") : - wxTimeSpan::Seconds(timeElapSec).Format(L"%H:%M:%S"), &layoutChanged); - - //adapt layout after content changes above - if (layoutChanged) - { - pnl.m_panelProgress->Layout(); - //small statistics panels: - //pnl.m_panelItemsProcessed->Layout(); - pnl.m_panelItemsRemaining->Layout(); - pnl.m_panelTimeRemaining ->Layout(); - //pnl.m_panelTimeElapsed->Layout(); -> needed? - } - -#ifdef ZEN_WIN - //workaround Windows 7 bug messing up z-order after temporary application hangs: https://sourceforge.net/tracker/index.php?func=detail&aid=3376523&group_id=234430&atid=1093080 - //This is still needed no matter if wxDialog or wxPanel is used! (2013-07) - if (parentFrame_) - if (evaluateZorder(*this, *parentFrame_) == ZORDER_WRONG) - { - HWND hProgress = static_cast<HWND>(this->GetHWND()); - if (::IsWindowVisible(hProgress)) - { - ::ShowWindow(hProgress, SW_HIDE); //make Windows recalculate z-order - ::ShowWindow(hProgress, SW_SHOW); // - } - } -#endif - - if (allowYield) - { - //support for pause button - if (paused_) - { - /* - ZEN_ON_SCOPE_EXIT(resumeTimer()); -> crashes on Fedora; WHY??? - => likely compiler bug!!! - 1. no crash on Fedora for: ZEN_ON_SCOPE_EXIT(this->resumeTimer()); - 1. no crash if we derive from wxFrame instead of template "TopLevelDialog" - 2. no crash on Ubuntu GCC - 3. following makes GCC crash already during compilation: auto dfd = zen::makeGuard([this]{ resumeTimer(); }); - */ - - stopTimer(); - - while (paused_) - { - wxTheApp->Yield(); //receive UI message that end pause OR forceful termination! - //*first* refresh GUI (removing flicker) before sleeping! - boost::this_thread::sleep(boost::posix_time::milliseconds(UI_UPDATE_INTERVAL)); - } - //if SyncProgressDialogImpl::OnClose() already wxWindow::Destroy() on OS X then this instance is already toast! - if (wereDead) - return; //GTFO and don't call this->resumeTimer() - - resumeTimer(); - } - else - /* - /|\ - | keep this sequence to ensure one full progress update before entering pause mode! - \|/ - */ - wxTheApp->Yield(); //receive UI message that sets pause status OR forceful termination! - } - else - this->Update(); //don't wait until next idle event (who knows what blocking process comes next?) -} - - -template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::updateDialogStatus() //depends on "syncStat_, paused_, finalResult" -{ - const wxString dlgStatusTxt = getDialogPhaseText(syncStat_, paused_, finalResult); - - pnl.m_staticTextPhase->SetLabel(dlgStatusTxt); - - //status bitmap - if (syncStat_) //sync running - { - auto setStatusBitmap = [&](const wchar_t* bmpName) - { - pnl.m_animCtrlSyncing->Hide(); - pnl.m_bitmapStatus->SetBitmap(getResourceImage(bmpName)); - pnl.m_bitmapStatus->SetToolTip(dlgStatusTxt); - pnl.m_bitmapStatus->Show(); - }; - - if (paused_) - setStatusBitmap(L"status_pause"); - else - switch (syncStat_->currentPhase()) - { - case ProcessCallback::PHASE_NONE: - pnl.m_animCtrlSyncing->Hide(); - pnl.m_bitmapStatus->Hide(); - break; - - case ProcessCallback::PHASE_SCANNING: - setStatusBitmap(L"status_scanning"); - break; - - case ProcessCallback::PHASE_COMPARING_CONTENT: - setStatusBitmap(L"status_binary_compare"); - break; - - case ProcessCallback::PHASE_SYNCHRONIZING: - pnl.m_bitmapStatus->SetBitmap(getResourceImage(L"status_syncing")); - pnl.m_bitmapStatus->SetToolTip(dlgStatusTxt); - pnl.m_bitmapStatus->Show(); - pnl.m_animCtrlSyncing->Show(); - pnl.m_animCtrlSyncing->SetToolTip(dlgStatusTxt); - break; - } - } - else //sync finished - { - auto setStatusBitmap = [&](const wchar_t* bmpName, const std::wstring& tooltip) - { - pnl.m_animCtrlSyncing->Hide(); - pnl.m_bitmapStatus->SetBitmap(getResourceImage(bmpName)); - pnl.m_bitmapStatus->Show(); - pnl.m_bitmapStatus->SetToolTip(tooltip); - }; - switch (finalResult) - { - case RESULT_ABORTED: - setStatusBitmap(L"status_aborted", _("Synchronization stopped")); - break; - - case RESULT_FINISHED_WITH_ERROR: - setStatusBitmap(L"status_finished_errors", _("Synchronization completed with errors")); - break; - - case RESULT_FINISHED_WITH_WARNINGS: - setStatusBitmap(L"status_finished_warnings", _("Synchronization completed with warnings")); - break; - - case RESULT_FINISHED_WITH_SUCCESS: - setStatusBitmap(L"status_finished_success", _("Synchronization completed successfully")); - break; - } - } - - //show status on Windows 7 taskbar - if (taskbar_.get()) - { - if (syncStat_) //sync running - { - if (paused_) - taskbar_->setStatus(Taskbar::STATUS_PAUSED); - else - switch (syncStat_->currentPhase()) - { - case ProcessCallback::PHASE_NONE: - case ProcessCallback::PHASE_SCANNING: - taskbar_->setStatus(Taskbar::STATUS_INDETERMINATE); - break; - - case ProcessCallback::PHASE_COMPARING_CONTENT: - case ProcessCallback::PHASE_SYNCHRONIZING: - taskbar_->setStatus(Taskbar::STATUS_NORMAL); - break; - } - } - else //sync finished - switch (finalResult) - { - case RESULT_ABORTED: - case RESULT_FINISHED_WITH_ERROR: - taskbar_->setStatus(Taskbar::STATUS_ERROR); - break; - - case RESULT_FINISHED_WITH_WARNINGS: - case RESULT_FINISHED_WITH_SUCCESS: - taskbar_->setStatus(Taskbar::STATUS_NORMAL); - break; - } - } - - //pause button - if (syncStat_) //sync running - pnl.m_buttonPause->SetLabel(paused_ ? _("&Continue") : _("&Pause")); - - pnl.Layout(); - this->Refresh(); //a few pixels below the status text need refreshing -} - - -template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::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 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! - - this->Close(); //generate close event: do NOT destroy window unconditionally! -} - - -template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::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 - -#if (defined __WXGTK__ || defined __WXOSX__) - //In wxWidgets 2.9.3 upwards, the wxWindow::Reparent() below fails on GTK and OS X if window is frozen! http://forums.codeblocks.org/index.php?topic=13388.45 -#else - wxWindowUpdateLocker dummy(this); //badly needed on Windows -#endif - - paused_ = false; //you never know? - - //update numbers one last time (as if sync were still running) - notifyProgressChange(); //make one last graph entry at the *current* time - updateGuiInt(false); - - switch (syncStat_->currentPhase()) //no matter if paused or not - { - case ProcessCallback::PHASE_NONE: - case ProcessCallback::PHASE_SCANNING: - //set overall speed -> not needed - //items processed -> not needed - break; - - case ProcessCallback::PHASE_COMPARING_CONTENT: - case ProcessCallback::PHASE_SYNCHRONIZING: - { - const int itemsCurrent = syncStat_->getObjectsCurrent(syncStat_->currentPhase()); - const int itemsTotal = syncStat_->getObjectsTotal (syncStat_->currentPhase()); - const Int64 dataCurrent = syncStat_->getDataCurrent (syncStat_->currentPhase()); - const Int64 dataTotal = syncStat_->getDataTotal (syncStat_->currentPhase()); - assert(dataCurrent <= dataTotal); - - //set overall speed (instead of current speed) - const long timeDelta = timeElapsed.Time() - phaseStartMs; //we need to consider "time within current phase" not total "timeElapsed"! - - const wxString overallBytesPerSecond = timeDelta == 0 ? wxString() : filesizeToShortString(dataCurrent * 1000 / timeDelta) + _("/sec"); - const wxString overallItemsPerSecond = timeDelta == 0 ? wxString() : replaceCpy(_("%x items/sec"), L"%x", formatThreeDigitPrecision(itemsCurrent * 1000.0 / timeDelta)); - - pnl.m_panelGraphBytes->setAttributes(pnl.m_panelGraphBytes->getAttributes().setCornerText(overallBytesPerSecond, Graph2D::CORNER_TOP_LEFT)); - pnl.m_panelGraphItems->setAttributes(pnl.m_panelGraphItems->getAttributes().setCornerText(overallItemsPerSecond, Graph2D::CORNER_TOP_LEFT)); - - //show new element "items processed" - pnl.m_panelItemsProcessed->Show(); - pnl.m_staticTextProcessedObj ->SetLabel(toGuiString(itemsCurrent)); - pnl.m_staticTextDataProcessed->SetLabel(L"(" + filesizeToShortString(dataCurrent) + L")"); - - //hide remaining elements... - if (itemsCurrent == itemsTotal && //...if everything was processed successfully - dataCurrent == dataTotal) - pnl.m_panelItemsRemaining->Hide(); - } - break; - } - - //------- change class state ------- - finalResult = resultId; - - syncStat_ = nullptr; - abortCb_ = nullptr; - //---------------------------------- - - updateDialogStatus(); - setExternalStatus(getDialogPhaseText(syncStat_, paused_, finalResult), wxString()); - - resumeFromSystray(); //if in tray mode... - - this->EnableCloseButton(true); - - pnl.m_bpButtonMinimizeToTray->Hide(); - pnl.m_buttonStop->Disable(); - pnl.m_buttonStop->Hide(); - pnl.m_buttonPause->Disable(); - pnl.m_buttonPause->Hide(); - pnl.m_buttonClose->Show(); - pnl.m_buttonClose->Enable(); - - pnl.m_buttonClose->SetFocus(); - - pnl.bSizerExecFinished->Show(false); - - //set std order after button visibility was set - setStandardButtonOrder(*pnl.bSizerStdButtons, StdButtons().setAffirmative(pnl.m_buttonClose)); - - //hide current operation status - pnl.bSizerStatusText->Show(false); - - //show and prepare final statistics - pnl.m_notebookResult->Show(); - -#if defined ZEN_WIN || defined ZEN_LINUX - pnl.m_staticlineFooter->Hide(); //win: m_notebookResult already has a window frame -#endif - - //hide remaining time - pnl.m_panelTimeRemaining->Hide(); - - //1. re-arrange graph into results listbook - const bool wasDetached = pnl.bSizerRoot->Detach(pnl.m_panelProgress); - assert(wasDetached); - (void)wasDetached; - pnl.m_panelProgress->Reparent(pnl.m_notebookResult); - pnl.m_notebookResult->AddPage(pnl.m_panelProgress, _("Statistics"), true); - - //2. log file - const size_t posLog = 1; - assert(pnl.m_notebookResult->GetPageCount() == 1); - LogPanel* logPanel = new LogPanel(pnl.m_notebookResult, log); //owned by m_notebookResult - pnl.m_notebookResult->AddPage(logPanel, _("Log"), false); - //bSizerHoldStretch->Insert(0, logPanel, 1, wxEXPAND); - - //show log instead of graph if errors occurred! (not required for ignored warnings) - if (log.getItemCount(TYPE_ERROR | TYPE_FATAL_ERROR) > 0) - pnl.m_notebookResult->ChangeSelection(posLog); - - //GetSizer()->SetSizeHints(this); //~=Fit() //not a good idea: will shrink even if window is maximized or was enlarged by the user - pnl.Layout(); - - pnl.m_panelProgress->Layout(); - //small statistics panels: - pnl.m_panelItemsProcessed->Layout(); - pnl.m_panelItemsRemaining->Layout(); - //pnl.m_panelTimeRemaining->Layout(); - //pnl.m_panelTimeElapsed->Layout(); -> needed? - - //play (optional) sound notification after sync has completed -> only play when waiting on results dialog, seems to be pointless otherwise! - switch (finalResult) - { - case SyncProgressDialog::RESULT_ABORTED: - break; - case SyncProgressDialog::RESULT_FINISHED_WITH_ERROR: - case SyncProgressDialog::RESULT_FINISHED_WITH_WARNINGS: - case SyncProgressDialog::RESULT_FINISHED_WITH_SUCCESS: - { - const Zstring soundFile = getResourceDir() + Zstr("Sync_Complete.wav"); - if (fileExists(soundFile)) - wxSound::Play(utfCvrtTo<wxString>(soundFile), wxSOUND_ASYNC); //warning: this may fail and show a wxWidgets error message! => must not play when running FFS as a service! - } - break; - } - - //Raise(); -> don't! user may be watching a movie in the meantime ;) note: resumeFromSystray() also calls Raise()! -} - - -template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::OnOkay(wxCommandEvent& event) -{ - this->Close(); //generate close event: do NOT destroy window unconditionally! -} - - -template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::OnCancel(wxCommandEvent& event) -{ - paused_ = false; - updateDialogStatus(); //update status + pause button - - //no Layout() or UI-update here to avoid cascaded Yield()-call - if (abortCb_) - abortCb_->requestAbortion(); -} - - -template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::OnPause(wxCommandEvent& event) -{ - paused_ = !paused_; - updateDialogStatus(); //update status + pause button -} - - -template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::OnClose(wxCloseEvent& event) -{ - //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 (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; - - wereDead = true; - this->Destroy(); //wxWidgets OS X: simple "delete"!!!!!!! -} - - -template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::OnIconize(wxIconizeEvent& event) -{ - /* - propagate progress dialog minimize/maximize to parent - ----------------------------------------------------- - Fedora/Debian/Ubuntu: - - wxDialog cannot be minimized - - worse, wxGTK sends stray iconize events *after* wxDialog::Destroy() - - worse, on Fedora an iconize event is issued directly after calling Close() - - worse, even wxDialog::Hide() causes iconize event! - => nothing to do - SUSE: - - wxDialog can be minimized (it just vanishes!) and in general also minimizes parent: except for our progress wxDialog!!! - - worse, wxDialog::Hide() causes iconize event - - probably the same issues with stray iconize events like Fedora/Debian/Ubuntu - - minimize button is always shown, even if wxMINIMIZE_BOX is omitted! - => nothing to do - Mac OS X: - - wxDialog can be minimized and automatically minimizes parent - - no iconize events seen by wxWidgets! - => nothing to do - Windows: - - wxDialog can be minimized but does not also minimize parent - - iconize events only seen for manual minimize - => propagate event to parent - */ -#ifdef ZEN_WIN - if (parentFrame_) - if (parentFrame_->IsIconized() != event.IsIconized()) //caveat: if window is maximized calling Iconize(false) will erroneously un-maximize! - parentFrame_->Iconize(event.IsIconized()); -#endif - event.Skip(); -} - - -template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::minimizeToTray() -{ - if (!trayIcon.get()) - { - trayIcon = make_unique<FfsTrayIcon>([this] { this->resumeFromSystray(); }); //FfsTrayIcon lifetime is a subset of "this"'s lifetime! - //we may destroy FfsTrayIcon even while in the FfsTrayIcon callback!!!! - - updateGuiInt(false); //set tray tooltip + progress: e.g. no updates while paused - - this->Hide(); - if (parentFrame_) - parentFrame_->Hide(); -#ifdef ZEN_MAC - //hide dock icon: else user is able to forcefully show the hidden main dialog by clicking on the icon!! - ProcessSerialNumber psn = { 0, kCurrentProcess }; - ::TransformProcessType(&psn, kProcessTransformToUIElementApplication); -#endif - } -} - - -template <class TopLevelDialog> -void SyncProgressDialogImpl<TopLevelDialog>::resumeFromSystray() -{ - if (trayIcon) - { - trayIcon.reset(); - - if (parentFrame_) - { - //if (parentFrame_->IsIconized()) //caveat: if window is maximized calling Iconize(false) will erroneously un-maximize! - // parentFrame_->Iconize(false); - parentFrame_->Show(); - parentFrame_->Raise(); - } - - //if (IsIconized()) //caveat: if window is maximized calling Iconize(false) will erroneously un-maximize! - // Iconize(false); - this->Show(); - this->Raise(); - this->SetFocus(); - - updateDialogStatus(); //restore Windows 7 task bar status (e.g. required in pause mode) - updateGuiInt(false); //restore Windows 7 task bar progress (e.g. required in pause mode) - -#ifdef ZEN_MAC - ProcessSerialNumber psn = { 0, kCurrentProcess }; - ::SetFrontProcess(&psn); //call before TransformProcessType() so that OSX menu is updated correctly - //why isn't this covered by wxWindows::Raise()?? - ::TransformProcessType(&psn, kProcessTransformToForegroundApplication); //show dock icon again -#endif - } -} - -//######################################################################################## - -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, - wxFrame* parentWindow, //may be nullptr - bool showProgress, - const wxString& jobName, - const std::wstring& execWhenFinished, - std::vector<std::wstring>& execFinishedHistory) -{ - if (parentWindow) //sync from GUI - return new SyncProgressDialogImpl<wxDialog>(wxDEFAULT_DIALOG_STYLE | wxMAXIMIZE_BOX | wxMINIMIZE_BOX | wxRESIZE_BORDER, - [&](wxDialog& progDlg) { return parentWindow; }, abortCb, - notifyWindowTerminate, syncStat, parentWindow, showProgress, jobName, execWhenFinished, execFinishedHistory); - else //FFS batch job - { - auto dlg = new SyncProgressDialogImpl<wxFrame>(wxDEFAULT_FRAME_STYLE, - [](wxFrame& progDlg) { return &progDlg; }, abortCb, - notifyWindowTerminate, syncStat, parentWindow, showProgress, jobName, execWhenFinished, execFinishedHistory); - - //only top level windows should have an icon: - dlg->SetIcon(getFfsIcon()); - return dlg; - } -} |