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