/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2014 SoftPLC Corporation, Dick Hollenbeck * Copyright (C) 2014-2020 KiCad Developers, see CHANGELOG.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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include KIFACE* KIWAY::m_kiface[KIWAY_FACE_COUNT]; int KIWAY::m_kiface_version[KIWAY_FACE_COUNT]; KIWAY::KIWAY( PGM_BASE* aProgram, int aCtlBits, wxFrame* aTop ): m_program( aProgram ), m_ctl( aCtlBits ), m_top( 0 ) { SetTop( aTop ); // hook player_destroy_handler() into aTop. // Prepare the room to store the frame names, once they will be created // with FRAME_T type as index in this table. // (note this is a list of frame names, but a non empty entry // does not mean the frame still exists. It means only the frame was created // at least once. It can be destroyed after. These entries are not cleared. // the purpose is just to allow a call to wxWindow::FindWindowByName(), from // a FRAME_T frame type m_playerFrameName.Add( wxEmptyString, KIWAY_PLAYER_COUNT ); } #if 0 // Any event types derived from wxCommandEvt, like wxWindowDestroyEvent, are // propagated upwards to parent windows if not handled below. Therefore the // m_top window should receive all wxWindowDestroyEvents originating from // KIWAY_PLAYERs. It does anyways, but now player_destroy_handler eavesdrops // on that event stream looking for KIWAY_PLAYERs being closed. void KIWAY::player_destroy_handler( wxWindowDestroyEvent& event ) { // Currently : do nothing event.Skip(); // skip to who, the wxApp? I'm the top window. } #endif void KIWAY::SetTop( wxFrame* aTop ) { #if 0 if( m_top ) { m_top->Disconnect( wxEVT_DESTROY, wxWindowDestroyEventHandler( KIWAY::player_destroy_handler ), NULL, this ); } if( aTop ) { aTop->Connect( wxEVT_DESTROY, wxWindowDestroyEventHandler( KIWAY::player_destroy_handler ), NULL, this ); } #endif m_top = aTop; } const wxString KIWAY::dso_search_path( FACE_T aFaceId ) { const char* name; switch( aFaceId ) { case FACE_SCH: name = KIFACE_PREFIX "eeschema"; break; case FACE_PCB: name = KIFACE_PREFIX "pcbnew"; break; case FACE_CVPCB: name = KIFACE_PREFIX "cvpcb"; break; case FACE_GERBVIEW: name = KIFACE_PREFIX "gerbview"; break; case FACE_PL_EDITOR: name = KIFACE_PREFIX "pl_editor"; break; case FACE_PCB_CALCULATOR: name = KIFACE_PREFIX "pcb_calculator"; break; case FACE_BMP2CMP: name = KIFACE_PREFIX "bitmap2component"; break; default: wxASSERT_MSG( 0, wxT( "caller has a bug, passed a bad aFaceId" ) ); return wxEmptyString; } #ifndef __WXMAC__ wxString path; if( m_ctl & (KFCTL_STANDALONE | KFCTL_CPP_PROJECT_SUITE) ) { // The 2 *.cpp program launchers: single_top.cpp and kicad.cpp expect // the *.kiface's to reside in same directory as their binaries do. // Not so for python launcher, identified by KFCTL_PY_PROJECT_SUITE path = wxStandardPaths::Get().GetExecutablePath(); } wxFileName fn = path; #else // we have the dso's in main OSX bundle kicad.app/Contents/PlugIns wxFileName fn = Pgm().GetExecutablePath(); fn.AppendDir( wxT( "Contents" ) ); fn.AppendDir( wxT( "PlugIns" ) ); #endif fn.SetName( name ); #ifdef DEBUG // To speed up development, it's sometimes nice to run kicad from inside // the build path. In that case, each program will be in a subdirectory. // To find the DSOs, we need to go up one directory and then enter a subdirectory. if( wxGetEnv( wxT( "KICAD_RUN_FROM_BUILD_DIR" ), nullptr ) ) { #ifdef __WXMAC__ fn = wxStandardPaths::Get().GetExecutablePath(); fn.RemoveLastDir(); fn.AppendDir( wxT( "PlugIns" ) ); fn.SetName( name ); #else const char* dirName; // The subdirectories usually have the same name as the kiface switch( aFaceId ) { case FACE_PL_EDITOR: dirName = "pagelayout_editor"; break; default: dirName = name + 1; break; } fn.RemoveLastDir(); fn.AppendDir( dirName ); #endif } #endif // Here a "suffix" == an extension with a preceding '.', // so skip the preceding '.' to get an extension fn.SetExt( &KIFACE_SUFFIX[1] ); return fn.GetFullPath(); } PROJECT& KIWAY::Prj() const { return Pgm().GetSettingsManager().Prj(); } KIFACE* KIWAY::KiFACE( FACE_T aFaceId, bool doLoad ) { // Since this will be called from python, cannot assume that code will // not pass a bad aFaceId. if( (unsigned) aFaceId >= arrayDim( m_kiface ) ) { // @todo : throw an exception here for python's benefit, at least that // way it gets some explanatory text. wxASSERT_MSG( 0, wxT( "caller has a bug, passed a bad aFaceId" ) ); return NULL; } // return the previously loaded KIFACE, if it was. if( m_kiface[aFaceId] ) return m_kiface[aFaceId]; wxString msg; // DSO with KIFACE has not been loaded yet, does caller want to load it? if( doLoad ) { wxString dname = dso_search_path( aFaceId ); wxDynamicLibrary dso; void* addr = NULL; // For some reason wxDynamicLibrary::Load() crashes in some languages // (chinese for instance) when loading the dynamic library. // The crash happens for Eeschema. // So switch to "C" locale during loading (LC_COLLATE is enough). int lc_new_type = LC_COLLATE; std::string user_locale = setlocale( lc_new_type, nullptr ); setlocale( lc_new_type, "C" ); bool success = dso.Load( dname, wxDL_VERBATIM | wxDL_NOW | wxDL_GLOBAL ); setlocale( lc_new_type, user_locale.c_str() ); if( !success ) { // Failure: error reporting UI was done via wxLogSysError(). // No further reporting required here. Apparently this is not true on all // platforms and/or wxWidgets builds and KiCad will crash. Throwing the exception // here and catching it in the KiCad launcher resolves the crash issue. See bug // report https://bugs.launchpad.net/kicad/+bug/1577786. msg.Printf( _( "Failed to load kiface library \"%s\"." ), dname ); THROW_IO_ERROR( msg ); } else if( ( addr = dso.GetSymbol( wxT( KIFACE_INSTANCE_NAME_AND_VERSION ) ) ) == NULL ) { // Failure: error reporting UI was done via wxLogSysError(). // No further reporting required here. Assume the same thing applies here as // above with the Load() call. This has not been tested. msg.Printf( _( "Could not read instance name and version symbol from kiface " "library \"%s\"." ), dname ); THROW_IO_ERROR( msg ); } else { KIFACE_GETTER_FUNC* getter = (KIFACE_GETTER_FUNC*) addr; KIFACE* kiface = getter( &m_kiface_version[aFaceId], KIFACE_VERSION, m_program ); // KIFACE_GETTER_FUNC function comment (API) says the non-NULL is unconditional. wxASSERT_MSG( kiface, wxT( "attempted DSO has a bug, failed to return a KIFACE*" ) ); // Give the DSO a single chance to do its "process level" initialization. // "Process level" specifically means stay away from any projects in there. if( kiface->OnKifaceStart( m_program, m_ctl ) ) { // Tell dso's wxDynamicLibrary destructor not to Unload() the program image. (void) dso.Detach(); return m_kiface[aFaceId] = kiface; } } // In any of the failure cases above, dso.Unload() should be called here // by dso destructor. // However: // There is a file installation bug. We only look for KIFACE_I's which we know // to exist, and we did not find one. If we do not find one, this is an // installation bug. msg = wxString::Format( _( "Fatal Installation Bug. File:\n" "\"%s\"\ncould not be loaded\n" ), dname ); if( ! wxFileExists( dname ) ) msg << _( "It is missing.\n" ); else msg << _( "Perhaps a shared library (.dll or .so) file is missing.\n" ); msg << _( "From command line: argv[0]:\n'" ); msg << wxStandardPaths::Get().GetExecutablePath() << wxT( "'\n" ); // This is a fatal error, one from which we cannot recover, nor do we want // to protect against in client code which would require numerous noisy // tests in numerous places. So we inform the user that the installation // is bad. This exception will likely not get caught until way up in the // wxApp derivative, at which point the process will exit gracefully. THROW_IO_ERROR( msg ); } return NULL; } KIWAY::FACE_T KIWAY::KifaceType( FRAME_T aFrameType ) { switch( aFrameType ) { case FRAME_SCH: case FRAME_SCH_SYMBOL_EDITOR: case FRAME_SCH_VIEWER: case FRAME_SCH_VIEWER_MODAL: case FRAME_SIMULATOR: return FACE_SCH; case FRAME_PCB_EDITOR: case FRAME_FOOTPRINT_EDITOR: case FRAME_FOOTPRINT_VIEWER: case FRAME_FOOTPRINT_VIEWER_MODAL: case FRAME_FOOTPRINT_WIZARD: case FRAME_PCB_DISPLAY3D: return FACE_PCB; case FRAME_CVPCB: case FRAME_CVPCB_DISPLAY: return FACE_CVPCB; case FRAME_GERBER: return FACE_GERBVIEW; case FRAME_PL_EDITOR: return FACE_PL_EDITOR; case FRAME_CALC: return FACE_PCB_CALCULATOR; case FRAME_BM2CMP: return FACE_BMP2CMP; default: return FACE_T( -1 ); } } KIWAY_PLAYER* KIWAY::GetPlayerFrame( FRAME_T aFrameType ) { if( m_playerFrameName[aFrameType].IsEmpty() ) return NULL; return static_cast( wxWindow::FindWindowByName( m_playerFrameName[aFrameType] ) ); } KIWAY_PLAYER* KIWAY::Player( FRAME_T aFrameType, bool doCreate, wxTopLevelWindow* aParent ) { // Since this will be called from python, cannot assume that code will // not pass a bad aFrameType. if( (unsigned) aFrameType >= KIWAY_PLAYER_COUNT ) { // @todo : throw an exception here for python's benefit, at least that // way it gets some explanatory text. wxASSERT_MSG( 0, wxT( "caller has a bug, passed a bad aFrameType" ) ); return nullptr; } // return the previously opened window KIWAY_PLAYER* frame = GetPlayerFrame( aFrameType ); if( frame ) return frame; if( doCreate ) { try { FACE_T face_type = KifaceType( aFrameType ); KIFACE* kiface = KiFACE( face_type ); frame = (KIWAY_PLAYER*) kiface->CreateWindow( aParent, // Parent window of frame in modal mode, // NULL in non modal mode aFrameType, this, m_ctl // questionable need, these same flags // were passed to KIFACE::OnKifaceStart() ); m_playerFrameName[aFrameType] = frame->GetName(); return frame; } catch( const IO_ERROR& ioe ) { DisplayErrorMessage( nullptr, _( "Error loading editor" ), ioe.What() ); } catch( const std::exception& e) { DisplayErrorMessage( nullptr, _( "Error loading editor" ), e.what() ); } catch( ... ) { DisplayErrorMessage( nullptr, _( "Error loading editor" ) ); } } return nullptr; } bool KIWAY::PlayerClose( FRAME_T aFrameType, bool doForce ) { // Since this will be called from python, cannot assume that code will // not pass a bad aFrameType. if( (unsigned) aFrameType >= KIWAY_PLAYER_COUNT ) { // @todo : throw an exception here for python's benefit, at least that // way it gets some explanatory text. wxASSERT_MSG( 0, wxT( "caller has a bug, passed a bad aFrameType" ) ); return false; } KIWAY_PLAYER* frame = GetPlayerFrame( aFrameType ); if( frame == NULL ) // Already closed return true; if( frame->NonUserClose( doForce ) ) return true; return false; } bool KIWAY::PlayersClose( bool doForce ) { bool ret = true; for( unsigned i=0; i < KIWAY_PLAYER_COUNT; ++i ) { ret = ret && PlayerClose( FRAME_T( i ), doForce ); } return ret; } void KIWAY::ExpressMail( FRAME_T aDestination, MAIL_T aCommand, std::string& aPayload, wxWindow* aSource ) { KIWAY_EXPRESS mail( aDestination, aCommand, aPayload, aSource ); ProcessEvent( mail ); } void KIWAY::SetLanguage( int aLanguage ) { wxString errMsg; bool ret = false; { // Only allow the traces to be logged by wx. We use our own system to log when the // OS doesn't support the language, so we want to hide the wx error. WX_LOG_TRACE_ONLY logtraceOnly; Pgm().SetLanguageIdentifier( aLanguage ); ret = Pgm().SetLanguage( errMsg ); } if( !ret ) { wxString lang; for( unsigned ii = 0; LanguagesList[ii].m_KI_Lang_Identifier != 0; ii++ ) { if( aLanguage == LanguagesList[ii].m_KI_Lang_Identifier ) { if( LanguagesList[ii].m_DoNotTranslate ) lang = LanguagesList[ii].m_Lang_Label; else lang = wxGetTranslation( LanguagesList[ii].m_Lang_Label ); break; } } DisplayErrorMessage( nullptr, wxString::Format( _( "Unable to switch language to %s" ), lang ), errMsg ); return; } #if 1 // This is a risky hack that goes away if we allow the language to be // set only from the top most frame if !Kiface.IsSingle() // Only for the C++ project manager, and not for the python one and not for // single_top do we look for the EDA_BASE_FRAME as the top level window. // For single_top this is not needed because that window is registered in // the array below. if( m_ctl & KFCTL_CPP_PROJECT_SUITE ) { // A dynamic_cast could be better, but creates link issues // (some basic_frame functions not found) on some platforms, // so a static_cast is used. EDA_BASE_FRAME* top = static_cast( m_top ); if( top ) top->ShowChangedLanguage(); } #endif for( unsigned i=0; i < KIWAY_PLAYER_COUNT; ++i ) { KIWAY_PLAYER* frame = GetPlayerFrame( ( FRAME_T )i ); if( frame ) { frame->ShowChangedLanguage(); } } } void KIWAY::CommonSettingsChanged( bool aEnvVarsChanged, bool aTextVarsChanged ) { if( m_ctl & KFCTL_CPP_PROJECT_SUITE ) { // A dynamic_cast could be better, but creates link issues // (some basic_frame functions not found) on some platforms, // so a static_cast is used. EDA_BASE_FRAME* top = static_cast( m_top ); if( top ) top->CommonSettingsChanged( aEnvVarsChanged, aTextVarsChanged ); } for( unsigned i=0; i < KIWAY_PLAYER_COUNT; ++i ) { KIWAY_PLAYER* frame = GetPlayerFrame( ( FRAME_T )i ); if( frame ) frame->CommonSettingsChanged( aEnvVarsChanged, aTextVarsChanged ); } } void KIWAY::ProjectChanged() { if( m_ctl & KFCTL_CPP_PROJECT_SUITE ) { // A dynamic_cast could be better, but creates link issues // (some basic_frame functions not found) on some platforms, // so a static_cast is used. EDA_BASE_FRAME* top = static_cast( m_top ); if( top ) top->ProjectChanged(); } for( unsigned i=0; i < KIWAY_PLAYER_COUNT; ++i ) { KIWAY_PLAYER* frame = GetPlayerFrame( ( FRAME_T )i ); if( frame ) frame->ProjectChanged(); } } bool KIWAY::ProcessEvent( wxEvent& aEvent ) { KIWAY_EXPRESS* mail = dynamic_cast( &aEvent ); if( mail ) { FRAME_T dest = mail->Dest(); // see if recipient is alive KIWAY_PLAYER* alive = Player( dest, false ); if( alive ) { #if 1 return alive->ProcessEvent( aEvent ); #else alive->KiwayMailIn( *mail ); return true; #endif } } return false; } void KIWAY::OnKiCadExit() { if( m_ctl & KFCTL_CPP_PROJECT_SUITE ) { // A dynamic_cast could be better, but creates link issues // (some basic_frame functions not found) on some platforms, // so a static_cast is used. EDA_BASE_FRAME* top = static_cast( m_top ); if( top ) top->Close( false ); } } void KIWAY::OnKiwayEnd() { for( KIFACE* i : m_kiface ) { if( i ) i->OnKifaceEnd(); } }