KiCad PCB EDA Suite
dialog_shim.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
5  * Copyright (C) 2012-2016 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23  */
24 
25 #include <dialog_shim.h>
26 #include <kiway_player.h>
27 #include <wx/evtloop.h>
28 #include <pgm_base.h>
29 #include <class_eda_rect.h>
30 
31 
34 {
35  wxWindow* m_win;
36 
37 public:
38 
39  WDO_ENABLE_DISABLE( wxWindow* aWindow ) :
40  m_win( aWindow )
41  {
42  if( m_win )
43  m_win->Disable();
44  }
45 
47  {
48  if( m_win )
49  {
50  m_win->Enable();
51  m_win->SetFocus(); // let's focus back on the parent window
52  }
53  }
54 };
55 
56 
57 DIALOG_SHIM::DIALOG_SHIM( wxWindow* aParent, wxWindowID id, const wxString& title,
58  const wxPoint& pos, const wxSize& size, long style, const wxString& name ) :
59  wxDialog( aParent, id, title, pos, size, style, name ),
60  KIWAY_HOLDER( 0 ),
61  m_qmodal_loop( 0 ),
62  m_qmodal_showing( false ),
63  m_qmodal_parent_disabler( 0 )
64 {
65  // pray that aParent is either a KIWAY_PLAYER or DIALOG_SHIM derivation.
66  KIWAY_HOLDER* h = dynamic_cast<KIWAY_HOLDER*>( aParent );
67 
68  // wxASSERT_MSG( h, wxT( "DIALOG_SHIM's parent is NULL or not derived from KIWAY_PLAYER nor DIALOG_SHIM" ) );
69 
70  if( h )
71  SetKiway( this, &h->Kiway() );
72 
73  Bind( wxEVT_CLOSE_WINDOW, &DIALOG_SHIM::OnCloseWindow, this );
74  Bind( wxEVT_BUTTON, &DIALOG_SHIM::OnButton, this );
75 
76 #ifdef __WINDOWS__
77  // On Windows, the app top windows can be brought to the foreground
78  // (at least temporary) in certain circumstances,
79  // for instance when calling an external tool in Eeschema boom generation.
80  // So set the parent KIWAY_PLAYER kicad frame (if exists) to top window
81  // to avoid this annoying behavior
82  KIWAY_PLAYER* parent_kiwayplayer = dynamic_cast<KIWAY_PLAYER*>( aParent );
83 
84  if( parent_kiwayplayer )
85  Pgm().App().SetTopWindow( parent_kiwayplayer );
86 #endif
87 
88 #if DLGSHIM_USE_SETFOCUS
89  Connect( wxEVT_INIT_DIALOG, wxInitDialogEventHandler( DIALOG_SHIM::onInit ) );
90 #endif
91 }
92 
93 
95 {
96  // if the dialog is quasi-modal, this will end its event loop
97  if( IsQuasiModal() )
98  EndQuasiModal( wxID_CANCEL );
99 
100  delete m_qmodal_parent_disabler; // usually NULL by now
101 }
102 
104 {
105  // must be called from the constructor of derived classes,
106  // when all widgets are initialized, and therefore their size fixed
107 
108  // SetSizeHints fixes the minimal size of sizers in the dialog
109  // (SetSizeHints calls Fit(), so no need to call it)
110  GetSizer()->SetSizeHints( this );
111 
112  // the default position, when calling the first time the dlg
113  Center();
114 }
115 
117 {
118 #ifdef __WXMAC__
119  // A ugly hack to fix an issue on OSX: ctrl+c closes the dialog instead of
120  // copying a text if a button with wxID_CANCEL is used in a wxStdDialogButtonSizer
121  // created by wxFormBuilder: the label is &Cancel, and this accelerator key has priority
122  // to copy text standard accelerator, and the dlg is closed when trying to copy text
123  wxButton* button = dynamic_cast< wxButton* > ( wxWindow::FindWindowById( wxID_CANCEL, this ) );
124 
125  if( button )
126  button->SetLabel( _( "Cancel" ) );
127 #endif
128 }
129 
130 
131 // our hashtable is an implementation secret, don't need or want it in a header file
132 #include <hashtables.h>
133 #include <base_struct.h> // EDA_RECT
134 #include <typeinfo>
135 
137 
138 bool DIALOG_SHIM::Show( bool show )
139 {
140  bool ret;
141  const char* hash_key;
142 
143  if( m_hash_key.size() )
144  {
145  // a special case like EDA_LIST_DIALOG, which has multiple uses.
146  hash_key = m_hash_key.c_str();
147  }
148  else
149  {
150  hash_key = typeid(*this).name();
151  }
152 
153  // Show or hide the window. If hiding, save current position and size.
154  // If showing, use previous position and size.
155  if( show )
156  {
157  wxDialog::Raise(); // Needed on OS X and some other window managers (i.e. Unity)
158  ret = wxDialog::Show( show );
159 
160  // classname is key, returns a zeroed out default EDA_RECT if none existed before.
161  EDA_RECT r = class_map[ hash_key ];
162 
163  if( r.GetSize().x != 0 && r.GetSize().y != 0 )
164  SetSize( r.GetPosition().x, r.GetPosition().y, r.GetSize().x, r.GetSize().y, 0 );
165  }
166  else
167  {
168  // Save the dialog's position & size before hiding, using classname as key
169  EDA_RECT r( wxDialog::GetPosition(), wxDialog::GetSize() );
170  class_map[ hash_key ] = r;
171 
172  ret = wxDialog::Show( show );
173  }
174 
176 
177  return ret;
178 }
179 
180 
181 bool DIALOG_SHIM::Enable( bool enable )
182 {
183  // so we can do logging of this state change:
184 
185 #if defined(DEBUG)
186  const char* type_id = typeid( *this ).name();
187  printf( "wxDialog %s: %s\n", type_id, enable ? "enabled" : "disabled" );
188 #endif
189 
190  return wxDialog::Enable( enable );
191 }
192 
193 
194 #if DLGSHIM_USE_SETFOCUS
195 
196 static bool findWindowRecursively( const wxWindowList& children, const wxWindow* wanted )
197 {
198  for( wxWindowList::const_iterator it = children.begin(); it != children.end(); ++it )
199  {
200  const wxWindow* child = *it;
201 
202  if( wanted == child )
203  return true;
204  else
205  {
206  if( findWindowRecursively( child->GetChildren(), wanted ) )
207  return true;
208  }
209  }
210 
211  return false;
212 }
213 
214 
215 static bool findWindowRecursively( const wxWindow* topmost, const wxWindow* wanted )
216 {
217  // wanted may be NULL and that is ok.
218 
219  if( wanted == topmost )
220  return true;
221 
222  return findWindowRecursively( topmost->GetChildren(), wanted );
223 }
224 
225 
227 void DIALOG_SHIM::onInit( wxInitDialogEvent& aEvent )
228 {
229  wxWindow* focusWnd = wxWindow::FindFocus();
230 
231  // If focusWnd is not already this window or a child of it, then SetFocus().
232  // Otherwise the derived class's constructor SetFocus() already to a specific
233  // child control.
234 
235  if( !findWindowRecursively( this, focusWnd ) )
236  {
237  // Linux wxGTK needs this to allow the ESCAPE key to close a wxDialog window.
238  SetFocus();
239  }
240 
241  aEvent.Skip(); // derived class's handler should be called too
242 }
243 #endif
244 
245 
246 /*
247  Quasi-Modal Mode Explained:
248 
249  The gtk calls in wxDialog::ShowModal() cause event routing problems if that
250  modal dialog then tries to use KIWAY_PLAYER::ShowModal(). The latter shows up
251  and mostly works but does not respond to the window decoration close button.
252  There is no way to get around this without reversing the gtk calls temporarily.
253 
254  Quasi-Modal mode is our own almost modal mode which disables only the parent
255  of the DIALOG_SHIM, leaving other frames operable and while staying captured in the
256  nested event loop. This avoids the gtk calls and leaves event routing pure
257  and sufficient to operate the KIWAY_PLAYER::ShowModal() properly. When using
258  ShowQuasiModal() you have to use EndQuasiModal() in your dialogs and not
259  EndModal(). There is also IsQuasiModal() but its value can only be true
260  when the nested event loop is active. Do not mix the modal and quasi-modal
261  functions. Use one set or the other.
262 
263  You might find this behavior preferable over a pure modal mode, and it was said
264  that only the Mac has this natively, but now other platforms have something
265  similar. You CAN use it anywhere for any dialog. But you MUST use it when
266  you want to use KIWAY_PLAYER::ShowModal() from a dialog event.
267 */
268 
269 
270 
271 /*
275 class ELOOP_ACTIVATOR
276 {
277  friend class EVENT_LOOP;
278 
279 public:
280  ELOOP_ACTIVATOR( WX_EVENT_LOOP* evtLoop )
281  {
282  m_evtLoopOld = wxEventLoopBase::GetActive();
283  // wxEventLoopBase::SetActive( evtLoop );
284  }
285 
286  ~ELOOP_ACTIVATOR()
287  {
288  // restore the previously active event loop
289  wxEventLoopBase::SetActive( m_evtLoopOld );
290  }
291 
292 private:
293  WX_EVENT_LOOP* m_evtLoopOld;
294 };
295 */
296 
297 
298 class EVENT_LOOP : public WX_EVENT_LOOP
299 {
300 public:
301 
303  {
304  ;
305  }
306 
308  {
309  }
310 
311 #if 0 // does not work any better than inherited wxGuiEventLoop functions:
312 
313  // sets the "should exit" flag and wakes up the loop so that it terminates
314  // soon
315  void ScheduleExit( int rc = 0 )
316  {
317  wxCHECK_RET( IsInsideRun(), wxT("can't call ScheduleExit() if not running") );
318 
319  m_exitcode = rc;
320  m_shouldExit = true;
321 
322  OnExit();
323 
324  // all we have to do to exit from the loop is to (maybe) wake it up so that
325  // it can notice that Exit() had been called
326  //
327  // in particular, do *not* use here calls such as PostQuitMessage() (under
328  // MSW) which terminate the current event loop here because we're not sure
329  // that it is going to be processed by the correct event loop: it would be
330  // possible that another one is started and terminated by mistake if we do
331  // this
332  WakeUp();
333  }
334 
335  int Run()
336  {
337  // event loops are not recursive, you need to create another loop!
338  //wxCHECK_MSG( !IsInsideRun(), -1, wxT("can't reenter a message loop") );
339 
340  // ProcessIdle() and ProcessEvents() below may throw so the code here should
341  // be exception-safe, hence we must use local objects for all actions we
342  // should undo
343  wxEventLoopActivator activate(this);
344 
345  // We might be called again, after a previous call to ScheduleExit(), so
346  // reset this flag.
347  m_shouldExit = false;
348 
349  // Set this variable to true for the duration of this method.
350  setInsideRun( true );
351 
352  struct SET_FALSE
353  {
354  EVENT_LOOP* m_loop;
355  SET_FALSE( EVENT_LOOP* aLoop ) : m_loop( aLoop ) {}
356  ~SET_FALSE() { m_loop->setInsideRun( false ); }
357  } t( this );
358 
359  // Finally really run the loop.
360  return DoRun();
361  }
362 
363  bool ProcessEvents()
364  {
365  // process pending wx events first as they correspond to low-level events
366  // which happened before, i.e. typically pending events were queued by a
367  // previous call to Dispatch() and if we didn't process them now the next
368  // call to it might enqueue them again (as happens with e.g. socket events
369  // which would be generated as long as there is input available on socket
370  // and this input is only removed from it when pending event handlers are
371  // executed)
372  if( wxTheApp )
373  {
374  wxTheApp->ProcessPendingEvents();
375 
376  // One of the pending event handlers could have decided to exit the
377  // loop so check for the flag before trying to dispatch more events
378  // (which could block indefinitely if no more are coming).
379  if( m_shouldExit )
380  return false;
381  }
382 
383  return Dispatch();
384  }
385 
386 
387  int DoRun()
388  {
389  // we must ensure that OnExit() is called even if an exception is thrown
390  // from inside ProcessEvents() but we must call it from Exit() in normal
391  // situations because it is supposed to be called synchronously,
392  // wxModalEventLoop depends on this (so we can't just use ON_BLOCK_EXIT or
393  // something similar here)
394  #if wxUSE_EXCEPTIONS
395  for( ; ; )
396  {
397  try
398  {
399  #endif // wxUSE_EXCEPTIONS
400 
401  // this is the event loop itself
402  for( ; ; )
403  {
404  // generate and process idle events for as long as we don't
405  // have anything else to do
406  while ( !m_shouldExit && !Pending() && ProcessIdle() )
407  ;
408 
409  if ( m_shouldExit )
410  break;
411 
412  // a message came or no more idle processing to do, dispatch
413  // all the pending events and call Dispatch() to wait for the
414  // next message
415  if ( !ProcessEvents() )
416  {
417  // we got WM_QUIT
418  break;
419  }
420  }
421 
422  // Process the remaining queued messages, both at the level of the
423  // underlying toolkit level (Pending/Dispatch()) and wx level
424  // (Has/ProcessPendingEvents()).
425  //
426  // We do run the risk of never exiting this loop if pending event
427  // handlers endlessly generate new events but they shouldn't do
428  // this in a well-behaved program and we shouldn't just discard the
429  // events we already have, they might be important.
430  for( ; ; )
431  {
432  bool hasMoreEvents = false;
433  if ( wxTheApp && wxTheApp->HasPendingEvents() )
434  {
435  wxTheApp->ProcessPendingEvents();
436  hasMoreEvents = true;
437  }
438 
439  if ( Pending() )
440  {
441  Dispatch();
442  hasMoreEvents = true;
443  }
444 
445  if ( !hasMoreEvents )
446  break;
447  }
448 
449  #if wxUSE_EXCEPTIONS
450  // exit the outer loop as well
451  break;
452  }
453  catch ( ... )
454  {
455  try
456  {
457  if ( !wxTheApp || !wxTheApp->OnExceptionInMainLoop() )
458  {
459  OnExit();
460  break;
461  }
462  //else: continue running the event loop
463  }
464  catch ( ... )
465  {
466  // OnException() throwed, possibly rethrowing the same
467  // exception again: very good, but we still need OnExit() to
468  // be called
469  OnExit();
470  throw;
471  }
472  }
473  }
474  #endif // wxUSE_EXCEPTIONS
475 
476  return m_exitcode;
477  }
478 
479 protected:
480  int m_exitcode;
481 
482  /* this only works if you add
483  friend class EVENT_LOOP
484  to EventLoopBase
485  */
486  void setInsideRun( bool aValue )
487  {
488  m_isInsideRun = aValue;
489  }
490 #endif
491 };
492 
493 
495 {
496  // This is an exception safe way to zero a pointer before returning.
497  // Yes, even though DismissModal() clears this first normally, this is
498  // here in case there's an exception before the dialog is dismissed.
499  struct NULLER
500  {
501  void*& m_what;
502  NULLER( void*& aPtr ) : m_what( aPtr ) {}
503  ~NULLER() { m_what = 0; } // indeed, set it to NULL on destruction
504  } clear_this( (void*&) m_qmodal_loop );
505 
506  // release the mouse if it's currently captured as the window having it
507  // will be disabled when this dialog is shown -- but will still keep the
508  // capture making it impossible to do anything in the modal dialog itself
509  wxWindow* win = wxWindow::GetCapture();
510  if( win )
511  win->ReleaseMouse();
512 
513  // Get the optimal parent
514  wxWindow* parent = GetParentForModalDialog( GetParent(), GetWindowStyle() );
515 
516  // Show the optimal parent
517  DBG( if( parent ) printf( "%s: optimal parent: %s\n", __func__, typeid(*parent).name() );)
518 
519  wxASSERT_MSG( !m_qmodal_parent_disabler,
520  wxT( "Caller using ShowQuasiModal() twice on same window?" ) );
521 
522  // quasi-modal: disable only my "optimal" parent
524 
525  Show( true );
526 
527  m_qmodal_showing = true;
528 
529  EVENT_LOOP event_loop;
530 
531  m_qmodal_loop = &event_loop;
532 
533  event_loop.Run();
534 
535  return GetReturnCode();
536 }
537 
538 
539 void DIALOG_SHIM::EndQuasiModal( int retCode )
540 {
541  // Hook up validator and transfer data from controls handling so quasi-modal dialogs
542  // handle validation in the same way as other dialogs.
543  if( ( retCode == wxID_OK ) && ( !Validate() || !TransferDataFromWindow() ) )
544  return;
545 
546  SetReturnCode( retCode );
547 
548  if( !IsQuasiModal() )
549  {
550  wxFAIL_MSG( wxT( "either DIALOG_SHIM::EndQuasiModal called twice or ShowQuasiModal wasn't called" ) );
551  return;
552  }
553 
554  m_qmodal_showing = false;
555 
556  if( m_qmodal_loop )
557  {
558  if( m_qmodal_loop->IsRunning() )
559  m_qmodal_loop->Exit( 0 );
560  else
561  m_qmodal_loop->ScheduleExit( 0 );
562 
563  m_qmodal_loop = NULL;
564  }
565 
568 
569  Show( false );
570 }
571 
572 
573 void DIALOG_SHIM::OnCloseWindow( wxCloseEvent& aEvent )
574 {
575  if( IsQuasiModal() )
576  {
577  EndQuasiModal( wxID_CANCEL );
578  return;
579  }
580 
581  // This is mandatory to allow wxDialogBase::OnCloseWindow() to be called.
582  aEvent.Skip();
583 }
584 
585 
586 void DIALOG_SHIM::OnButton( wxCommandEvent& aEvent )
587 {
588  if( IsQuasiModal() )
589  {
590  const int id = aEvent.GetId();
591 
592  if( id == GetAffirmativeId() )
593  {
594  EndQuasiModal( id );
595  }
596  else if( id == wxID_APPLY )
597  {
598  // Dialogs that provide Apply buttons should make sure data is valid before
599  // allowing a transfer, as there is no other way to indicate failure
600  // (i.e. the dialog can't refuse to close as it might with OK, because it
601  // isn't closing anyway)
602  if( Validate() )
603  {
604  bool success = TransferDataFromWindow();
605  (void) success;
606  }
607  }
608  else if( id == GetEscapeId() ||
609  (id == wxID_CANCEL && GetEscapeId() == wxID_ANY) )
610  {
611  EndQuasiModal( wxID_CANCEL );
612  }
613  else // not a standard button
614  {
615  aEvent.Skip();
616  }
617 
618  return;
619  }
620 
621  // This is mandatory to allow wxDialogBase::OnButton() to be called.
622  aEvent.Skip();
623 }
bool m_qmodal_showing
Definition: dialog_shim.h:132
Class KIWAY_HOLDER is a mix in class which holds the location of a wxWindow's KIWAY.
Definition: kiway_player.h:48
Class KIWAY_PLAYER is a wxFrame capable of the OpenProjectFiles function, meaning it can load a porti...
Definition: kiway_player.h:111
void SetKiway(wxWindow *aDest, KIWAY *aKiway)
Function SetKiway.
#define WX_EVENT_LOOP
Definition: kiway_player.h:91
KIWAY & Kiway() const
Function Kiway returns a reference to the KIWAY that this object has an opportunity to participate in...
Definition: kiway_player.h:60
void OnButton(wxCommandEvent &aEvent)
Function OnCloseWindow.
std::string m_hash_key
Definition: dialog_shim.h:128
void FinishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
VTBL_ENTRY wxApp & App()
Function App returns a bare naked wxApp, which may come from wxPython, SINGLE_TOP, or kicad.exe.
Definition: pgm_base.cpp:321
PGM_BASE & Pgm()
The global Program "get" accessor.
Definition: kicad.cpp:65
EVENT_LOOP * m_qmodal_loop
Definition: dialog_shim.h:131
void FixOSXCancelButtonIssue()
A ugly hack to fix an issue on OSX: when typing ctrl+c in a wxTextCtrl inside a dialog, it is closed instead of copying a text if a button with wxID_CANCEL is used in a wxStdDialogButtonSizer, when the dlg is created by wxFormBuilder: the label is &Cancel, and this accelerator key has priority to copy text standard accelerator, and the dlg is closed when trying to copy text this function do nothing on other platforms.
void OnCloseWindow(wxCloseEvent &aEvent)
Function OnCloseWindow.
int ShowQuasiModal()
static RECT_MAP class_map
bool IsQuasiModal()
Definition: dialog_shim.h:93
const wxPoint & GetPosition() const
bool Show(bool show) override
WDO_ENABLE_DISABLE * m_qmodal_parent_disabler
Definition: dialog_shim.h:133
void EndQuasiModal(int retCode)
see class PGM_BASE
DIALOG_SHIM(wxWindow *aParent, wxWindowID id, const wxString &title, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxDEFAULT_DIALOG_STYLE, const wxString &name=wxDialogNameStr)
Definition: dialog_shim.cpp:57
Class EDA_RECT handles the component boundary box.
Toggle a window's "enable" status to disabled, then enabled on destruction.
Definition: dialog_shim.cpp:33
const char * name
bool Enable(bool enable) override
#define DBG(x)
Definition: fctsys.h:33
Basic classes for most KiCad items.
const wxSize & GetSize() const
WDO_ENABLE_DISABLE(wxWindow *aWindow)
Definition: dialog_shim.cpp:39
std::unordered_map< std::string, EDA_RECT > RECT_MAP
Map a C string to an EDA_RECT.
Definition: hashtables.h:136