Audacity 3.2.0
JournalEvents.cpp
Go to the documentation of this file.
1/*!********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file JournalEvents.cpp
6
7 Paul Licameli
8
9*********************************************************************/
10
11#include "JournalEvents.h"
12#include "Journal.h"
13#include "JournalOutput.h"
14#include "JournalRegistry.h"
15#include "JournalWindowPaths.h"
16
17#include <map>
18#include <mutex>
19#include <optional>
20#include <unordered_map>
21
22#include <wx/eventfilter.h>
23#include <wx/spinctrl.h>
24#include <wx/textctrl.h>
25#include <wx/tglbtn.h>
26#include <wx/valgen.h>
27#include <wx/window.h>
28
29#include "AudacityException.h"
30#include "BasicUI.h"
31#include "Identifier.h"
32#include "wxArrayStringEx.h"
33
34namespace Journal {
35
36namespace Events {
37
38namespace {
39
40struct Type;
41using Types = std::vector< Type >;
42
43// The list of event types to intercept and record. Its construction must be
44// delayed until wxWidgets has initialized and chosen the integer values of
45// run-time event types.
46static const Types &TypeCatalog();
47
48// Events need to be recorded in the journal, but the numbers associated with
49// event types by wxWidgets are chosen dynamically and may not be the same
50// across runs or platforms. We need an invariant name for each event type
51// of interest, which also makes the journal more legible than if we wrote mere
52// numbers.
54
55// Lookup into the event type catalog during recording
56using ByTypeMap = std::map< wxEventType, const Type& >;
57static const ByTypeMap &ByType();
58
59// Lookup into the event type catalog during playback
60using ByCodeMap = std::unordered_map< Code, const Type & >;
61static const ByCodeMap &ByCode();
62
63// This sub-object populates the journal system's dictionary for playback
65 static bool DispatchEvent( wxArrayStringEx fields );
66 explicit RegisteredEventType( Code code )
67 : RegisteredCommand{ code.GET(), DispatchEvent }
68 {}
69};
70
72static bool sWatching{ true };
73
79
80 // Function that returns a list of parameters that, with the event type,
81 // are sufficient to record an event to the journal and recreate it on
82 // playback; or a nullopt to skip the event
83 using Serializer =
84 std::function< std::optional<wxArrayStringEx>( const wxEvent& ) >;
85
86 // Function that recreates an event at playback; or a null for failure
87 using Deserializer = std::function< std::unique_ptr<wxEvent>(
88 wxEventType, const wxArrayStringEx& ) >;
89
90 // Helper to keep casts out of the supplied function's definition
91 // Tag is the subtype of wxEvent expected by fn
92 template< typename Tag, typename SerialFn >
94 {
95 // Return an adaptor taking wxEvent
96 return [fn = std::move( fn )]( const wxEvent &e ){
97 return fn(dynamic_cast<const Tag&>(e));
98 };
99 }
100
101 // Helper to check consistency
102 // Tag is a subtype of wxEvent (such as wxCommandEvent), and fn defines
103 // a deserializer dependent on a run-time wxEvent type (such as one of
104 // the many event types implemented by wxCommandEvent)
105 template< typename Tag, typename Fn >
106 Deserializer checkDeserializer( wxEventTypeTag<Tag> type, Fn fn )
107 {
108 // Check consistency of the deduced template parameters
109 // The deserializer should produce unique pointer to Tag
110 using DeserializerResult =
111 decltype( *fn( wxEVT_NULL, wxArrayStringEx{} ) );
112 static_assert( std::is_same_v< Tag&, DeserializerResult >,
113 "deserializer produces the wrong type" );
114
115 return std::move( fn );
116 }
117
118 // Type-erasing constructor applies compile-time type consistency checks to
119 // the functions, which can be supplied as lambdas using a subtype of wxEvent
120 template<
121 typename Tag, // such as wxCommandEvent
122 typename SerialFn,
123 typename DeserialFn
124 >
126 wxEventTypeTag<Tag> type,
129 const Code &code, SerialFn serialFn, DeserialFn deserialFn )
130 : RegisteredEventType{ code }
131 , type{ type }
132 , code{ code }
133 , serializer{ makeSerializer<Tag>( std::move( serialFn ) ) }
134 , deserializer{ checkDeserializer( type, std::move( deserialFn ) ) }
135 {
136 }
137
138 // Data members
139 wxEventType type;
143};
144
145bool RegisteredEventType::DispatchEvent( wxArrayStringEx fields )
146{
147 bool handled = false;
148 if ( !fields.empty() ) {
149 auto &catalog = ByCode();
150 auto first = fields.begin();
151 if (auto iter = catalog.find( *first ); iter != catalog.end()) {
152 auto &type = iter->second;
153 fields.erase( first );
154 auto pEvent = type.deserializer( type.type, fields );
155 if ( pEvent ) {
156 // So far only dispatching command events. Maybe other
157 // methods of dispatch will be appropriate for other types.
158 if ( auto pHandler = dynamic_cast<wxEvtHandler*>(
159 pEvent->GetEventObject() )
160 ) {
161 if (auto pWindow = dynamic_cast<wxWindow *>(pHandler))
162 // Allow for the pushing and popping of event handlers
163 // on windows
164 pHandler = pWindow->GetEventHandler();
165 pHandler->SafelyProcessEvent( *pEvent );
166 handled = true;
167 }
168 }
169 }
170 }
171 return handled;
172}
173
174wxString WindowEventName( const wxEvent &event )
175{
176 wxString windowName;
177 if ( auto pWindow = dynamic_cast< wxWindow * >( event.GetEventObject() ) )
178 windowName = WindowPaths::FindPath( *pWindow ).GET();
179 return windowName;
180}
181
182std::optional<wxArrayStringEx> WindowEventSerialization( const wxEvent &event )
183{
184 std::optional< wxArrayStringEx > result;
185 if ( auto windowName = WindowEventName( event ); !windowName.empty() )
186 result.emplace( wxArrayStringEx{ windowName } );
187 else
189 return result;
190}
191
192template<typename Event = wxCommandEvent, typename EventType >
193static Type NullaryCommandType( EventType type, const wxString &code ){
194 const auto deserialize =
195 []( wxEventType evtType, const wxArrayStringEx &components) {
196 std::unique_ptr< wxCommandEvent > result;
197 if ( components.size() == 1 ) {
198 if ( auto pWindow = WindowPaths::FindByPath( components[0] ) ) {
199 result = std::make_unique< wxCommandEvent >(
200 evtType, pWindow->GetId() );
201 result->SetEventObject( pWindow );
202 }
203 }
204 return result;
205 };
206
207 return Type{ type, code, WindowEventSerialization, deserialize };
208}
209
210template<typename Event = wxCommandEvent, typename EventType >
211static Type BooleanCommandType( EventType type, const wxString &code ){
212 // Writes a window identifier to the journal and a bool
213 const auto serialize =
214 []( const Event &event ) {
215 auto result = WindowEventSerialization( event );
216 if ( result )
217 result->push_back(
218 wxString::Format( L"%d", event.GetInt() ? 1 : 0 ) );
219 return result;
220 };
221
222 const auto deserialize =
223 []( wxEventType type, const wxArrayStringEx &components ) {
224 std::unique_ptr< Event > result;
225 int value;
226 if ( components.size() == 2 ) {
227 if ( auto pWindow = WindowPaths::FindByPath( components[0] ) ) {
228 if ( long longValue; components[1].ToLong( &longValue ) ) {
229 bool value = (longValue != 0);
230 result = std::make_unique< Event >(
231 type, pWindow->GetId() );
232 result->SetEventObject( pWindow );
233 result->SetInt( value );
234 // Also change the state of the control before the event is
235 // processed. This class handles the most common control
236 // types.
237 wxGenericValidator validator( &value );
238 validator.SetWindow( pWindow );
239 validator.TransferToWindow();
240 }
241 }
242 }
243 return result;
244 };
245
246 return Type{ type, code, serialize, deserialize };
247}
248
249template<typename Event = wxCommandEvent, typename EventType >
250static Type NumericalCommandType( EventType type, const wxString &code ){
251 // Writes a window identifier to the journal and an int
252 const auto serialize =
253 []( const Event &event ) {
254 auto result = WindowEventSerialization( event );
255 if ( result )
256 result->push_back( wxString::Format( L"%d", event.GetInt() ) );
257 return result;
258 };
259
260 const auto deserialize =
261 []( wxEventType type, const wxArrayStringEx &components ) {
262 std::unique_ptr< Event > result;
263 int value;
264 if ( components.size() == 2 ) {
265 if ( auto pWindow = WindowPaths::FindByPath( components[0] ) ) {
266 if ( long longValue;
267 components[1].ToLong( &longValue ) &&
268 longValue == (value = static_cast<int>(longValue) )
269 ) {
270 result = std::make_unique< Event >(
271 type, pWindow->GetId() );
272 result->SetEventObject( pWindow );
273 result->SetInt( value );
274 // Also change the state of the control before the event is
275 // processed. This class handles the most common control
276 // types.
277 wxGenericValidator validator( &value );
278 validator.SetWindow( pWindow );
279 validator.TransferToWindow();
280 }
281 }
282 }
283 return result;
284 };
285
286 return Type{ type, code, serialize, deserialize };
287}
288
289template<typename Event = wxCommandEvent, typename EventType >
290static Type TextualCommandType( EventType type, const wxString &code ){
291 // Writes a window identifier to the journal and a string
292 const auto serialize =
293 []( const Event &event ) {
294 auto result = WindowEventSerialization( event );
295 if ( result )
296 result->push_back( event.GetString() );
297 return result;
298 };
299
300 const auto deserialize =
301 []( wxEventType type, const wxArrayStringEx &components ) {
302 std::unique_ptr< Event > result;
303 if ( components.size() == 2 ) {
304 if ( auto pWindow = WindowPaths::FindByPath( components[0] ) ) {
305 auto value = components[1];
306 result = std::make_unique< Event >(
307 type, pWindow->GetId() );
308 result->SetEventObject( pWindow );
309 result->SetString( value );
310 // Also change the state of the control before the event is
311 // processed.
312 if ( auto pCtrl = dynamic_cast<wxTextEntry*>( pWindow ) ) {
313 // Generic validator calls the SetValue() member function,
314 // which generates an event for this type of control, but we
315 // don't want that. So use ChangeValue() instead
316 pCtrl->ChangeValue( value );
317 }
318 else {
319 // This class handles the most common control types.
320 wxGenericValidator validator( &value );
321 validator.SetWindow( pWindow );
322 validator.TransferToWindow();
323 }
324 }
325 }
326 return result;
327 };
328
329 return Type{ type, code, serialize, deserialize };
330}
331
333{
334 static Types result {
335 NullaryCommandType( wxEVT_BUTTON, "Press" ),
336 NullaryCommandType( wxEVT_COMBOBOX_DROPDOWN, "DropDown" ),
337 NullaryCommandType( wxEVT_COMBOBOX_CLOSEUP, "CloseUp" ),
338
339 BooleanCommandType( wxEVT_CHECKBOX, "Check" ),
340 BooleanCommandType( wxEVT_RADIOBUTTON, "Radio" ),
341 BooleanCommandType( wxEVT_RADIOBOX, "RadioBox" ),
342 BooleanCommandType( wxEVT_TOGGLEBUTTON, "Toggle" ),
343
344 NumericalCommandType( wxEVT_CHOICE, "Choose" ),
345 NumericalCommandType( wxEVT_SLIDER, "Slide" ),
346 NumericalCommandType<wxSpinEvent>( wxEVT_SPINCTRL, "Spin" ),
347 NumericalCommandType<wxSpinEvent>( wxEVT_SPIN_DOWN, "SpinUp" ),
348 NumericalCommandType<wxSpinEvent>( wxEVT_SPIN_UP, "SpinDown" ),
349
350 TextualCommandType( wxEVT_COMBOBOX, "Combo" ),
351 TextualCommandType( wxEVT_TEXT, "Text" ),
352 TextualCommandType( wxEVT_TEXT_ENTER, "FullText" ),
353 };
354 return result;
355}
356
357static const ByTypeMap &ByType()
358{
359 static std::once_flag flag;
360 static ByTypeMap result;
361 std::call_once( flag, []{
362 for ( const auto &type : TypeCatalog() )
363 result.emplace( type.type, type );
364 } );
365 return result;
366}
367
368static const ByCodeMap &ByCode()
369{
370 static std::once_flag flag;
371 static ByCodeMap result;
372 std::call_once( flag, []{
373 for ( const auto &type : TypeCatalog() )
374 result.emplace( type.code, type );
375 } );
376 return result;
377}
378
380struct Watcher : wxEventFilter
381{
383 {
384 wxEvtHandler::AddFilter( this );
385 }
386
388 {
389 wxEvtHandler::RemoveFilter( this );
390 }
391
392 int FilterEvent( wxEvent &event ) override
393 {
394 if (!IsWatching())
395 // Previously encountered error stopped recording of any more events
396 return Event_Skip;
397
398 static const auto &catalog = Events::ByType();
399 const auto type = event.GetEventType();
400 if (const auto iter = catalog.find(type); iter != catalog.end()) {
401 // Try to write a representation to the journal
402 const auto &info = iter->second;
403 auto pStrings = info.serializer(event);
404 if (!pStrings)
405 return Event_Skip;
406 else {
407 pStrings->insert(pStrings->begin(), info.code.GET());
408 Journal::Output(*pStrings);
409 }
410 }
411 else
412 // Just ignore this un-catalogued event type
413 ;
414
415 return Event_Skip;
416 }
417};
418
419}
420
422{
423 return sWatching;
424}
425
427{
428 // After one event of one of the interesting types fails to record,
429 // don't try again
430 sWatching = false;
432 BasicUI::ShowMessageBox(XO("Journal recording failed"));
433 } );
434}
435
436namespace {
437
439{
440 (void) TypeCatalog();
441}
442
443void Watch()
444{
445 static Watcher instance;
446}
447
448// Add a callback for startup of journalling
450 // Register the event handler for recording
451 // and dictionary items for replaying
452 if ( !GetError() && IsRecording() )
453 // one time installation
454 Watch();
455
456 if ( !GetError() && IsReplaying() )
457 // Be sure event types are registered for dispatch
458 Initialize();
459
460 return true;
461} };
462
463}
464
465}
466
467}
Declare abstract class AudacityException, some often-used subclasses, and GuardedCall.
Toolkit-neutral facade for basic user interface services.
XO("Cut/Copy/Paste")
The output stream of the journal system.
Journal system's error status, command dictionary, initializers.
static const auto fn
static std::once_flag flag
An explicitly nonlocalized string, not meant for the user to see.
Definition: Identifier.h:22
const wxString & GET() const
Explicit conversion to wxString, meant to be ugly-looking and demanding of a comment why it's correct...
Definition: Identifier.h:66
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:213
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
Definition: BasicUI.h:279
static bool sWatching
Whether the event filter is still watching events.
static Type BooleanCommandType(EventType type, const wxString &code)
static Type NumericalCommandType(EventType type, const wxString &code)
static Type NullaryCommandType(EventType type, const wxString &code)
std::map< wxEventType, const Type & > ByTypeMap
std::unordered_map< Code, const Type & > ByCodeMap
std::optional< wxArrayStringEx > WindowEventSerialization(const wxEvent &event)
static Type TextualCommandType(EventType type, const wxString &code)
void FailedEventSerialization()
Path FindPath(const wxWindow &window)
wxWindow * FindByPath(const Path &path)
Facilities for recording and playback of sequences of user interaction.
bool GetError()
void Output(const wxString &string)
bool IsReplaying()
Definition: Journal.cpp:216
bool IsRecording()
STL namespace.
Deserializer checkDeserializer(wxEventTypeTag< Tag > type, Fn fn)
std::function< std::optional< wxArrayStringEx >(const wxEvent &) > Serializer
std::function< std::unique_ptr< wxEvent >(wxEventType, const wxArrayStringEx &) > Deserializer
Code code
Persistent cross-platform name corresponding to the int.
Type(wxEventTypeTag< Tag > type, const Code &code, SerialFn serialFn, DeserialFn deserialFn)
Singleton object listens to global wxEvent stream.