1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
// *****************************************************************************
// * 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 *
// *****************************************************************************
#ifndef CONTEXT_MENU_H_18047302153418174632141234
#define CONTEXT_MENU_H_18047302153418174632141234
#include <map>
#include <vector>
#include <functional>
#include <wx/menu.h>
#include <wx/app.h>
#include "dc.h"
warn_static("remove after test")
#include "image_tools.h"
/* A context menu supporting lambda callbacks!
Usage:
ContextMenu menu;
menu.addItem(L"Some Label", [&]{ ...do something... }); -> capture by reference is fine, as long as captured variables have at least scope of ContextMenu::popup()!
...
menu.popup(wnd); */
namespace zen
{
inline
void setImage(wxMenuItem& menuItem, const wxImage& img)
{
menuItem.SetBitmap(toBitmapBundle(img));
}
class ContextMenu : private wxEvtHandler
{
public:
ContextMenu() {}
void addItem(const wxString& label, const std::function<void()>& command, const wxImage& img = wxNullImage, bool enabled = true)
{
wxMenuItem* newItem = new wxMenuItem(menu_.get(), wxID_ANY, label); //menu owns item!
if (img.IsOk())
setImage(*newItem, img); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason
menu_->Append(newItem);
if (!enabled)
newItem->Enable(false); //do not enable BEFORE appending item! wxWidgets screws up for yet another crappy reason
commandList_[newItem->GetId()] = command; //defer event connection, this may be a submenu only!
}
void addCheckBox(const wxString& label, const std::function<void()>& command, bool checked, bool enabled = true)
{
wxMenuItem* newItem = menu_->AppendCheckItem(wxID_ANY, label);
newItem->Check(checked);
if (!enabled)
newItem->Enable(false);
commandList_[newItem->GetId()] = command;
}
void addRadio(const wxString& label, const std::function<void()>& command, bool selected, bool enabled = true)
{
wxMenuItem* newItem = menu_->AppendRadioItem(wxID_ANY, label);
newItem->Check(selected);
if (!enabled)
newItem->Enable(false);
commandList_[newItem->GetId()] = command;
}
void addSeparator() { menu_->AppendSeparator(); }
void addSubmenu(const wxString& label, ContextMenu& submenu, const wxImage& img = wxNullImage, bool enabled = true) //invalidates submenu!
{
//transfer submenu commands:
commandList_.insert(submenu.commandList_.begin(), submenu.commandList_.end());
submenu.commandList_.clear();
submenu.menu_->SetNextHandler(menu_.get()); //on wxGTK submenu events are not propagated to their parent menu by default!
wxMenuItem* newItem = new wxMenuItem(menu_.get(), wxID_ANY, label, L"", wxITEM_NORMAL, submenu.menu_.release()); //menu owns item, item owns submenu!
if (img.IsOk())
setImage(*newItem, img); //do not set AFTER appending item! wxWidgets screws up for yet another crappy reason
menu_->Append(newItem);
if (!enabled)
newItem->Enable(false);
}
void popup(wxWindow& wnd, const wxPoint& pos = wxDefaultPosition) //show popup menu + process lambdas
{
//eventually all events from submenu items will be received by this menu
for (const auto& [itemId, command] : commandList_)
menu_->Bind(wxEVT_COMMAND_MENU_SELECTED, [command /*clang bug*/= command](wxCommandEvent& event) { command(); }, itemId);
wnd.PopupMenu(menu_.get(), pos);
wxTheApp->ProcessPendingEvents(); //make sure lambdas are evaluated before going out of scope;
//although all events seem to be processed within wxWindows::PopupMenu, we shouldn't trust wxWidgets in this regard
}
private:
ContextMenu (const ContextMenu&) = delete;
ContextMenu& operator=(const ContextMenu&) = delete;
std::unique_ptr<wxMenu> menu_ = std::make_unique<wxMenu>();
std::map<int /*item id*/, std::function<void()> /*command*/> commandList_;
};
//GTK: image must be set *before* adding wxMenuItem to menu or it won't show => workaround:
inline //also needed on Windows + macOS since wxWidgets 3.1.6 (thanks?)
void fixMenuIcons(wxMenu& menu)
{
std::vector<std::pair<wxMenuItem*, size_t /*pos*/>> itemsWithBmp;
{
size_t pos = 0;
for (wxMenuItem* item : menu.GetMenuItems())
{
if (item->GetBitmap().IsOk())
itemsWithBmp.emplace_back(item, pos);
++pos;
}
}
for (const auto& [item, pos] : itemsWithBmp)
if (!menu.Insert(pos, menu.Remove(item))) //detach + reinsert
assert(false);
}
}
#endif //CONTEXT_MENU_H_18047302153418174632141234
|