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 
101  delete m_qmodal_parent_disabler; // usually NULL by now
102 }
103 
105 {
106  // must be called from the constructor of derived classes,
107  // when all widgets are initialized, and therefore their size fixed
108 
109  // SetSizeHints fixes the minimal size of sizers in the dialog
110  // (SetSizeHints calls Fit(), so no need to call it)
111  GetSizer()->SetSizeHints( this );
112 
113  // the default position, when calling the first time the dlg
114  Center();
115 }
116 
118 {
119 #ifdef __WXMAC__
120  // A ugly hack to fix an issue on OSX: ctrl+c closes the dialog instead of
121  // copying a text if a button with wxID_CANCEL is used in a wxStdDialogButtonSizer
122  // created by wxFormBuilder: the label is &Cancel, and this accelerator key has priority
123  // to copy text standard accelerator, and the dlg is closed when trying to copy text
124  wxButton* button = dynamic_cast< wxButton* > ( wxWindow::FindWindowById( wxID_CANCEL, this ) );
125 
126  if( button )
127  button->SetLabel( _( "Cancel" ) );
128 #endif
129 }
130 
131 
132 // our hashtable is an implementation secret, don't need or want it in a header file
133 #include <hashtables.h>
134 #include <base_struct.h> // EDA_RECT
135 #include <typeinfo>
136 
138 
139 bool DIALOG_SHIM::Show( bool show )
140 {
141  bool ret;
142  const char* hash_key;
143 
144  if( m_hash_key.size() )
145  {
146  // a special case like EDA_LIST_DIALOG, which has multiple uses.
147  hash_key = m_hash_key.c_str();
148  }
149  else
150  {
151  hash_key = typeid(*this).name();
152  }
153 
154  // Show or hide the window. If hiding, save current position and size.
155  // If showing, use previous position and size.
156  if( show )
157  {
158 #ifndef __WINDOWS__
159  wxDialog::Raise(); // Needed on OS X and some other window managers (i.e. Unity)
160 #endif
161  ret = wxDialog::Show( show );
162 
163  // classname is key, returns a zeroed out default EDA_RECT if none existed before.
164  EDA_RECT r = class_map[ hash_key ];
165 
166  if( r.GetSize().x != 0 && r.GetSize().y != 0 )
167  SetSize( r.GetPosition().x, r.GetPosition().y, r.GetSize().x, r.GetSize().y, 0 );
168  }
169  else
170  {
171  // Save the dialog's position & size before hiding, using classname as key
172  EDA_RECT r( wxDialog::GetPosition(), wxDialog::GetSize() );
173  class_map[ hash_key ] = r;
174 
175  ret = wxDialog::Show( show );
176  }
177 
179 
180  return ret;
181 }
182 
183 
184 bool DIALOG_SHIM::Enable( bool enable )
185 {
186  // so we can do logging of this state change:
187 
188 #if defined(DEBUG)
189  const char* type_id = typeid( *this ).name();
190  printf( "wxDialog %s: %s\n", type_id, enable ? "enabled" : "disabled" );
191 #endif
192 
193  return wxDialog::Enable( enable );
194 }
195 
196 
197 #if DLGSHIM_USE_SETFOCUS
198 
199 static bool findWindowRecursively( const wxWindowList& children, const wxWindow* wanted )
200 {
201  for( wxWindowList::const_iterator it = children.begin(); it != children.end(); ++it )
202  {
203  const wxWindow* child = *it;
204 
205  if( wanted == child )
206  return true;
207  else
208  {
209  if( findWindowRecursively( child->GetChildren(), wanted ) )
210  return true;
211  }
212  }
213 
214  return false;
215 }
216 
217 
218 static bool findWindowRecursively( const wxWindow* topmost, const wxWindow* wanted )
219 {
220  // wanted may be NULL and that is ok.
221 
222  if( wanted == topmost )
223  return true;
224 
225  return findWindowRecursively( topmost->GetChildren(), wanted );
226 }
227 
228 
230 void DIALOG_SHIM::onInit( wxInitDialogEvent& aEvent )
231 {
232  wxWindow* focusWnd = wxWindow::FindFocus();
233 
234  // If focusWnd is not already this window or a child of it, then SetFocus().
235  // Otherwise the derived class's constructor SetFocus() already to a specific
236  // child control.
237 
238  if( !findWindowRecursively( this, focusWnd ) )
239  {
240  // Linux wxGTK needs this to allow the ESCAPE key to close a wxDialog window.
241  SetFocus();
242  }
243 
244  aEvent.Skip(); // derived class's handler should be called too
245 }
246 #endif
247 
248 
249 /*
250  Quasi-Modal Mode Explained:
251 
252  The gtk calls in wxDialog::ShowModal() cause event routing problems if that
253  modal dialog then tries to use KIWAY_PLAYER::ShowModal(). The latter shows up
254  and mostly works but does not respond to the window decoration close button.
255  There is no way to get around this without reversing the gtk calls temporarily.
256 
257  Quasi-Modal mode is our own almost modal mode which disables only the parent
258  of the DIALOG_SHIM, leaving other frames operable and while staying captured in the
259  nested event loop. This avoids the gtk calls and leaves event routing pure
260  and sufficient to operate the KIWAY_PLAYER::ShowModal() properly. When using
261  ShowQuasiModal() you have to use EndQuasiModal() in your dialogs and not
262  EndModal(). There is also IsQuasiModal() but its value can only be true
263  when the nested event loop is active. Do not mix the modal and quasi-modal
264  functions. Use one set or the other.
265 
266  You might find this behavior preferable over a pure modal mode, and it was said
267  that only the Mac has this natively, but now other platforms have something
268  similar. You CAN use it anywhere for any dialog. But you MUST use it when
269  you want to use KIWAY_PLAYER::ShowModal() from a dialog event.
270 */
271 
272 
273 
274 /*
278 class ELOOP_ACTIVATOR
279 {
280  friend class EVENT_LOOP;
281 
282 public:
283  ELOOP_ACTIVATOR( WX_EVENT_LOOP* evtLoop )
284  {
285  m_evtLoopOld = wxEventLoopBase::GetActive();
286  // wxEventLoopBase::SetActive( evtLoop );
287  }
288 
289  ~ELOOP_ACTIVATOR()
290  {
291  // restore the previously active event loop
292  wxEventLoopBase::SetActive( m_evtLoopOld );
293  }
294 
295 private:
296  WX_EVENT_LOOP* m_evtLoopOld;
297 };
298 */
299 
300 
301 class EVENT_LOOP : public WX_EVENT_LOOP
302 {
303 public:
304 
306  {
307  ;
308  }
309 
311  {
312  }
313 
314 #if 0 // does not work any better than inherited wxGuiEventLoop functions:
315 
316  // sets the "should exit" flag and wakes up the loop so that it terminates
317  // soon
318  void ScheduleExit( int rc = 0 )
319  {
320  wxCHECK_RET( IsInsideRun(), wxT("can't call ScheduleExit() if not running") );
321 
322  m_exitcode = rc;
323  m_shouldExit = true;
324 
325  OnExit();
326 
327  // all we have to do to exit from the loop is to (maybe) wake it up so that
328  // it can notice that Exit() had been called
329  //
330  // in particular, do *not* use here calls such as PostQuitMessage() (under
331  // MSW) which terminate the current event loop here because we're not sure
332  // that it is going to be processed by the correct event loop: it would be
333  // possible that another one is started and terminated by mistake if we do
334  // this
335  WakeUp();
336  }
337 
338  int Run()
339  {
340  // event loops are not recursive, you need to create another loop!
341  //wxCHECK_MSG( !IsInsideRun(), -1, wxT("can't reenter a message loop") );
342 
343  // ProcessIdle() and ProcessEvents() below may throw so the code here should
344  // be exception-safe, hence we must use local objects for all actions we
345  // should undo
346  wxEventLoopActivator activate(this);
347 
348  // We might be called again, after a previous call to ScheduleExit(), so
349  // reset this flag.
350  m_shouldExit = false;
351 
352  // Set this variable to true for the duration of this method.
353  setInsideRun( true );
354 
355  struct SET_FALSE
356  {
357  EVENT_LOOP* m_loop;
358  SET_FALSE( EVENT_LOOP* aLoop ) : m_loop( aLoop ) {}
359  ~SET_FALSE() { m_loop->setInsideRun( false ); }
360  } t( this );
361 
362  // Finally really run the loop.
363  return DoRun();
364  }
365 
366  bool ProcessEvents()
367  {
368  // process pending wx events first as they correspond to low-level events
369  // which happened before, i.e. typically pending events were queued by a
370  // previous call to Dispatch() and if we didn't process them now the next
371  // call to it might enqueue them again (as happens with e.g. socket events
372  // which would be generated as long as there is input available on socket
373  // and this input is only removed from it when pending event handlers are
374  // executed)
375  if( wxTheApp )
376  {
377  wxTheApp->ProcessPendingEvents();
378 
379  // One of the pending event handlers could have decided to exit the
380  // loop so check for the flag before trying to dispatch more events
381  // (which could block indefinitely if no more are coming).
382  if( m_shouldExit )
383  return false;
384  }
385 
386  return Dispatch();
387  }
388 
389 
390  int DoRun()
391  {
392  // we must ensure that OnExit() is called even if an exception is thrown
393  // from inside ProcessEvents() but we must call it from Exit() in normal
394  // situations because it is supposed to be called synchronously,
395  // wxModalEventLoop depends on this (so we can't just use ON_BLOCK_EXIT or
396  // something similar here)
397  #if wxUSE_EXCEPTIONS
398  for( ; ; )
399  {
400  try
401  {
402  #endif // wxUSE_EXCEPTIONS
403 
404  // this is the event loop itself
405  for( ; ; )
406  {
407  // generate and process idle events for as long as we don't
408  // have anything else to do
409  while ( !m_shouldExit && !Pending() && ProcessIdle() )
410  ;
411 
412  if ( m_shouldExit )
413  break;
414 
415  // a message came or no more idle processing to do, dispatch
416  // all the pending events and call Dispatch() to wait for the
417  // next message
418  if ( !ProcessEvents() )
419  {
420  // we got WM_QUIT
421  break;
422  }
423  }
424 
425  // Process the remaining queued messages, both at the level of the
426  // underlying toolkit level (Pending/Dispatch()) and wx level
427  // (Has/ProcessPendingEvents()).
428  //
429  // We do run the risk of never exiting this loop if pending event
430  // handlers endlessly generate new events but they shouldn't do
431  // this in a well-behaved program and we shouldn't just discard the
432  // events we already have, they might be important.
433  for( ; ; )
434  {
435  bool hasMoreEvents = false;
436  if ( wxTheApp && wxTheApp->HasPendingEvents() )
437  {
438  wxTheApp->ProcessPendingEvents();
439  hasMoreEvents = true;
440  }
441 
442  if ( Pending() )
443  {
444  Dispatch();
445  hasMoreEvents = true;
446  }
447 
448  if ( !hasMoreEvents )
449  break;
450  }
451 
452  #if wxUSE_EXCEPTIONS
453  // exit the outer loop as well
454  break;
455  }
456  catch ( ... )
457  {
458  try
459  {
460  if ( !wxTheApp || !wxTheApp->OnExceptionInMainLoop() )
461  {
462  OnExit();
463  break;
464  }
465  //else: continue running the event loop
466  }
467  catch ( ... )
468  {
469  // OnException() throwed, possibly rethrowing the same
470  // exception again: very good, but we still need OnExit() to
471  // be called
472  OnExit();
473  throw;
474  }
475  }
476  }
477  #endif // wxUSE_EXCEPTIONS
478 
479  return m_exitcode;
480  }
481 
482 protected:
483  int m_exitcode;
484 
485  /* this only works if you add
486  friend class EVENT_LOOP
487  to EventLoopBase
488  */
489  void setInsideRun( bool aValue )
490  {
491  m_isInsideRun = aValue;
492  }
493 #endif
494 };
495 
496 
498 {
499  // This is an exception safe way to zero a pointer before returning.
500  // Yes, even though DismissModal() clears this first normally, this is
501  // here in case there's an exception before the dialog is dismissed.
502  struct NULLER
503  {
504  void*& m_what;
505  NULLER( void*& aPtr ) : m_what( aPtr ) {}
506  ~NULLER() { m_what = 0; } // indeed, set it to NULL on destruction
507  } clear_this( (void*&) m_qmodal_loop );
508 
509  // release the mouse if it's currently captured as the window having it
510  // will be disabled when this dialog is shown -- but will still keep the
511  // capture making it impossible to do anything in the modal dialog itself
512  wxWindow* win = wxWindow::GetCapture();
513  if( win )
514  win->ReleaseMouse();
515 
516  // Get the optimal parent
517  wxWindow* parent = GetParentForModalDialog( GetParent(), GetWindowStyle() );
518 
519  // Show the optimal parent
520  DBG( if( parent ) printf( "%s: optimal parent: %s\n", __func__, typeid(*parent).name() );)
521 
522  wxASSERT_MSG( !m_qmodal_parent_disabler,
523  wxT( "Caller using ShowQuasiModal() twice on same window?" ) );
524 
525  // quasi-modal: disable only my "optimal" parent
527 
528  Show( true );
529 
530  m_qmodal_showing = true;
531 
532  EVENT_LOOP event_loop;
533 
534  m_qmodal_loop = &event_loop;
535 
536  event_loop.Run();
537 
538  return GetReturnCode();
539 }
540 
541 
542 void DIALOG_SHIM::EndQuasiModal( int retCode )
543 {
544  // Hook up validator and transfer data from controls handling so quasi-modal dialogs
545  // handle validation in the same way as other dialogs.
546  if( ( retCode == wxID_OK ) && ( !Validate() || !TransferDataFromWindow() ) )
547  return;
548 
549  SetReturnCode( retCode );
550 
551  if( !IsQuasiModal() )
552  {
553  wxFAIL_MSG( wxT( "either DIALOG_SHIM::EndQuasiModal called twice or ShowQuasiModal wasn't called" ) );
554  return;
555  }
556 
557  m_qmodal_showing = false;
558 
559  if( m_qmodal_loop )
560  {
561  if( m_qmodal_loop->IsRunning() )
562  m_qmodal_loop->Exit( 0 );
563  else
564  m_qmodal_loop->ScheduleExit( 0 );
565 
566  m_qmodal_loop = NULL;
567  }
568 
571 
572  Show( false );
573 }
574 
575 
576 void DIALOG_SHIM::OnCloseWindow( wxCloseEvent& aEvent )
577 {
578  if( IsQuasiModal() )
579  {
580  EndQuasiModal( wxID_CANCEL );
581  return;
582  }
583 
584  // This is mandatory to allow wxDialogBase::OnCloseWindow() to be called.
585  aEvent.Skip();
586 }
587 
588 
589 void DIALOG_SHIM::OnButton( wxCommandEvent& aEvent )
590 {
591  if( IsQuasiModal() )
592  {
593  const int id = aEvent.GetId();
594 
595  if( id == GetAffirmativeId() )
596  {
597  EndQuasiModal( id );
598  }
599  else if( id == wxID_APPLY )
600  {
601  // Dialogs that provide Apply buttons should make sure data is valid before
602  // allowing a transfer, as there is no other way to indicate failure
603  // (i.e. the dialog can't refuse to close as it might with OK, because it
604  // isn't closing anyway)
605  if( Validate() )
606  {
607  bool success = TransferDataFromWindow();
608  (void) success;
609  }
610  }
611  else if( id == GetEscapeId() ||
612  (id == wxID_CANCEL && GetEscapeId() == wxID_ANY) )
613  {
614  EndQuasiModal( wxID_CANCEL );
615  }
616  else // not a standard button
617  {
618  aEvent.Skip();
619  }
620 
621  return;
622  }
623 
624  // This is mandatory to allow wxDialogBase::OnButton() to be called.
625  aEvent.Skip();
626 }
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:323
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