Audacity 3.2.0
SnappingToolBar.cpp
Go to the documentation of this file.
1/* SPDX-License-Identifier: GPL-2.0-or-later */
2/**********************************************************************
3
4 Audacity: A Digital Audio Editor
5
6 SnappingToolBar.cpp
7
8 Dmitry Vedenko
9
10*******************************************************************/
11
12#include "SnappingToolBar.h"
13
14#include <algorithm>
15#include <cassert>
16
17#include <wx/sizer.h>
18#include <wx/checkbox.h>
19#include <wx/combo.h>
20#include <wx/menu.h>
21#include <wx/textctrl.h>
22
23#include "ToolManager.h"
24
25
26#include "widgets/BasicMenu.h"
28
30
31#include "Prefs.h"
32#include "Project.h"
33#include "ViewInfo.h"
34
35#include "AllThemeResources.h"
36
37#include "ProjectSnap.h"
38
39#if wxUSE_ACCESSIBILITY
40#include "WindowAccessible.h"
41#endif
42
43namespace
44{
47{
48 auto item = SnapFunctionsRegistry::Find(snapTo);
49 return item != nullptr ? item->label : XO("Unknown");
50}
51
52/*
53 * This class provides a hack to use popup menu instead of the dropdown list.
54 * This allows to organize the list of items in a more user-friendly way.
55 */
56class SnapModePopup final : public wxComboPopup
57{
58public:
60 : mProject { project }
61 , mSnappingModeChangedSubscription(ProjectSnap::Get(project).Subscribe(
62 [this](auto& msg) {
63 UpdateCurrentIndex(msg.newSnapTo);
64
65 auto comboCtrl = GetComboCtrl();
66
67 comboCtrl->SetValue(
68 GetSnapToLabel(msg.newSnapTo).Translation());
69
70 comboCtrl->SetName(GetComboCtrl()->GetValue());
71 }))
72 {
73 }
74
75 void Init () override
76 {
77 // Build a linear list from all the items in the snap functions registry
78 SnapFunctionsRegistry::Visit([this](const SnapRegistryItem& item, auto&) {
79 mSnapToList.push_back(item.name);
80 });
81
82 UpdateCurrentIndex(ReadSnapTo());
83 }
84
85 bool Create(wxWindow* parent) override
86 {
87 mControl = safenew wxWindow(parent, wxID_ANY);
88
89 // This call cannot happen in Init(), because the combobox is not yet in a valid
90 // state. Doing a deferred call from Init() is unsafe,
91 // as in some cases Audacity recreates the combobox multiple times before the next
92 // event loop iteration.
93 GetComboCtrl()->SetValue(GetStringValue());
94
95 return mControl;
96 }
97
98 wxWindow* GetControl() override
99 {
100 return mControl;
101 }
102
103 wxString GetStringValue() const override
104 {
106 }
107
108 void OnPopup() override
109 {
110 // Build a popup menu based on snap functions registry
111 wxMenu menu;
112 std::vector<wxMenu*> menuStack{ &menu };
113
114 const auto visitor = std::tuple{
115 [&](const SnapRegistryGroup& item, auto &) {
116 if (item.Inlined())
117 return;
118
119 auto menu = safenew wxMenu;
120
121 menuStack.back()->AppendSubMenu(menu, item.Label().Translation());
122 menuStack.push_back(menu);
123 },
124 [&, this](const SnapRegistryItem& item, auto &) {
125 auto menuItem = menuStack.back()->AppendCheckItem(wxID_ANY, item.label.Translation());
126
127 if (ReadSnapTo() == item.name)
128 menuItem->Check();
129
130 menuStack.back()->Bind(
131 wxEVT_MENU,
132 [this, id = item.name](wxCommandEvent&) {
133 ProjectSnap::Get(mProject).SetSnapTo(id);
134 },
135 menuItem->GetId()
136 );
137 },
138 [&](const SnapRegistryGroup& item, auto &) {
139 assert(!menuStack.empty());
140
141 if (item.Inlined())
142 {
143 menuStack.back()->AppendSeparator();
144 return;
145 }
146
147 menuStack.pop_back();
148 }
149 };
151
152 BasicMenu::Handle { &menu }.Popup(
153 wxWidgetsWindowPlacement { GetComboCtrl() },
154 { 0, GetComboCtrl()->GetSize().y });
155
156 // Hide the combobox list after the menu was closed
157 BasicUI::CallAfter([this] { Dismiss(); });
158 }
159
160 void SetStringValue(const wxString& value) override
161 {
162 wxComboPopup::SetStringValue(value);
163 }
164
165 bool FindItem(const wxString& item, wxString* trueItem = NULL) override
166 {
167 return wxComboPopup::FindItem(item, trueItem);
168 }
169
170 void OnComboKeyEvent(wxKeyEvent& event) override
171 {
172 const auto keyCode = event.GetKeyCode();
173
174 if (keyCode == WXK_RETURN || keyCode == WXK_NUMPAD_ENTER)
175 {
176 GetComboCtrl()->ShowPopup();
177 return;
178 }
179
180 int direction = 0;
181
182 if (
183 keyCode == WXK_UP || keyCode == WXK_NUMPAD_UP || keyCode == WXK_LEFT ||
184 keyCode == WXK_NUMPAD_LEFT)
185 direction = -1;
186 else if (
187 keyCode == WXK_DOWN || keyCode == WXK_NUMPAD_DOWN ||
188 keyCode == WXK_RIGHT || keyCode == WXK_NUMPAD_RIGHT)
189 direction = 1;
190
191 if (direction == 0)
192 return;
193
194 const auto newIndex = std::clamp<ptrdiff_t>(
195 mCurrentIndex + direction, 0, mSnapToList.size() - 1);
196
197 if (newIndex == mCurrentIndex)
198 return;
199
200 mCurrentIndex = newIndex;
201
202 ProjectSnap::Get(mProject).SetSnapTo(mSnapToList[mCurrentIndex]);
203 }
204
205 void OnComboCharEvent(wxKeyEvent& event) override
206 {
207 // Consume the event to prevent editing
208 }
209
210 void UpdateCurrentIndex(const Identifier& identifier)
211 {
212 if (
213 mCurrentIndex < mSnapToList.size() &&
214 mSnapToList[mCurrentIndex] == identifier)
215 return;
216
217 mCurrentIndex = static_cast<size_t>(std::distance(
218 mSnapToList.begin(),
219 std::find(mSnapToList.begin(), mSnapToList.end(), identifier)));
220 }
221
222private:
224 wxWeakRef<wxWindow> mControl;
225
226 std::vector<Identifier> mSnapToList;
227 std::ptrdiff_t mCurrentIndex { -1 };
228
230};
231}
232
234
235BEGIN_EVENT_TABLE(SnappingToolBar, ToolBar)
238
240{
241 return wxT("Snapping");
242}
243
245 : ToolBar(project, XO("Snapping"), ID())
246 , mSnappingModeChangedSubscription(ProjectSnap::Get(mProject).Subscribe(
247 [this](auto settings)
248 {
250 mSnapModeCheckBox->SetValue(
251 settings.newSnapMode != SnapMode::SNAP_OFF);
252
253 if (mSnapToCombo)
254 mSnapToCombo->Enable(settings.newSnapMode != SnapMode::SNAP_OFF);
255 }))
256{
257#ifdef __WXGTK__
258 const auto height = 2 * toolbarSingle;
259 SetMinSize({ -1, height });
260 SetMaxSize({ -1, height });
261#endif
262}
263
265{
266}
267
269{
270 return true;
271}
272
274{
275 return BotDockID;
276}
277
279{
280 auto &toolManager = ToolManager::Get( project );
281 return *static_cast<SnappingToolBar*>(toolManager.GetToolBar(ID()));
282}
283
285{
286 return Get( const_cast<AudacityProject&>( project )) ;
287}
288
289void SnappingToolBar::Create(wxWindow* parent)
290{
291 ToolBar::Create(parent);
292 UpdatePrefs();
293}
294
296{
297 SetBackgroundColour( theTheme.Colour( clrMedium ) );
298
299 auto sizer = safenew wxFlexGridSizer(1, 1, 1);
300 Add(sizer, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
301
302 auto boxSizer = safenew wxBoxSizer(wxHORIZONTAL);
303
304 mSnapModeCheckBox = safenew wxCheckBox(this, wxID_ANY, {});
305
307#if wxUSE_ACCESSIBILITY
308 // so that name can be set on a standard control
309 mSnapModeCheckBox->SetAccessible(
311#endif
312
313 auto snapLabelCtrl = safenew auStaticText(this, SnapLabel.Translation());
314
315 boxSizer->Add(mSnapModeCheckBox, 0, wxEXPAND, 0);
316 boxSizer->Add(snapLabelCtrl, 0, wxEXPAND, 0);
317
318 sizer->Add(boxSizer, 0, wxBOTTOM | wxRIGHT | wxEXPAND, 5);
319
320 const bool snapEnabled =
322
323 mSnapModeCheckBox->SetValue(snapEnabled);
324
325
326 mSnapToCombo = safenew wxComboCtrl(
327 this, wxID_ANY, {}, wxDefaultPosition, wxDefaultSize /*, wxCB_READONLY*/);
328#if wxUSE_ACCESSIBILITY
329 // so that name can be set on a standard control
330 mSnapToCombo->GetTextCtrl()->SetAccessible(
331 safenew WindowAccessible(mSnapToCombo->GetTextCtrl()));
332#endif
333
334 //mSnapToCombo->SetEditable(false);
335 mSnapToCombo->SetPopupControl(safenew SnapModePopup(mProject));
336 /* i18n-hint: combo box is the type of the control/widget */
337 mSnapToCombo->GetTextCtrl()->SetName(XO("Snap to combo box").Translation());
338 /* Narrator screen reader by default reads the accessibility name of the
339 containing window, which by default is combobox, so set it to an empty string. */
340 mSnapToCombo->SetLabel(wxT(""));
341 mSnapToCombo->Enable(snapEnabled);
342 mSnapToCombo->SetMinSize(wxSize(150, -1));
343
344 sizer->Add(mSnapToCombo, 1, wxRIGHT | wxEXPAND, 5);
345
346 mSnapModeCheckBox->Bind(
347 wxEVT_CHECKBOX, [this](auto&) { OnSnapModeChanged(); });
348
349 mSnapModeCheckBox->Bind(
350 wxEVT_CHAR_HOOK,
351 [this](auto& evt)
352 {
353 const auto keyCode = evt.GetKeyCode();
354
355 if (keyCode != WXK_NUMPAD_ENTER && keyCode != WXK_RETURN)
356 {
357 evt.Skip();
358 return;
359 }
360
361 mSnapModeCheckBox->SetValue(!mSnapModeCheckBox->GetValue());
362
364 });
365
366 mSnapModeCheckBox->Bind(
367 wxEVT_SET_FOCUS,
368 [snapLabelCtrl](auto&) { snapLabelCtrl->SetSelected(true); });
369
370 mSnapModeCheckBox->Bind(
371 wxEVT_KILL_FOCUS,
372 [snapLabelCtrl](auto&) { snapLabelCtrl->SetSelected(false); });
373
374 // When the focus is lost, clear out any text selection.
375 // See https://github.com/audacity/audacity/issues/4427
376 mSnapToCombo->Bind(
377 wxEVT_KILL_FOCUS, [this](auto&) { mSnapToCombo->SelectNone(); });
378
379 snapLabelCtrl->Bind(
380 wxEVT_LEFT_UP,
381 [this](auto&)
382 {
383 mSnapModeCheckBox->SetValue(!mSnapModeCheckBox->GetValue());
385 });
386
388 Fit();
389 Layout();
390}
391
393{
394 // Set label to pull in language change
395 SetLabel(XO("Snapping"));
396
398 // Give base class a chance
400}
401
403{
404}
405
406void SnappingToolBar::OnSize(wxSizeEvent& evt)
407{
408 Refresh( true );
409
410 evt.Skip();
411}
412
414{
415 const bool snapEnabled = mSnapModeCheckBox->GetValue();
416
419
420 mSnapToCombo->Enable(snapEnabled);
421
422
423 // wxEVT_KILL_FOCUS is not always sent by wxWidgets.
424 // Remove any selection from the combo box if we've disabled it.
425 if (!snapEnabled)
426 mSnapToCombo->SelectNone();
427}
428
432} };
433
434namespace {
436 /* i18n-hint: Clicking this menu item shows the toolbar
437 for selecting a time range of audio */
438 SnappingToolBar::ID(), wxT("ShowSnappingTB"), XXO("&Snapping Toolbar")
439};
440}
441
wxT("CloseDown"))
Abstractions of menus and their items.
END_EVENT_TABLE()
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
#define safenew
Definition: MemoryX.h:10
Identifier ReadSnapTo()
Definition: SnapUtils.cpp:90
IMPLEMENT_CLASS(SnappingToolBar, ToolBar)
static RegisteredToolbarFactory factory
const auto project
THEME_API Theme theTheme
Definition: Theme.cpp:82
static constexpr auto toolbarSingle
Height of a single line toolbar.
Definition: ToolBar.h:53
static Settings & settings()
Definition: TrackInfo.cpp:51
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
void Popup(const BasicUI::WindowPlacement &window, const Point &pos={})
Display the menu at pos, invoke at most one action, then hide it.
Definition: BasicMenu.cpp:209
An explicitly nonlocalized string, not meant for the user to see.
Definition: Identifier.h:22
A move-only handle representing a connection to a Publisher.
Definition: Observer.h:70
Project snapping settings.
Definition: ProjectSnap.h:29
void SetSnapMode(SnapMode mode)
Definition: ProjectSnap.cpp:41
void SetSnapTo(Identifier snap)
Definition: ProjectSnap.cpp:59
static ProjectSnap & Get(AudacityProject &project)
Definition: ProjectSnap.cpp:27
SnapMode GetSnapMode() const
Definition: ProjectSnap.cpp:54
static Identifier ID()
void Populate() override
virtual ~SnappingToolBar()
void RegenerateTooltips() override
bool ShownByDefault() const override
Whether the toolbar should be shown by default. Default implementation returns true.
void OnSize(wxSizeEvent &evt)
void UpdatePrefs() override
void Create(wxWindow *parent) override
SnappingToolBar(AudacityProject &project)
static SnappingToolBar & Get(AudacityProject &project)
wxWeakRef< wxComboCtrl > mSnapToCombo
DockID DefaultDockID() const override
Which dock the toolbar defaults into. Default implementation chooses the top dock.
wxWeakRef< wxCheckBox > mSnapModeCheckBox
wxColour & Colour(int iIndex)
Works with ToolManager and ToolDock to provide a dockable window in which buttons can be placed.
Definition: ToolBar.h:73
AudacityProject & mProject
Definition: ToolBar.h:247
DockID
Identifies one of the docking areas for toolbars.
Definition: ToolBar.h:91
@ BotDockID
Definition: ToolBar.h:93
void Add(wxWindow *window, int proportion=0, int flag=wxALIGN_TOP, int border=0, wxObject *userData=NULL)
Definition: ToolBar.cpp:709
void SetLabel(const wxString &label) override
Definition: ToolBar.cpp:408
void UpdatePrefs() override
Definition: ToolBar.cpp:622
virtual void Create(wxWindow *parent)
Definition: ToolBar.cpp:492
wxWindowPtr< ToolBar > Holder
Definition: ToolBar.h:77
static ToolManager & Get(AudacityProject &project)
Holds a msgid for the translation catalog; may also bind format arguments.
wxString Translation() const
TranslatableString Stripped(unsigned options=MenuCodes) const
non-mutating, constructs another TranslatableString object
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...
void SetStringValue(const wxString &value) override
bool FindItem(const wxString &item, wxString *trueItem=NULL) override
is like wxStaticText, except it can be themed. wxStaticText can't be.
Definition: auStaticText.h:20
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:214
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:202
TranslatableString GetSnapToLabel(Identifier snapTo)
const Identifier name
Definition: Registry.h:86
static const SnapRegistryItem * Find(const Identifier &id)
Definition: SnapUtils.cpp:121
static void Visit(const SnapRegistryVisitor &visitor)
Definition: SnapUtils.cpp:110
const TranslatableString label
Definition: SnapUtils.h:59
const TranslatableString & Label() const
Definition: SnapUtils.h:73
bool Inlined() const
Definition: SnapUtils.h:72
Window placement information for wxWidgetsBasicUI can be constructed from a wxWindow pointer.