Audacity  3.0.3
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 
34 namespace Journal {
35 
36 namespace Events {
37 
38 namespace {
39 
40 struct Type;
41 using 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.
46 static 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.
53 using Code = Identifier;
54 
55 // Lookup into the event type catalog during recording
56 using ByTypeMap = std::map< wxEventType, const Type& >;
57 static const ByTypeMap &ByType();
58 
59 // Lookup into the event type catalog during playback
60 using ByCodeMap = std::unordered_map< Code, const Type & >;
61 static 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 
72 static 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 
145 bool 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 
174 wxString 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 
182 std::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 
192 template<typename Event = wxCommandEvent, typename EventType >
193 static 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 
210 template<typename Event = wxCommandEvent, typename EventType >
211 static 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 
249 template<typename Event = wxCommandEvent, typename EventType >
250 static 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 
289 template<typename Event = wxCommandEvent, typename EventType >
290 static 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 
357 static 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 
368 static 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 
380 struct 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;
431  BasicUI::CallAfter( []{
432  BasicUI::ShowMessageBox(XO("Journal recording failed"));
433  } );
434 }
435 
436 namespace {
437 
439 {
440  (void) TypeCatalog();
441 }
442 
443 void 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 }
Journal::Events::anonymous_namespace{JournalEvents.cpp}::TextualCommandType
static Type TextualCommandType(EventType type, const wxString &code)
Definition: JournalEvents.cpp:290
Journal
Facilities for recording and playback of sequences of user interaction.
Journal::WindowPaths::FindByPath
wxWindow * FindByPath(const Path &path)
Definition: JournalWindowPaths.cpp:116
Journal::RegisteredCommand
Definition: JournalRegistry.h:38
Journal::Events::IsWatching
bool IsWatching()
Definition: JournalEvents.cpp:421
Journal::WindowPaths::FindPath
Path FindPath(const wxWindow &window)
Definition: JournalWindowPaths.cpp:109
fn
static const auto fn
Definition: WaveformView.cpp:1108
Journal::Events::anonymous_namespace{JournalEvents.cpp}::ByCodeMap
std::unordered_map< Code, const Type & > ByCodeMap
Definition: JournalEvents.cpp:60
flag
static std::once_flag flag
Definition: WaveformView.cpp:1119
BasicUI::ShowMessageBox
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:248
Journal::Events::anonymous_namespace{JournalEvents.cpp}::Type
Definition: JournalEvents.cpp:78
Journal::Events::anonymous_namespace{JournalEvents.cpp}::TypeCatalog
static const Types & TypeCatalog()
Definition: JournalEvents.cpp:332
XO
#define XO(s)
Definition: Internat.h:31
Journal::Events::FailedEventSerialization
void FailedEventSerialization()
Definition: JournalEvents.cpp:426
Journal::Events::anonymous_namespace{JournalEvents.cpp}::RegisteredEventType::RegisteredEventType
RegisteredEventType(Code code)
Definition: JournalEvents.cpp:66
Journal::Events::anonymous_namespace{JournalEvents.cpp}::Type::code
Code code
Persistent cross-platform name corresponding to the int.
Definition: JournalEvents.cpp:140
Journal::Events::anonymous_namespace{JournalEvents.cpp}::Type::Serializer
std::function< std::optional< wxArrayStringEx >(const wxEvent &) > Serializer
Definition: JournalEvents.cpp:84
BasicUI::CallAfter
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:38
Journal::Events::anonymous_namespace{JournalEvents.cpp}::Type::Deserializer
std::function< std::unique_ptr< wxEvent >(wxEventType, const wxArrayStringEx &) > Deserializer
Definition: JournalEvents.cpp:88
Journal::Events::anonymous_namespace{JournalEvents.cpp}::RegisteredEventType
Definition: JournalEvents.cpp:64
wxArrayStringEx
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
Definition: wxArrayStringEx.h:18
Journal::Events::anonymous_namespace{JournalEvents.cpp}::ByTypeMap
std::map< wxEventType, const Type & > ByTypeMap
Definition: JournalEvents.cpp:56
Identifier
An explicitly nonlocalized string, not meant for the user to see.
Definition: Identifier.h:22
JournalEvents.h
Journal.h
Journal::Events::anonymous_namespace{JournalEvents.cpp}::Type::makeSerializer
Serializer makeSerializer(SerialFn fn)
Definition: JournalEvents.cpp:93
Journal::Output
void Output(const wxString &string)
Definition: JournalOutput.cpp:45
Journal::GetError
bool GetError()
Definition: JournalRegistry.cpp:30
Journal::IsRecording
bool IsRecording()
Definition: JournalOutput.cpp:28
Journal::Events::anonymous_namespace{JournalEvents.cpp}::Watcher::FilterEvent
int FilterEvent(wxEvent &event) override
Definition: JournalEvents.cpp:392
Identifier.h
Journal::Events::anonymous_namespace{JournalEvents.cpp}::ByCode
static const ByCodeMap & ByCode()
Definition: JournalEvents.cpp:368
Journal::Events::anonymous_namespace{JournalEvents.cpp}::Watcher
Singleton object listens to global wxEvent stream.
Definition: JournalEvents.cpp:381
AudacityException.h
Declare abstract class AudacityException, some often-used subclasses, and GuardedCall.
Journal::Events::anonymous_namespace{JournalEvents.cpp}::Type::type
wxEventType type
Just an int!
Definition: JournalEvents.cpp:139
Journal::Events::anonymous_namespace{JournalEvents.cpp}::Initialize
void Initialize()
Definition: JournalEvents.cpp:438
Journal::Events::anonymous_namespace{JournalEvents.cpp}::NumericalCommandType
static Type NumericalCommandType(EventType type, const wxString &code)
Definition: JournalEvents.cpp:250
Journal::IsReplaying
bool IsReplaying()
Definition: Journal.cpp:131
Identifier::GET
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
BasicUI.h
Toolkit-neutral facade for basic user interface services.
JournalRegistry.h
Journal system's error status, command dictionary, initializers.
Journal::Events::anonymous_namespace{JournalEvents.cpp}::WindowEventName
wxString WindowEventName(const wxEvent &event)
Definition: JournalEvents.cpp:174
wxArrayStringEx.h
Journal::Events::anonymous_namespace{JournalEvents.cpp}::Type::Type
Type(wxEventTypeTag< Tag > type, const Code &code, SerialFn serialFn, DeserialFn deserialFn)
Definition: JournalEvents.cpp:125
Journal::Events::anonymous_namespace{JournalEvents.cpp}::sWatching
static bool sWatching
Whether the event filter is still watching events.
Definition: JournalEvents.cpp:72
Journal::RegisteredInitializer
Definition: JournalRegistry.h:55
JournalWindowPaths.h
JournalOutput.h
The output stream of the journal system.
Journal::Events::anonymous_namespace{JournalEvents.cpp}::BooleanCommandType
static Type BooleanCommandType(EventType type, const wxString &code)
Definition: JournalEvents.cpp:211
Journal::Events::anonymous_namespace{JournalEvents.cpp}::Type::deserializer
Deserializer deserializer
Definition: JournalEvents.cpp:142
Journal::Events::anonymous_namespace{JournalEvents.cpp}::Watcher::Watcher
Watcher()
Definition: JournalEvents.cpp:382
Journal::Events::anonymous_namespace{JournalEvents.cpp}::initializer
RegisteredInitializer initializer
Definition: JournalEvents.cpp:449
Journal::Events::anonymous_namespace{JournalEvents.cpp}::Type::checkDeserializer
Deserializer checkDeserializer(wxEventTypeTag< Tag > type, Fn fn)
Definition: JournalEvents.cpp:106
Journal::Events::anonymous_namespace{JournalEvents.cpp}::Types
std::vector< Type > Types
Definition: JournalEvents.cpp:41
Journal::Events::anonymous_namespace{JournalEvents.cpp}::WindowEventSerialization
std::optional< wxArrayStringEx > WindowEventSerialization(const wxEvent &event)
Definition: JournalEvents.cpp:182
Journal::Events::anonymous_namespace{JournalEvents.cpp}::Type::serializer
Serializer serializer
Definition: JournalEvents.cpp:141
Journal::Events::anonymous_namespace{JournalEvents.cpp}::NullaryCommandType
static Type NullaryCommandType(EventType type, const wxString &code)
Definition: JournalEvents.cpp:193
Journal::Events::anonymous_namespace{JournalEvents.cpp}::Watcher::~Watcher
~Watcher()
Definition: JournalEvents.cpp:387
Journal::Events::anonymous_namespace{JournalEvents.cpp}::ByType
static const ByTypeMap & ByType()
Definition: JournalEvents.cpp:357
Journal::Events::anonymous_namespace{JournalEvents.cpp}::Watch
void Watch()
Definition: JournalEvents.cpp:443