Files
2025-12-10 14:38:26 -08:00

199 lines
6.4 KiB
C++

// *****************************************************************************
// * This file is part of the FreeFileSync project. It is distributed under *
// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
// *****************************************************************************
#include "tray_icon.h"
#include <zen/i18n.h>
#include <wx/taskbar.h>
#include <wx/menu.h>
#include <wx/icon.h> //req. by Linux
#include <wx+/dc.h>
#include <wx+/image_tools.h>
#include <wx+/image_resources.h>
using namespace zen;
using namespace fff;
namespace
{
void fillRange(wxImage& img, int pixelFirst, int pixelLast, const wxColor& col) //tolerant input range
{
const int width = img.GetWidth ();
const int height = img.GetHeight();
if (width > 0 && height > 0)
{
pixelFirst = std::max(pixelFirst, 0);
pixelLast = std::min(pixelLast, width * height);
if (pixelFirst < pixelLast)
{
const unsigned char r = col.Red (); //
const unsigned char g = col.Green(); //getting RGB involves virtual function calls!
const unsigned char b = col.Blue (); //
unsigned char* rgb = img.GetData() + pixelFirst * 3;
for (int x = pixelFirst; x < pixelLast; ++x)
{
*rgb++ = r;
*rgb++ = g;
*rgb++ = b;
}
if (img.HasAlpha()) //make progress indicator fully opaque:
std::fill(img.GetAlpha() + pixelFirst, img.GetAlpha() + pixelLast, wxIMAGE_ALPHA_OPAQUE);
}
}
}
}
//------------------------------------------------------------------------------------------------
//generate icon with progress indicator
class FfsTrayIcon::ProgressIconGenerator
{
public:
explicit ProgressIconGenerator(const wxImage& logo) : logo_(logo) {}
wxBitmap get(double fraction);
private:
const wxImage logo_;
wxBitmap iconBuf_;
int startPixBuf_ = -1;
};
wxBitmap FfsTrayIcon::ProgressIconGenerator::get(double fraction)
{
if (!logo_.IsOk() || logo_.GetWidth() <= 0 || logo_.GetHeight() <= 0)
return wxIcon();
const int pixelCount = logo_.GetWidth() * logo_.GetHeight();
const int startFillPixel = std::clamp<int>(std::floor(fraction * pixelCount), 0, pixelCount);
if (startPixBuf_ != startFillPixel)
{
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
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);
}
//fill yellow remainder
fillRange(genImage, startFillPixel, pixelCount, wxColor(240, 200, 0));
iconBuf_ = toScaledBitmap(genImage);
startPixBuf_ = startFillPixel;
}
return iconBuf_;
}
class FfsTrayIcon::TrayIconImpl : public wxTaskBarIcon
{
public:
TrayIconImpl(const std::function<void()>& requestResume) : requestResume_(requestResume)
{
Bind(wxEVT_TASKBAR_LEFT_UP, [this](wxTaskBarIconEvent& event) { if (requestResume_) requestResume_(); });
}
void disconnectCallbacks() { requestResume_ = nullptr; }
private:
wxMenu* CreatePopupMenu() override
{
if (!requestResume_)
return nullptr;
wxMenu* contextMenu = new wxMenu;
wxMenuItem* defaultItem = new wxMenuItem(contextMenu, wxID_ANY, _("&Restore"));
//wxWidgets font mess-up:
//1. font must be set *before* wxMenu::Append()!
//2. don't use defaultItem->GetFont(); making it bold creates a huge font size for some reason
contextMenu->Append(defaultItem);
contextMenu->Bind(wxEVT_COMMAND_MENU_SELECTED, [this](wxCommandEvent& event) { if (requestResume_) requestResume_(); }, defaultItem->GetId());
return contextMenu; //ownership transferred to caller
}
//void onLeftDownClick(wxEvent& event)
//{
// //copied from wxTaskBarIconBase::OnRightButtonDown()
// if (wxMenu* menu = CreatePopupMenu())
// {
// PopupMenu(menu);
// delete menu;
// }
//}
std::function<void()> requestResume_;
};
FfsTrayIcon::FfsTrayIcon(const std::function<void()>& requestResume) :
trayIcon_(new TrayIconImpl(requestResume)),
progressIcon_(std::make_unique<ProgressIconGenerator>(loadImage("start_sync", dipToScreen(24))))
{
[[maybe_unused]] const bool rv = trayIcon_->SetIcon(progressIcon_->get(activeFraction_), activeToolTip_);
assert(rv); //caveat wxTaskBarIcon::SetIcon() can return true, even if not wxTaskBarIcon::IsAvailable()!!!
}
FfsTrayIcon::~FfsTrayIcon()
{
trayIcon_->disconnectCallbacks(); //TrayIconImpl has longer lifetime than FfsTrayIcon: avoid callback!
trayIcon_->RemoveIcon();
//*schedule* for destruction: delete during next idle event (handle late window messages, e.g. when double-clicking)
trayIcon_->Destroy(); //uses wxPendingDelete
}
void FfsTrayIcon::setToolTip(const wxString& toolTip)
{
activeToolTip_ = toolTip;
trayIcon_->SetIcon(progressIcon_->get(activeFraction_), activeToolTip_); //another wxWidgets design bug: non-orthogonal method!
}
void FfsTrayIcon::setProgress(double fraction)
{
activeFraction_ = fraction;
trayIcon_->SetIcon(progressIcon_->get(activeFraction_), activeToolTip_);
}