/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2014 SoftPLC Corporation, Dick Hollenbeck * Copyright (C) 2016 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 */ #ifndef KIWAY_H_ #define KIWAY_H_ /* The KIWAY and KIFACE classes are used to communicate between various process modules, all residing within a single process. The program modules are either top level like an *.exe or subsidiary like a *.dll. In much of the documentation the term DSO is used to refer to the *.dll portions, that is the term used on linux. But it should be taken to mean DLL on Windows.

These are a couple of reasons why this design was chosen:

  1. By using DSOs within a single process, it is not necessary to use IPC. The DSOs can send wxEvents between themselves using wxEvtHandler interfaces in a platform independent way. There can also be function calls from one DSO to another.
  2. The use of a number of separately linked DSOs closely resembles the original KiCad program design, consisting of Eeschema and Pcbnew. But it also allows separate compilation and linking of those two DSOs without a ton of inter-DSO dependencies and common data structures. Linking smaller, purpose specific DSOs is thought to be better for maintenance simplicity than a large single link image.
  3. By keeping the core functionality in DSOs rather than EXE tops, it becomes possible to re-use the DSOs under different program tops. For example, a DSO named _pcbnew.so can be used under a C++ top or under a python top. Only one CMake target must be defined to build either. Whether that is a separate build or not is not the important thing. Simply having a single CMake target has advantages. (Each builder person will have his/her own intentions relative to use of python or not.) Once a DSO is python capable, it can be driven by any number of python program tops, including demo-ing (automation) and testing separately.
All KiCad source code is UTF8 encoded by law, so make sure your editor is set as such! As such, it is OK to use UTF8 characters: ┏ ┗ ┓ ┛ ━ ┃

                             ┏━━━process top━━━━━┓
                             ┃                   ┃       wxEvent channels
         ┏━━━━━━━━━━━━━━━━━━━-━[KIWAY project 1]━-━━━━━━━━━━━━━━━━━━━━━━┓
         ┃                   ┃                   ┃                      ┃
         ┃     ┏━━━━━━━━━━━━━-━[KIWAY project 2]━-━━━━━━━━━━┓           ┃
         ┃     ┃             ┃                   ┃          ┃           ┃
         ┃     ┃           ┏━-━[KIWAY project 3]━-━┓        ┃           ┃
         ┃     ┃           ┃ ┗━━━━━━━━━━━━━━━━━━━┛ ┃        ┃           ┃
         ┃     ┃           ┃                       ┃        ┃           ┃
         ┃     ┃           ┃                       ┃        ┃           ┃
┏━━━━━━━━|━━━━━|━━━━━━━━━━━|━━━━━━━━━┓    ┏━━━━━━━━|━━━━━━━━|━━━━━━━━━━━|━━━━━┓
┃ KIFACE ┃     ┃           ┃         ┃    ┃ KIFACE ┃        ┃           ┃     ┃
┃        ┃     ┃           ┃         ┃    ┃        ┃        ┃           ┃     ┃
┃        ┃     ┃           ┃         ┃    ┃        ┃        ┃           ┃     ┃
┃┏━━━━━━━+━┓ ┏━+━━━━━━━┓ ┏━+━━━━━━━┓ ┃    ┃┏━━━━━━━+━┓ ┏━━━━+━━━━┓ ┏━━━━+━━━━┓┃
┃┃wxFrame  ┃ ┃wxFrame  ┃ ┃wxFrame  ┃ ┃    ┃┃wxFrame  ┃ ┃wxFrame  ┃ ┃wxFrame  ┃┃
┃┃project 1┃ ┃project 2┃ ┃project 3┃ ┃    ┃┃project 3┃ ┃project 2┃ ┃project 1┃┃
┃┗━━━━━━━━━┛ ┗━━━━━━━━━┛ ┗━━━━━━━━━┛ ┃    ┃┗━━━━━━━━━┛ ┗━━━━━━━━━┛ ┗━━━━━━━━━┛┃
┃                                    ┃    ┃                                   ┃
┃                                    ┃    ┃                                   ┃
┗━━━━━━ eeschema DSO ━━━━━━━━━━━━━━━━┛    ┗━━━━━━ pcbnew DSO ━━━━━━━━━━━━━━━━━┛

*/ #include #include #include #include #include #include #include #include #define VTBL_ENTRY virtual #define KIFACE_VERSION 1 #define KIFACE_GETTER KIFACE_1 // The KIFACE acquistion function is declared extern "C" so its name should not // be mangled. #define KIFACE_INSTANCE_NAME_AND_VERSION "KIFACE_1" #ifndef SWIG #if defined(__linux__) || defined(__FreeBSD__) #define LIB_ENV_VAR wxT( "LD_LIBRARY_PATH" ) #elif defined(__WXMAC__) #define LIB_ENV_VAR wxT( "DYLD_LIBRARY_PATH" ) #elif defined(_WIN32) #define LIB_ENV_VAR wxT( "PATH" ) #else #error Platform support missing #endif #endif // SWIG class wxConfigBase; class wxWindow; class PGM_BASE; class KIWAY; class KIWAY_PLAYER; class wxTopLevelWindow; /** * KIFACE * is used by a participant in the KIWAY alchemy. KIWAY is a minimalistic * software bus for communications between various DLLs/DSOs (DSOs) within the same * KiCad process. It makes it possible to call between DSOs without having to link * them together. Most all calls are via virtual functions which means C++ vtables * are used to hold function pointers and eliminate the need to link to specific * object code libraries. There is one KIWAY in the launching portion of the process * for each open KiCad project. Each project has its own KIWAY. Within a KIWAY * is an actual PROJECT data structure. A KIWAY also facilitates communicating * between DSOs on the topic of the project in question. */ struct KIFACE { // The order of functions establishes the vtable sequence, do not change the // order of functions in this listing unless you recompile all clients of // this interface. virtual ~KIFACE() throw() {} #define KFCTL_STANDALONE (1<<0) ///< Am running as a standalone Top. #define KFCTL_CPP_PROJECT_SUITE (1<<1) ///< Am running under C++ project mgr, possibly with others #define KFCTL_PY_PROJECT_SUITE (1<<2) ///< Am running under python project mgr, possibly with others /** * Function OnKifaceStart * is called just once shortly after the DSO is loaded. It is the second * function called, immediately after the KIFACE_GETTER(). However before * either of those, static C++ constructors are called. The DSO implementation * should do process level initialization here, not project specific since there * will be multiple projects open eventually. * * @param aProgram is the process block: PGM_BASE* * * @param aCtlBits consists of bit flags from the set of KFCTL_* \#defines above. * * @return bool - true if DSO initialized OK, false if not. When returning * false, the loader may optionally decide to terminate the process or not, * but will not put out any UI because that is the duty of this function to say * why it is returning false. Never return false without having reported * to the UI why. */ VTBL_ENTRY bool OnKifaceStart( PGM_BASE* aProgram, int aCtlBits ) = 0; /** * Function OnKifaceEnd * is called just once just before the DSO is to be unloaded. It is called * before static C++ destructors are called. A default implementation is supplied. */ VTBL_ENTRY void OnKifaceEnd() = 0; /** * Function CreateWindow * creates a wxWindow for the current project. The caller * must cast the return value into the known type. * * @param aParent may be NULL, or is otherwise the parent to connect under. If NULL * then caller may want to connect the returned wxWindow into some hierarchy after * this function returns. * * @param aClassId identifies which wxFrame or wxDialog to retrieve, using a value * known to the implementing KIFACE. * * @param aKIWAY tells the window which KIWAY (and PROJECT) it is a participant in. * * @param aCtlBits consists of bit flags from the set of KFCTL_* \#defines above. * * @return wxWindow* - and if not NULL, should be cast into the known type using * and old school cast. dynamic_cast is problematic since it needs typeinfo probably * not contained in the caller's link image. */ VTBL_ENTRY wxWindow* CreateWindow( wxWindow* aParent, int aClassId, KIWAY* aKIWAY, int aCtlBits = 0 ) = 0; /** * Function SaveFileAs * Saving a file under a different name is delegated to the various KIFACEs because * the project doesn't know the internal format of the various files (which may have * paths in them that need updating). */ VTBL_ENTRY void SaveFileAs( const wxString& srcProjectBasePath, const wxString& srcProjectName, const wxString& newProjectBasePath, const wxString& newProjectName, const wxString& srcFilePath, wxString& aErrors ) { // If a KIFACE owns files then it needs to implement this.... } /** * Function IfaceOrAddress * returns a pointer to the requested object. The safest way to use this * is to retrieve a pointer to a static instance of an interface, similar to * how the KIFACE interface is exported. But if you know what you are doing * use it to retrieve anything you want. Segfaults are your fault. * * @param aDataId identifies which object you want the address of, and consists * of choices known in advance by the implementing KIFACE. * * @return void* - and must be cast into the known type. */ VTBL_ENTRY void* IfaceOrAddress( int aDataId ) = 0; }; /** * KIWAY * is a minimalistic software bus for communications between various * DLLs/DSOs (DSOs) within the same KiCad process. It makes it possible * to call between DSOs without having to link them together, and without * having to link to the top process module which houses the KIWAY(s). More importantly * it makes it possible to send custom wxEvents between DSOs and from the top * process module down into the DSOs. The latter capability is thought useful * for driving the lower DSOs from a python test rig or for demo (automation) purposes. *

* Most all calls are via virtual functions, which means C++ vtables * are used to hold function pointers and eliminate the need to link to specific * object code libraries, speeding development and encouraging clearly defined * interface design. Unlike Microsoft COM, which is a multi-vendor design supporting * DLL's built at various points in time, the KIWAY alchemy is single project, with * all components being built at the same time. So one should expect solid compatibility * between all KiCad components, as long at they are compiled at the same time. *

* There is one KIWAY in the launching portion of the process * for each open KiCad project. Each project has its own KIWAY. Available to * each KIWAY is an actual PROJECT data structure. If you have a KIWAY, you * can get to the PROJECT using KIWAY::Prj(). *

* In summary, a KIWAY facilitates communicating between DSOs, where the topic * of the communication is project specific. Here a "project" means a BOARD * and a SCHEMATIC and a NETLIST, (anything relating to production of a single BOARD * and added to class PROJECT.) */ class KIWAY : public wxEvtHandler { friend struct PGM_SINGLE_TOP; // can use set_kiface() public: /// Known KIFACE implementations enum FACE_T { FACE_SCH, ///< eeschema DSO FACE_PCB, ///< pcbnew DSO FACE_CVPCB, FACE_GERBVIEW, FACE_PL_EDITOR, FACE_PCB_CALCULATOR, FACE_BMP2CMP, KIWAY_FACE_COUNT }; ~KIWAY() throw () {} /** * Function KifaceType * is a simple mapping function which returns the FACE_T which is known to * implement @a aFrameType. * * @return KIWAY::FACE_T - a valid value or FACE_T(-1) if given a bad aFrameType. */ static FACE_T KifaceType( FRAME_T aFrameType ); // If you change the vtable, recompile all of KiCad. /** * Function KiFACE * returns the KIFACE* given a FACE_T. If it is not already loaded, the * KIFACE is loaded and initialized with a call to KIFACE::OnKifaceStart() */ VTBL_ENTRY KIFACE* KiFACE( FACE_T aFaceId, bool doLoad = true ); /** * Function Player * returns the KIWAY_PLAYER* given a FRAME_T. If it is not already created, * the required KIFACE is found and loaded and initialized if necessary, then * the KIWAY_PLAYER window is created but not shown. Caller must Show() it. * If it is already created, then the existing KIWAY_PLAYER* pointer is returned. * * @param aFrameType is from enum #FRAME_T. * @param doCreate when true asks that the player be created if it is not * already created, false means do not create and maybe return NULL. * @param aParent is a parent for modal KIWAY_PLAYER frames, otherwise NULL * used only when doCreate = true and by KIWAY_PLAYER frames created in modal form * because the are using the wxFLOAT_ON_PARENT style * * @return KIWAY_PLAYER* - a valid opened KIWAY_PLAYER or NULL if there * is something wrong or doCreate was false and the player has yet to be created. * * @throw IO_ERROR if the *.kiface file could not be found, filled with text saying what. */ VTBL_ENTRY KIWAY_PLAYER* Player( FRAME_T aFrameType, bool doCreate = true, wxTopLevelWindow* aParent = NULL ); /** * Function PlayerClose * calls the KIWAY_PLAYER::Close( bool force ) function on the window and * if not vetoed, returns true, else false. If window actually closes, then * this KIWAY marks it as not opened internally. * * @return bool - true the window is closed and not vetoed, else false. */ VTBL_ENTRY bool PlayerClose( FRAME_T aFrameType, bool doForce ); /** * Function PlayersClose * calls the KIWAY_PLAYER::Close( bool force ) function on all the windows and * if none are vetoed, returns true, else false. If any window actually closes, then * this KIWAY marks it as not opened internally. * * @return bool - true indicates that all windows closed because none were vetoed, * false means at least one cast a veto. Any that cast a veto are still open. */ VTBL_ENTRY bool PlayersClose( bool doForce ); /** * Function ExpressMail * send aPayload to aDestination from aSource. Recipient receives this in its * KIWAY_PLAYER::KiwayMailIn() function and can efficiently switch() based on * aCommand in there. */ VTBL_ENTRY void ExpressMail( FRAME_T aDestination, MAIL_T aCommand, std::string& aPayload, wxWindow* aSource = NULL ); /** * Function Prj * returns the PROJECT associated with this KIWAY. This is here as an * accessor, so that there is freedom to put the actual PROJECT storage * in a place decided by the implementation, and not known to the caller. */ VTBL_ENTRY PROJECT& Prj() const; /** * Function SetLanguage * changes the language and then calls ShowChangedLanguage() on all KIWAY_PLAYERs. */ VTBL_ENTRY void SetLanguage( int aLanguage ); /** * Function CommonSettingsChanged * Calls CommonSettingsChanged() on all KIWAY_PLAYERs. * Used after changing suite-wide options such as panning, autosave interval, etc. */ VTBL_ENTRY void CommonSettingsChanged( bool aEnvVarsChanged, bool aTextVarsChanged ); KIWAY( PGM_BASE* aProgram, int aCtlBits, wxFrame* aTop = NULL ); /** * Function SetTop * tells this KIWAY about the top most frame in the program and optionally * allows it to play the role of one of the KIWAY_PLAYERs if launched from * single_top.cpp. * * @param aTop is the top most wxFrame in the entire program. */ void SetTop( wxFrame* aTop ); void OnKiCadExit(); void OnKiwayEnd(); bool ProcessEvent( wxEvent& aEvent ) override; private: /// Get the [path &] name of the DSO holding the requested FACE_T. const wxString dso_search_path( FACE_T aFaceId ); #if 0 /// hooked into m_top in SetTop(), marks child frame as closed. void player_destroy_handler( wxWindowDestroyEvent& event ); #endif bool set_kiface( FACE_T aFaceType, KIFACE* aKiface ) { if( (unsigned) aFaceType < (unsigned) KIWAY_FACE_COUNT ) { m_kiface[aFaceType] = aKiface; return true; } return false; } /** * @return the reference of the KIWAY_PLAYER having the type aFrameType * if exists, or NULL if this KIWAY_PLAYER was not yet created, or was closed */ KIWAY_PLAYER* GetPlayerFrame( FRAME_T aFrameType ); static KIFACE* m_kiface[KIWAY_FACE_COUNT]; static int m_kiface_version[KIWAY_FACE_COUNT]; PGM_BASE* m_program; int m_ctl; wxFrame* m_top; // Usually m_top is the Project manager // a string array ( size KIWAY_PLAYER_COUNT ) to Store the frame name // of PLAYER frames which were run. // A non empty name means only a PLAYER was run at least one time. // It can be closed. Call : // wxWindow::FindWindowByName( m_playerFrameName[aFrameType] ) // to know if still exists (or GetPlayerFrame( FRAME_T aFrameType ) wxArrayString m_playerFrameName; }; #ifndef SWIG // provided by single_top.cpp and kicad.cpp; extern KIWAY Kiway; // whereas python launchers: single_top.py and project manager instantiate as a python object #endif /** * Function Pointer KIFACE_GETTER_FUNC * points to the one and only KIFACE export. The export's address * is looked up via symbolic string and should be extern "C" to avoid name * mangling. This function will only be called one time. The DSO itself however * may be asked to support multiple Top windows, i.e. multiple projects * within its lifetime. * * @param aKIFACEversion is where to put the API version implemented by the KIFACE. * @param aKIWAYversion tells the KIFACE what KIWAY version will be available. * @param aProgram is a pointer to the PGM_BASE for this process. * @return KIFACE* - unconditionally, cannot fail. */ typedef KIFACE* KIFACE_GETTER_FUNC( int* aKIFACEversion, int aKIWAYversion, PGM_BASE* aProgram ); #ifndef SWIG /// No name mangling. Each KIFACE (DSO/DLL) will implement this once. extern "C" { #if defined(BUILD_KIWAY_DLL) MY_API( KIFACE* ) KIFACE_GETTER( int* aKIFACEversion, int aKIWAYversion, PGM_BASE* aProgram ); #else KIFACE* KIFACE_GETTER( int* aKIFACEversion, int aKIWAYversion, PGM_BASE* aProgram ); #endif } #endif // SWIG #endif // KIWAY_H_