/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2014 SoftPLC Corporation, Dick Hollenbeck * Copyright (C) 2016-2020 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 */ #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. * *
  3. 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.
  4. * *
  5. 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.
  6. * * *
* * 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 #include #define KIFACE_VERSION 1 #define KIFACE_GETTER KIFACE_1 // The KIFACE acquisition 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; class TOOL_ACTION; /** * Implement 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) ///< Running as a standalone Top. #define KFCTL_CPP_PROJECT_SUITE (1<<1) ///< Running under C++ project mgr, possibly with others. /** * 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 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. */ virtual bool OnKifaceStart( PGM_BASE* aProgram, int aCtlBits ) = 0; /** * 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. */ virtual void OnKifaceEnd() = 0; /** * Create 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 the window created 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. */ virtual wxWindow* CreateWindow( wxWindow* aParent, int aClassId, KIWAY* aKIWAY, int aCtlBits = 0 ) = 0; /** * 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). */ virtual 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.... } /** * Return 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 the requested object which must be cast into the known type. */ virtual void* IfaceOrAddress( int aDataId ) = 0; /** * Append this Kiface's registered actions to the given list. */ virtual void GetActions( std::vector& aActions ) const = 0; }; /** * 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, FACE_PYTHON, KIWAY_FACE_COUNT }; ~KIWAY() throw () {} /** * A simple mapping function which returns the FACE_T which is known to implement * @a aFrameType. * * @return a valid value #KIWAY::FACE_T or FACE_T(-1) if given a bad @a aFrameType. */ static FACE_T KifaceType( FRAME_T aFrameType ); // If you change the vtable, recompile all of KiCad. /** * Return the KIFACE* given a FACE_T. * * If it is not already loaded, the KIFACE is loaded and initialized with a call to * KIFACE::OnKifaceStart(). */ virtual KIFACE* KiFACE( FACE_T aFaceId, bool doLoad = true ); /** * Return 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 * * @return 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. */ virtual KIWAY_PLAYER* Player( FRAME_T aFrameType, bool doCreate = true, wxTopLevelWindow* aParent = nullptr ); /** * Call 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 true if the window is closed and not vetoed, else false. */ virtual bool PlayerClose( FRAME_T aFrameType, bool doForce ); /** * Call 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 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. */ virtual bool PlayersClose( bool doForce ); /** * Send @a aPayload to @a aDestination from @a aSource. * * The recipient receives this in its #KIWAY_PLAYER::KiwayMailIn() function and can * efficiently switch() based on @a aCommand in there. */ virtual void ExpressMail( FRAME_T aDestination, MAIL_T aCommand, std::string& aPayload, wxWindow* aSource = nullptr ); /** * Append all registered actions to the given list. */ virtual void GetActions( std::vector& aActions ) const; /** * Return 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. */ virtual PROJECT& Prj() const; /** * Change the language and then calls ShowChangedLanguage() on all #KIWAY_PLAYERs. */ virtual void SetLanguage( int aLanguage ); /** * Call CommonSettingsChanged() on all KIWAY_PLAYERs. * * Use after changing suite-wide options such as panning, autosave interval, etc. */ virtual void CommonSettingsChanged( bool aEnvVarsChanged, bool aTextVarsChanged ); /** * Calls ProjectChanged() on all KIWAY_PLAYERs. * Used after changing the project to ensure all players are updated correctly. */ virtual void ProjectChanged(); KIWAY( PGM_BASE* aProgram, int aCtlBits, wxFrame* aTop = nullptr ); /** * Tell 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 ); wxFrame* GetTop() { return m_top; } void OnKiCadExit(); void OnKiwayEnd(); bool ProcessEvent( wxEvent& aEvent ) override; /** * Gets the window pointer to the blocking dialog (to send it signals) * @return Pointer to blocking dialog window or null if none */ wxWindow* GetBlockingDialog(); void SetBlockingDialog( wxWindow* aWin ); 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 @a 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 wxWindowID m_blockingDialog; // An array to store the window ID of PLAYER frames which were run. // A non empty name means only a PLAYER was run at least one time. // Empty entries are represented by wxID_NONE. // They can be closed, and the stored window ID may be invalid. // Call: wxWindow::FindWindowById( m_playerFrameId[aFrameType] ) // to know if still exists (or GetPlayerFrame( FRAME_T aFrameType ) std::atomic m_playerFrameId[KIWAY_PLAYER_COUNT]; }; #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 /** * Point 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 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_