summaryrefslogtreecommitdiff
path: root/common/dialog_shim.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'common/dialog_shim.cpp')
-rw-r--r--common/dialog_shim.cpp619
1 files changed, 619 insertions, 0 deletions
diff --git a/common/dialog_shim.cpp b/common/dialog_shim.cpp
new file mode 100644
index 0000000..a05a5b6
--- /dev/null
+++ b/common/dialog_shim.cpp
@@ -0,0 +1,619 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
+ * Copyright (C) 2012-2016 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include <dialog_shim.h>
+#include <kiway_player.h>
+#include <wx/evtloop.h>
+#include <pgm_base.h>
+
+
+/// Toggle a window's "enable" status to disabled, then enabled on destruction.
+class WDO_ENABLE_DISABLE
+{
+ wxWindow* m_win;
+
+public:
+
+ WDO_ENABLE_DISABLE( wxWindow* aWindow ) :
+ m_win( aWindow )
+ {
+ if( m_win )
+ m_win->Disable();
+ }
+
+ ~WDO_ENABLE_DISABLE()
+ {
+ if( m_win )
+ {
+ m_win->Enable();
+ m_win->SetFocus(); // let's focus back on the parent window
+ }
+ }
+};
+
+
+DIALOG_SHIM::DIALOG_SHIM( wxWindow* aParent, wxWindowID id, const wxString& title,
+ const wxPoint& pos, const wxSize& size, long style, const wxString& name ) :
+ wxDialog( aParent, id, title, pos, size, style, name ),
+ KIWAY_HOLDER( 0 ),
+ m_qmodal_loop( 0 ),
+ m_qmodal_showing( false ),
+ m_qmodal_parent_disabler( 0 )
+{
+ // pray that aParent is either a KIWAY_PLAYER or DIALOG_SHIM derivation.
+ KIWAY_HOLDER* h = dynamic_cast<KIWAY_HOLDER*>( aParent );
+
+ // wxASSERT_MSG( h, wxT( "DIALOG_SHIM's parent is NULL or not derived from KIWAY_PLAYER nor DIALOG_SHIM" ) );
+
+ if( h )
+ SetKiway( this, &h->Kiway() );
+
+ Bind( wxEVT_CLOSE_WINDOW, &DIALOG_SHIM::OnCloseWindow, this );
+ Bind( wxEVT_BUTTON, &DIALOG_SHIM::OnButton, this );
+
+#ifdef __WINDOWS__
+ // On Windows, the app top windows can be brought to the foreground
+ // (at least temporary) in certain circumstances,
+ // for instance when calling an external tool in Eeschema boom generation.
+ // So set the parent KIWAY_PLAYER kicad frame (if exists) to top window
+ // to avoid this annoying behavior
+ KIWAY_PLAYER* parent_kiwayplayer = dynamic_cast<KIWAY_PLAYER*>( aParent );
+
+ if( parent_kiwayplayer )
+ Pgm().App().SetTopWindow( parent_kiwayplayer );
+#endif
+
+#if DLGSHIM_USE_SETFOCUS
+ Connect( wxEVT_INIT_DIALOG, wxInitDialogEventHandler( DIALOG_SHIM::onInit ) );
+#endif
+}
+
+
+DIALOG_SHIM::~DIALOG_SHIM()
+{
+ // if the dialog is quasi-modal, this will end its event loop
+ if( IsQuasiModal() )
+ EndQuasiModal( wxID_CANCEL );
+
+ delete m_qmodal_parent_disabler; // usually NULL by now
+}
+
+void DIALOG_SHIM::FinishDialogSettings()
+{
+ // must be called from the constructor of derived classes,
+ // when all widgets are initialized, and therefore their size fixed
+
+ // SetSizeHints fixes the minimal size of sizers in the dialog
+ // (SetSizeHints calls Fit(), so no need to call it)
+ GetSizer()->SetSizeHints( this );
+
+ // the default position, when calling the first time the dlg
+ Center();
+}
+
+void DIALOG_SHIM::FixOSXCancelButtonIssue()
+{
+#ifdef __WXMAC__
+ // A ugly hack to fix an issue on OSX: ctrl+c closes the dialog instead of
+ // copying a text if a button with wxID_CANCEL is used in a wxStdDialogButtonSizer
+ // 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
+ wxButton* button = dynamic_cast< wxButton* > ( wxWindow::FindWindowById( wxID_CANCEL, this ) );
+
+ if( button )
+ button->SetLabel( _( "Cancel" ) );
+#endif
+}
+
+
+// our hashtable is an implementation secret, don't need or want it in a header file
+#include <hashtables.h>
+#include <base_struct.h> // EDA_RECT
+#include <typeinfo>
+
+static RECT_MAP class_map;
+
+bool DIALOG_SHIM::Show( bool show )
+{
+ bool ret;
+ const char* hash_key;
+
+ if( m_hash_key.size() )
+ {
+ // a special case like EDA_LIST_DIALOG, which has multiple uses.
+ hash_key = m_hash_key.c_str();
+ }
+ else
+ {
+ hash_key = typeid(*this).name();
+ }
+
+ // Show or hide the window. If hiding, save current position and size.
+ // If showing, use previous position and size.
+ if( show )
+ {
+ wxDialog::Raise(); // Needed on OS X and some other window managers (i.e. Unity)
+ ret = wxDialog::Show( show );
+
+ // classname is key, returns a zeroed out default EDA_RECT if none existed before.
+ EDA_RECT r = class_map[ hash_key ];
+
+ if( r.GetSize().x != 0 && r.GetSize().y != 0 )
+ SetSize( r.GetPosition().x, r.GetPosition().y, r.GetSize().x, r.GetSize().y, 0 );
+ }
+ else
+ {
+ // Save the dialog's position & size before hiding, using classname as key
+ EDA_RECT r( wxDialog::GetPosition(), wxDialog::GetSize() );
+ class_map[ hash_key ] = r;
+
+ ret = wxDialog::Show( show );
+ }
+ return ret;
+}
+
+
+bool DIALOG_SHIM::Enable( bool enable )
+{
+ // so we can do logging of this state change:
+
+#if defined(DEBUG)
+ const char* type_id = typeid( *this ).name();
+ printf( "wxDialog %s: %s\n", type_id, enable ? "enabled" : "disabled" );
+#endif
+
+ return wxDialog::Enable( enable );
+}
+
+
+#if DLGSHIM_USE_SETFOCUS
+
+static bool findWindowRecursively( const wxWindowList& children, const wxWindow* wanted )
+{
+ for( wxWindowList::const_iterator it = children.begin(); it != children.end(); ++it )
+ {
+ const wxWindow* child = *it;
+
+ if( wanted == child )
+ return true;
+ else
+ {
+ if( findWindowRecursively( child->GetChildren(), wanted ) )
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+static bool findWindowRecursively( const wxWindow* topmost, const wxWindow* wanted )
+{
+ // wanted may be NULL and that is ok.
+
+ if( wanted == topmost )
+ return true;
+
+ return findWindowRecursively( topmost->GetChildren(), wanted );
+}
+
+
+/// Set the focus if it is not already set in a derived constructor to a specific control.
+void DIALOG_SHIM::onInit( wxInitDialogEvent& aEvent )
+{
+ wxWindow* focusWnd = wxWindow::FindFocus();
+
+ // If focusWnd is not already this window or a child of it, then SetFocus().
+ // Otherwise the derived class's constructor SetFocus() already to a specific
+ // child control.
+
+ if( !findWindowRecursively( this, focusWnd ) )
+ {
+ // Linux wxGTK needs this to allow the ESCAPE key to close a wxDialog window.
+ SetFocus();
+ }
+
+ aEvent.Skip(); // derived class's handler should be called too
+}
+#endif
+
+
+/*
+ Quasi-Modal Mode Explained:
+
+ The gtk calls in wxDialog::ShowModal() cause event routing problems if that
+ modal dialog then tries to use KIWAY_PLAYER::ShowModal(). The latter shows up
+ and mostly works but does not respond to the window decoration close button.
+ There is no way to get around this without reversing the gtk calls temporarily.
+
+ Quasi-Modal mode is our own almost modal mode which disables only the parent
+ of the DIALOG_SHIM, leaving other frames operable and while staying captured in the
+ nested event loop. This avoids the gtk calls and leaves event routing pure
+ and sufficient to operate the KIWAY_PLAYER::ShowModal() properly. When using
+ ShowQuasiModal() you have to use EndQuasiModal() in your dialogs and not
+ EndModal(). There is also IsQuasiModal() but its value can only be true
+ when the nested event loop is active. Do not mix the modal and quasi-modal
+ functions. Use one set or the other.
+
+ You might find this behavior preferable over a pure modal mode, and it was said
+ that only the Mac has this natively, but now other platforms have something
+ similar. You CAN use it anywhere for any dialog. But you MUST use it when
+ you want to use KIWAY_PLAYER::ShowModal() from a dialog event.
+*/
+
+
+
+/*
+/// wxEventLoopActivator but with a friend so it
+/// has access to m_evtLoopOld, and it does not SetActive() as that is
+/// done inside base class Run().
+class ELOOP_ACTIVATOR
+{
+ friend class EVENT_LOOP;
+
+public:
+ ELOOP_ACTIVATOR( WX_EVENT_LOOP* evtLoop )
+ {
+ m_evtLoopOld = wxEventLoopBase::GetActive();
+ // wxEventLoopBase::SetActive( evtLoop );
+ }
+
+ ~ELOOP_ACTIVATOR()
+ {
+ // restore the previously active event loop
+ wxEventLoopBase::SetActive( m_evtLoopOld );
+ }
+
+private:
+ WX_EVENT_LOOP* m_evtLoopOld;
+};
+*/
+
+
+class EVENT_LOOP : public WX_EVENT_LOOP
+{
+public:
+
+ EVENT_LOOP()
+ {
+ ;
+ }
+
+ ~EVENT_LOOP()
+ {
+ }
+
+#if 0 // does not work any better than inherited wxGuiEventLoop functions:
+
+ // sets the "should exit" flag and wakes up the loop so that it terminates
+ // soon
+ void ScheduleExit( int rc = 0 )
+ {
+ wxCHECK_RET( IsInsideRun(), wxT("can't call ScheduleExit() if not running") );
+
+ m_exitcode = rc;
+ m_shouldExit = true;
+
+ OnExit();
+
+ // all we have to do to exit from the loop is to (maybe) wake it up so that
+ // it can notice that Exit() had been called
+ //
+ // in particular, do *not* use here calls such as PostQuitMessage() (under
+ // MSW) which terminate the current event loop here because we're not sure
+ // that it is going to be processed by the correct event loop: it would be
+ // possible that another one is started and terminated by mistake if we do
+ // this
+ WakeUp();
+ }
+
+ int Run()
+ {
+ // event loops are not recursive, you need to create another loop!
+ //wxCHECK_MSG( !IsInsideRun(), -1, wxT("can't reenter a message loop") );
+
+ // ProcessIdle() and ProcessEvents() below may throw so the code here should
+ // be exception-safe, hence we must use local objects for all actions we
+ // should undo
+ wxEventLoopActivator activate(this);
+
+ // We might be called again, after a previous call to ScheduleExit(), so
+ // reset this flag.
+ m_shouldExit = false;
+
+ // Set this variable to true for the duration of this method.
+ setInsideRun( true );
+
+ struct SET_FALSE
+ {
+ EVENT_LOOP* m_loop;
+ SET_FALSE( EVENT_LOOP* aLoop ) : m_loop( aLoop ) {}
+ ~SET_FALSE() { m_loop->setInsideRun( false ); }
+ } t( this );
+
+ // Finally really run the loop.
+ return DoRun();
+ }
+
+ bool ProcessEvents()
+ {
+ // process pending wx events first as they correspond to low-level events
+ // which happened before, i.e. typically pending events were queued by a
+ // previous call to Dispatch() and if we didn't process them now the next
+ // call to it might enqueue them again (as happens with e.g. socket events
+ // which would be generated as long as there is input available on socket
+ // and this input is only removed from it when pending event handlers are
+ // executed)
+ if( wxTheApp )
+ {
+ wxTheApp->ProcessPendingEvents();
+
+ // One of the pending event handlers could have decided to exit the
+ // loop so check for the flag before trying to dispatch more events
+ // (which could block indefinitely if no more are coming).
+ if( m_shouldExit )
+ return false;
+ }
+
+ return Dispatch();
+ }
+
+
+ int DoRun()
+ {
+ // we must ensure that OnExit() is called even if an exception is thrown
+ // from inside ProcessEvents() but we must call it from Exit() in normal
+ // situations because it is supposed to be called synchronously,
+ // wxModalEventLoop depends on this (so we can't just use ON_BLOCK_EXIT or
+ // something similar here)
+ #if wxUSE_EXCEPTIONS
+ for( ; ; )
+ {
+ try
+ {
+ #endif // wxUSE_EXCEPTIONS
+
+ // this is the event loop itself
+ for( ; ; )
+ {
+ // generate and process idle events for as long as we don't
+ // have anything else to do
+ while ( !m_shouldExit && !Pending() && ProcessIdle() )
+ ;
+
+ if ( m_shouldExit )
+ break;
+
+ // a message came or no more idle processing to do, dispatch
+ // all the pending events and call Dispatch() to wait for the
+ // next message
+ if ( !ProcessEvents() )
+ {
+ // we got WM_QUIT
+ break;
+ }
+ }
+
+ // Process the remaining queued messages, both at the level of the
+ // underlying toolkit level (Pending/Dispatch()) and wx level
+ // (Has/ProcessPendingEvents()).
+ //
+ // We do run the risk of never exiting this loop if pending event
+ // handlers endlessly generate new events but they shouldn't do
+ // this in a well-behaved program and we shouldn't just discard the
+ // events we already have, they might be important.
+ for( ; ; )
+ {
+ bool hasMoreEvents = false;
+ if ( wxTheApp && wxTheApp->HasPendingEvents() )
+ {
+ wxTheApp->ProcessPendingEvents();
+ hasMoreEvents = true;
+ }
+
+ if ( Pending() )
+ {
+ Dispatch();
+ hasMoreEvents = true;
+ }
+
+ if ( !hasMoreEvents )
+ break;
+ }
+
+ #if wxUSE_EXCEPTIONS
+ // exit the outer loop as well
+ break;
+ }
+ catch ( ... )
+ {
+ try
+ {
+ if ( !wxTheApp || !wxTheApp->OnExceptionInMainLoop() )
+ {
+ OnExit();
+ break;
+ }
+ //else: continue running the event loop
+ }
+ catch ( ... )
+ {
+ // OnException() throwed, possibly rethrowing the same
+ // exception again: very good, but we still need OnExit() to
+ // be called
+ OnExit();
+ throw;
+ }
+ }
+ }
+ #endif // wxUSE_EXCEPTIONS
+
+ return m_exitcode;
+ }
+
+protected:
+ int m_exitcode;
+
+ /* this only works if you add
+ friend class EVENT_LOOP
+ to EventLoopBase
+ */
+ void setInsideRun( bool aValue )
+ {
+ m_isInsideRun = aValue;
+ }
+#endif
+};
+
+
+int DIALOG_SHIM::ShowQuasiModal()
+{
+ // This is an exception safe way to zero a pointer before returning.
+ // Yes, even though DismissModal() clears this first normally, this is
+ // here in case there's an exception before the dialog is dismissed.
+ struct NULLER
+ {
+ void*& m_what;
+ NULLER( void*& aPtr ) : m_what( aPtr ) {}
+ ~NULLER() { m_what = 0; } // indeed, set it to NULL on destruction
+ } clear_this( (void*&) m_qmodal_loop );
+
+ // release the mouse if it's currently captured as the window having it
+ // will be disabled when this dialog is shown -- but will still keep the
+ // capture making it impossible to do anything in the modal dialog itself
+ wxWindow* win = wxWindow::GetCapture();
+ if( win )
+ win->ReleaseMouse();
+
+ // Get the optimal parent
+ wxWindow* parent = GetParentForModalDialog( GetParent(), GetWindowStyle() );
+
+ // Show the optimal parent
+ DBG( if( parent ) printf( "%s: optimal parent: %s\n", __func__, typeid(*parent).name() );)
+
+ wxASSERT_MSG( !m_qmodal_parent_disabler,
+ wxT( "Caller using ShowQuasiModal() twice on same window?" ) );
+
+ // quasi-modal: disable only my "optimal" parent
+ m_qmodal_parent_disabler = new WDO_ENABLE_DISABLE( parent );
+
+ Show( true );
+
+ m_qmodal_showing = true;
+
+ EVENT_LOOP event_loop;
+
+ m_qmodal_loop = &event_loop;
+
+ event_loop.Run();
+
+ return GetReturnCode();
+}
+
+
+void DIALOG_SHIM::EndQuasiModal( int retCode )
+{
+ // Hook up validator and transfer data from controls handling so quasi-modal dialogs
+ // handle validation in the same way as other dialogs.
+ if( ( retCode == wxID_OK ) && ( !Validate() || !TransferDataFromWindow() ) )
+ return;
+
+ SetReturnCode( retCode );
+
+ if( !IsQuasiModal() )
+ {
+ wxFAIL_MSG( wxT( "either DIALOG_SHIM::EndQuasiModal called twice or ShowQuasiModal wasn't called" ) );
+ return;
+ }
+
+ m_qmodal_showing = false;
+
+ if( m_qmodal_loop )
+ {
+ if( m_qmodal_loop->IsRunning() )
+ m_qmodal_loop->Exit( 0 );
+ else
+ m_qmodal_loop->ScheduleExit( 0 );
+
+ m_qmodal_loop = NULL;
+ }
+
+ delete m_qmodal_parent_disabler;
+ m_qmodal_parent_disabler = 0;
+
+ Show( false );
+}
+
+
+void DIALOG_SHIM::OnCloseWindow( wxCloseEvent& aEvent )
+{
+ if( IsQuasiModal() )
+ {
+ EndQuasiModal( wxID_CANCEL );
+ return;
+ }
+
+ // This is mandatory to allow wxDialogBase::OnCloseWindow() to be called.
+ aEvent.Skip();
+}
+
+
+void DIALOG_SHIM::OnButton( wxCommandEvent& aEvent )
+{
+ if( IsQuasiModal() )
+ {
+ const int id = aEvent.GetId();
+
+ if( id == GetAffirmativeId() )
+ {
+ EndQuasiModal( id );
+ }
+ else if( id == wxID_APPLY )
+ {
+ // Dialogs that provide Apply buttons should make sure data is valid before
+ // allowing a transfer, as there is no other way to indicate failure
+ // (i.e. the dialog can't refuse to close as it might with OK, because it
+ // isn't closing anyway)
+ if( Validate() )
+ {
+ bool success = TransferDataFromWindow();
+ (void) success;
+ }
+ }
+ else if( id == GetEscapeId() ||
+ (id == wxID_CANCEL && GetEscapeId() == wxID_ANY) )
+ {
+ EndQuasiModal( wxID_CANCEL );
+ }
+ else // not a standard button
+ {
+ aEvent.Skip();
+ }
+
+ return;
+ }
+
+ // This is mandatory to allow wxDialogBase::OnButton() to be called.
+ aEvent.Skip();
+}