diff options
Diffstat (limited to 'ui/tray_icon.cpp')
-rw-r--r-- | ui/tray_icon.cpp | 224 |
1 files changed, 100 insertions, 124 deletions
diff --git a/ui/tray_icon.cpp b/ui/tray_icon.cpp index 6a9c640c..3826458f 100644 --- a/ui/tray_icon.cpp +++ b/ui/tray_icon.cpp @@ -14,8 +14,6 @@ #include "../lib/resources.h" -const wxEventType FFS_REQUEST_RESUME_TRAY_EVENT = wxNewEventType(); - namespace { void fillRange(wxImage& img, int pixelFirst, int pixelLast, const wxColor& col) //tolerant input range @@ -52,90 +50,56 @@ void fillRange(wxImage& img, int pixelFirst, int pixelLast, const wxColor& col) wxIcon generateProgressIcon(const wxImage& logo, double fraction) //generate icon with progress indicator { - if (!logo.IsOk()) + if (!logo.IsOk() || logo.GetWidth() <= 0 || logo.GetHeight() <= 0) return wxIcon(); const int pixelCount = logo.GetWidth() * logo.GetHeight(); - const int startFillPixel = std::min(numeric::round(fraction * pixelCount), pixelCount); + const int startFillPixel = numeric::confineCpy(numeric::round(fraction * pixelCount), 0, pixelCount); //minor optimization static std::pair<int, wxIcon> buffer = std::make_pair(-1, wxNullIcon); if (buffer.first != startFillPixel) { - //progress bar - if (logo.GetWidth() > 0 && - logo.GetHeight() > 0) - { - wxImage genImage(logo.Copy()); //workaround wxWidgets' screwed-up design from hell: their copy-construction implements reference-counting WITHOUT copy-on-write! - - //gradually make FFS icon brighter while nearing completion - zen::brighten(genImage, -200 * (1 - fraction)); - - //fill black border row - if (startFillPixel <= pixelCount - genImage.GetWidth()) - { - /* - -------- - ---bbbbb - bbbbSyyy S : start yellow remainder - yyyyyyyy - */ - int bStart = startFillPixel - genImage.GetWidth(); - if (bStart % genImage.GetWidth() != 0) //add one more black pixel, see ascii-art - --bStart; - fillRange(genImage, bStart, startFillPixel, *wxBLACK); - } - else if (startFillPixel < pixelCount) - { - //special handling for last row - /* - -------- - -------- - ---bbbbb - ---bSyyy S : start yellow remainder - */ - int bStart = startFillPixel - genImage.GetWidth() - 1; - int bEnd = (bStart / genImage.GetWidth() + 1) * genImage.GetWidth(); - - fillRange(genImage, bStart, bEnd, *wxBLACK); - fillRange(genImage, startFillPixel - 1, startFillPixel, *wxBLACK); - } + wxImage genImage(logo.Copy()); //workaround wxWidgets' screwed-up design from hell: their copy-construction implements reference-counting WITHOUT copy-on-write! - //fill yellow remainder - fillRange(genImage, startFillPixel, pixelCount, wxColour(240, 200, 0)); + //gradually make FFS icon brighter while nearing completion + zen::brighten(genImage, -200 * (1 - fraction)); + //fill black border row + if (startFillPixel <= pixelCount - genImage.GetWidth()) + { /* - const int indicatorWidth = genImage.GetWidth() * .4; - const int indicatorXBegin = std::ceil((genImage.GetWidth() - indicatorWidth) / 2.0); - const int indicatorYBegin = genImage.GetHeight() - indicatorHeight; - - //draw progress indicator: do NOT use wxDC::DrawRectangle! Doesn't respect alpha in Windows, but does in Linux! - //We need a simple, working solution: - - for (int row = indicatorYBegin; row < genImage.GetHeight(); ++row) - { - for (int col = indicatorXBegin; col < indicatorXBegin + indicatorWidth; ++col) - { - unsigned char* const pixelBegin = data + (row * genImage.GetWidth() + col) * 3; - pixelBegin[0] = 240; //red - pixelBegin[1] = 200; //green - pixelBegin[2] = 0; //blue - } - } - - if (genImage.HasAlpha()) - { - unsigned char* const alpha = genImage.GetAlpha(); - //make progress indicator fully opaque: - for (int row = indicatorYBegin; row < genImage.GetHeight(); ++row) - ::memset(alpha + row * genImage.GetWidth() + indicatorXBegin, wxIMAGE_ALPHA_OPAQUE, indicatorWidth); - } + -------- + ---bbbbb + bbbbSyyy S : start yellow remainder + yyyyyyyy */ - buffer.second.CopyFromBitmap(wxBitmap(genImage)); + int bStart = startFillPixel - genImage.GetWidth(); + if (bStart % genImage.GetWidth() != 0) //add one more black pixel, see ascii-art + --bStart; + fillRange(genImage, bStart, startFillPixel, *wxBLACK); + } + else if (startFillPixel < pixelCount) + { + //special handling for last row + /* + -------- + -------- + ---bbbbb + ---bSyyy S : start yellow remainder + */ + int bStart = startFillPixel - genImage.GetWidth() - 1; + int bEnd = (bStart / genImage.GetWidth() + 1) * genImage.GetWidth(); + + fillRange(genImage, bStart, bEnd, *wxBLACK); + fillRange(genImage, startFillPixel - 1, startFillPixel, *wxBLACK); } - else - buffer.second = wxIcon(); + + //fill yellow remainder + fillRange(genImage, startFillPixel, pixelCount, wxColour(240, 200, 0)); + + buffer.second.CopyFromBitmap(wxBitmap(genImage)); } return buffer.second; @@ -145,7 +109,7 @@ wxIcon generateProgressIcon(const wxImage& logo, double fraction) //generate ico enum Selection { - CONTEXT_RESTORE = 1, //wxWidgets: "A MenuItem ID of Zero does not work under Mac" + CONTEXT_RESTORE = 1, //wxWidgets: "A MenuItem ID of zero does not work under Mac" CONTEXT_ABOUT = wxID_ABOUT }; } @@ -154,95 +118,107 @@ enum Selection class FfsTrayIcon::TaskBarImpl : public wxTaskBarIcon { public: - TaskBarImpl(FfsTrayIcon& parent) : parent_(&parent) {} + TaskBarImpl(const std::function<void()>& onRequestResume) : onRequestResume_(onRequestResume) + { + Connect(wxEVT_TASKBAR_LEFT_DCLICK, wxCommandEventHandler(TaskBarImpl::OnDoubleClick), nullptr, this); //register double-click + } - void parentHasDied() { parent_ = nullptr; } + //virtual ~TaskBarImpl(){} + + void dontCallbackAnymore() { onRequestResume_ = nullptr; } private: virtual wxMenu* CreatePopupMenu() { - if (!parent_) + if (!onRequestResume_) return nullptr; wxMenu* contextMenu = new wxMenu; - contextMenu->Append(CONTEXT_ABOUT, _("&About")); - contextMenu->AppendSeparator(); contextMenu->Append(CONTEXT_RESTORE, _("&Restore")); + contextMenu->AppendSeparator(); + contextMenu->Append(CONTEXT_ABOUT, _("&About")); //event handling - contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(FfsTrayIcon::OnContextMenuSelection), nullptr, parent_); + contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TaskBarImpl::OnContextMenuSelection), nullptr, this); return contextMenu; //ownership transferred to caller } - FfsTrayIcon* parent_; + void OnContextMenuSelection(wxCommandEvent& event) + { + switch (static_cast<Selection>(event.GetId())) + { + case CONTEXT_ABOUT: + { + //ATTENTION: the modal dialog below does NOT disable all GUI input, e.g. user may still double-click on tray icon + //which will implicitly destroy the tray icon while still showing the modal dialog + SetEvtHandlerEnabled(false); + ZEN_ON_SCOPE_EXIT(SetEvtHandlerEnabled(true)); + + zen::showAboutDialog(nullptr); + } + break; + + case CONTEXT_RESTORE: + if (onRequestResume_) + onRequestResume_(); + break; + } + } + + void OnDoubleClick(wxCommandEvent& event) + { + if (onRequestResume_) + onRequestResume_(); + } + + std::function<void()> onRequestResume_; }; -FfsTrayIcon::FfsTrayIcon() : - trayIcon(new TaskBarImpl(*this)), - fractionLast(1), //show FFS logo by default +FfsTrayIcon::FfsTrayIcon(const std::function<void()>& onRequestResume) : + trayIcon(new TaskBarImpl(onRequestResume)), + activeFraction(1), //show FFS logo by default #if defined ZEN_WIN || defined ZEN_MAC //16x16 seems to be the only size that is shown correctly on OS X logo(getResourceImage(L"FFS_tray_16x16").ConvertToImage()) #elif defined ZEN_LINUX logo(getResourceImage(L"FFS_tray_24x24").ConvertToImage()) #endif { - trayIcon->SetIcon(generateProgressIcon(logo, fractionLast), L"FreeFileSync"); - trayIcon->Connect(wxEVT_TASKBAR_LEFT_DCLICK, wxCommandEventHandler(FfsTrayIcon::OnDoubleClick), nullptr, this); //register double-click + trayIcon->SetIcon(generateProgressIcon(logo, activeFraction), L"FreeFileSync"); } FfsTrayIcon::~FfsTrayIcon() { - trayIcon->RemoveIcon(); //hide icon until final deletion takes place - trayIcon->Disconnect(wxEVT_TASKBAR_LEFT_DCLICK, wxCommandEventHandler(FfsTrayIcon::OnDoubleClick), nullptr, this); - trayIcon->parentHasDied(); //TaskBarImpl (potentially) has longer lifetime than FfsTrayIcon: avoid callback! - - //use wxWidgets delayed destruction: delete during next idle loop iteration (handle late window messages, e.g. when double-clicking) - if (!wxPendingDelete.Member(trayIcon)) - wxPendingDelete.Append(trayIcon); -} + trayIcon->dontCallbackAnymore(); //TaskBarImpl has longer lifetime than FfsTrayIcon: avoid callback! + /* + This is not working correctly on OS X! It seems both wxTaskBarIcon::RemoveIcon() and ~wxTaskBarIcon() are broken and do NOT immediately + remove the icon from the system tray! Only some time later in the event loop which called these functions they will be removed. + Maybe some system component has still shared ownership? Objective C auto release pools are freed at the end of the current event loop... + Anyway, wxWidgets fails to disconnect the wxTaskBarIcon event handlers before calling "[m_statusitem release]"! -void FfsTrayIcon::setToolTip(const wxString& toolTip) -{ - toolTipLast = toolTip; - trayIcon->SetIcon(generateProgressIcon(logo, fractionLast), toolTip); //another wxWidgets design bug: non-orthogonal method! -} + => !!!clicking on the icon after ~wxTaskBarIcon ran crashes the application!!! + - if ~wxTaskBarIcon() ran from the SyncProgressDialog::updateGui() event loop (e.g. user manually clicking the icon) => icon removed on return + - if ~wxTaskBarIcon() ran from SyncProgressDialog::closeWindowDirectly() => leaves the icon dangling until user closes this dialog and outter event loop runs! + */ -void FfsTrayIcon::setProgress(double fraction) -{ - fractionLast = fraction; - trayIcon->SetIcon(generateProgressIcon(logo, fraction), toolTipLast); + trayIcon->RemoveIcon(); //required on Windows: unlike on OS X, wxPendingDelete does not kick in before main event loop! + //use wxWidgets delayed destruction: delete during next idle loop iteration (handle late window messages, e.g. when double-clicking) + wxPendingDelete.Append(trayIcon); } -void FfsTrayIcon::OnContextMenuSelection(wxCommandEvent& event) +void FfsTrayIcon::setToolTip(const wxString& toolTip) { - switch (static_cast<Selection>(event.GetId())) - { - case CONTEXT_ABOUT: - { - //ATTENTION: the modal dialog below does NOT disable all GUI input, e.g. user may still double-click on tray icon - //which will implicitly destroy the tray icon while still showing the modal dialog - trayIcon->SetEvtHandlerEnabled(false); - zen::showAboutDialog(nullptr); - trayIcon->SetEvtHandlerEnabled(true); - } - break; - case CONTEXT_RESTORE: - { - wxCommandEvent dummy(FFS_REQUEST_RESUME_TRAY_EVENT); - ProcessEvent(dummy); - } - } + activeToolTip = toolTip; + trayIcon->SetIcon(generateProgressIcon(logo, activeFraction), activeToolTip); //another wxWidgets design bug: non-orthogonal method! } -void FfsTrayIcon::OnDoubleClick(wxCommandEvent& event) +void FfsTrayIcon::setProgress(double fraction) { - wxCommandEvent dummy(FFS_REQUEST_RESUME_TRAY_EVENT); - ProcessEvent(dummy); + activeFraction = fraction; + trayIcon->SetIcon(generateProgressIcon(logo, activeFraction), activeToolTip); } - |