KiCad PCB EDA Suite
pcbnew_action_plugins.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) 2017-2021 KiCad Developers, see AUTHORS.txt for contributors.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, you may find one here:
18  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19  * or you may search the http://www.gnu.org website for the version 2 license,
20  * or you may write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22  */
23 
24 #include "pcbnew_action_plugins.h"
25 #include <bitmaps.h>
26 #include <board.h>
27 #include <footprint.h>
28 #include <track.h>
29 #include <zone.h>
30 #include <menus_helpers.h>
31 #include <pcbnew_settings.h>
32 #include <python_scripting.h>
33 #include <tool/action_menu.h>
34 #include <tool/action_toolbar.h>
35 
37 {
38  PyLOCK lock;
39 
40  m_PyAction = aAction;
41  Py_XINCREF( aAction );
42 }
43 
44 
46 {
47  PyLOCK lock;
48 
49  Py_XDECREF( m_PyAction );
50 }
51 
52 
53 PyObject* PYTHON_ACTION_PLUGIN::CallMethod( const char* aMethod, PyObject* aArglist )
54 {
55  PyLOCK lock;
56 
57  PyErr_Clear();
58  // pFunc is a new reference to the desired method
59  PyObject* pFunc = PyObject_GetAttrString( m_PyAction, aMethod );
60 
61  if( pFunc && PyCallable_Check( pFunc ) )
62  {
63  PyObject* result = PyObject_CallObject( pFunc, aArglist );
64 
65  if( PyErr_Occurred() )
66  {
67  wxMessageBox( PyErrStringWithTraceback(),
68  _( "Exception on python action plugin code" ),
69  wxICON_ERROR | wxOK );
70  }
71 
72  if( result )
73  {
74  Py_XDECREF( pFunc );
75  return result;
76  }
77  }
78  else
79  {
80  wxString msg = wxString::Format( _( "Method \"%s\" not found, or not callable" ), aMethod );
81  wxMessageBox( msg, _( "Unknown Method" ), wxICON_ERROR | wxOK );
82  }
83 
84  if( pFunc )
85  {
86  Py_XDECREF( pFunc );
87  }
88 
89  return NULL;
90 }
91 
92 
93 wxString PYTHON_ACTION_PLUGIN::CallRetStrMethod( const char* aMethod, PyObject* aArglist )
94 {
95  wxString ret;
96  PyLOCK lock;
97 
98  PyObject* result = CallMethod( aMethod, aArglist );
99 
100  ret = PyStringToWx( result );
101  Py_XDECREF( result );
102 
103  return ret;
104 }
105 
106 
108 {
109  PyLOCK lock;
110 
111  return CallRetStrMethod( "GetCategoryName" );
112 }
113 
114 
116 {
117  PyLOCK lock;
118 
119  return CallRetStrMethod( "GetName" );
120 }
121 
122 
124 {
125  PyLOCK lock;
126 
127  return CallRetStrMethod( "GetDescription" );
128 }
129 
130 
132 {
133  PyLOCK lock;
134 
135  PyObject* result = CallMethod( "GetShowToolbarButton");
136 
137  return PyObject_IsTrue(result);
138 }
139 
140 
142 {
143  PyLOCK lock;
144 
145  return CallRetStrMethod( "GetIconFileName" );
146 }
147 
148 
150 {
151  PyLOCK lock;
152 
153  return CallRetStrMethod( "GetPluginPath" );
154 }
155 
156 
158 {
159  PyLOCK lock;
160 
161  CallMethod( "Run" );
162 }
163 
164 
166 {
167  return (void*) m_PyAction;
168 }
169 
170 
171 void PYTHON_ACTION_PLUGINS::register_action( PyObject* aPyAction )
172 {
173  PYTHON_ACTION_PLUGIN* fw = new PYTHON_ACTION_PLUGIN( aPyAction );
174 
175  fw->register_action();
176 }
177 
178 
179 void PYTHON_ACTION_PLUGINS::deregister_action( PyObject* aPyAction )
180 {
181  // deregister also destroys the previously created "PYTHON_ACTION_PLUGIN object"
182  ACTION_PLUGINS::deregister_object( (void*) aPyAction );
183 }
184 
185 
186 #if defined(KICAD_SCRIPTING) && defined(KICAD_SCRIPTING_ACTION_MENU)
187 
188 void PCB_EDIT_FRAME::OnActionPluginMenu( wxCommandEvent& aEvent )
189 {
190  ACTION_PLUGIN* actionPlugin = ACTION_PLUGINS::GetActionByMenu( aEvent.GetId() );
191 
192  if( actionPlugin )
193  RunActionPlugin( actionPlugin );
194 }
195 
196 void PCB_EDIT_FRAME::OnActionPluginButton( wxCommandEvent& aEvent )
197 {
198  ACTION_PLUGIN* actionPlugin = ACTION_PLUGINS::GetActionByButton( aEvent.GetId() );
199 
200  if( actionPlugin )
201  RunActionPlugin( actionPlugin );
202 }
203 
204 void PCB_EDIT_FRAME::RunActionPlugin( ACTION_PLUGIN* aActionPlugin )
205 {
206 
207  PICKED_ITEMS_LIST itemsList;
208  BOARD* currentPcb = GetBoard();
209  bool fromEmpty = false;
210 
211  // Append tracks:
212  for( TRACK* item : currentPcb->Tracks() )
213  {
214  ITEM_PICKER picker( nullptr, item, UNDO_REDO::CHANGED );
215  itemsList.PushItem( picker );
216  }
217 
218  // Append footprints:
219  for( FOOTPRINT* item : currentPcb->Footprints() )
220  {
221  ITEM_PICKER picker( nullptr, item, UNDO_REDO::CHANGED );
222  itemsList.PushItem( picker );
223  }
224 
225  // Append drawings
226  for( BOARD_ITEM* item : currentPcb->Drawings() )
227  {
228  ITEM_PICKER picker( nullptr, item, UNDO_REDO::CHANGED );
229  itemsList.PushItem( picker );
230  }
231 
232  // Append zones outlines
233  for( ZONE* zone : currentPcb->Zones() )
234  {
235  ITEM_PICKER picker( nullptr, zone, UNDO_REDO::CHANGED );
236  itemsList.PushItem( picker );
237  }
238 
239  if( itemsList.GetCount() > 0 )
241  else
242  fromEmpty = true;
243 
244  itemsList.ClearItemsList();
245 
246  // Execute plugin itself...
248  aActionPlugin->Run();
250 
251  // Get back the undo buffer to fix some modifications
252  PICKED_ITEMS_LIST* oldBuffer = NULL;
253 
254  if( fromEmpty )
255  {
256  oldBuffer = new PICKED_ITEMS_LIST();
257  }
258  else
259  {
260  oldBuffer = PopCommandFromUndoList();
261  wxASSERT( oldBuffer );
262  }
263 
264  // Try do discover what was modified
265  PICKED_ITEMS_LIST deletedItemsList;
266 
267  // The list of existing items after running the action script
268  std::set<BOARD_ITEM*> currItemList;
269 
270  // Append tracks:
271  for( TRACK* item : currentPcb->Tracks() )
272  currItemList.insert( item );
273 
274  // Append footprints:
275  for( FOOTPRINT* item : currentPcb->Footprints() )
276  currItemList.insert( item );
277 
278  // Append drawings
279  for( BOARD_ITEM* item : currentPcb->Drawings() )
280  currItemList.insert( item );
281 
282  // Append zones outlines
283  for( ZONE* zone : currentPcb->Zones() )
284  currItemList.insert( zone );
285 
286  // Found deleted footprints
287  for( unsigned int i = 0; i < oldBuffer->GetCount(); i++ )
288  {
289  BOARD_ITEM* item = (BOARD_ITEM*) oldBuffer->GetPickedItem( i );
290  ITEM_PICKER picker( nullptr, item, UNDO_REDO::DELETED );
291 
292  wxASSERT( item );
293 
294  if( currItemList.find( item ) == currItemList.end() )
295  deletedItemsList.PushItem( picker );
296  }
297 
298  // Mark deleted elements in undolist
299  for( unsigned int i = 0; i < deletedItemsList.GetCount(); i++ )
300  {
301  oldBuffer->PushItem( deletedItemsList.GetItemWrapper( i ) );
302  }
303 
304  // Find new footprints
305  for( FOOTPRINT* item : currentPcb->Footprints() )
306  {
307  if( !oldBuffer->ContainsItem( item ) )
308  {
309  ITEM_PICKER picker( nullptr, item, UNDO_REDO::NEWITEM );
310  oldBuffer->PushItem( picker );
311  }
312  }
313 
314  for( TRACK* item : currentPcb->Tracks() )
315  {
316  if( !oldBuffer->ContainsItem( item ) )
317  {
318  ITEM_PICKER picker( nullptr, item, UNDO_REDO::NEWITEM );
319  oldBuffer->PushItem( picker );
320  }
321  }
322 
323  for( BOARD_ITEM* item : currentPcb->Drawings() )
324  {
325  if( !oldBuffer->ContainsItem( item ) )
326  {
327  ITEM_PICKER picker( nullptr, item, UNDO_REDO::NEWITEM );
328  oldBuffer->PushItem( picker );
329  }
330  }
331 
332  for( ZONE* zone : currentPcb->Zones() )
333  {
334  if( !oldBuffer->ContainsItem( zone ) )
335  {
336  ITEM_PICKER picker( nullptr, zone, UNDO_REDO::NEWITEM );
337  oldBuffer->PushItem( picker );
338  }
339  }
340 
341  if( oldBuffer->GetCount() )
342  {
343  OnModify();
344  PushCommandToUndoList( oldBuffer );
345  }
346  else
347  {
348  delete oldBuffer;
349  }
350 
352 }
353 
354 
355 void PCB_EDIT_FRAME::buildActionPluginMenus( ACTION_MENU* actionMenu )
356 {
357  if( !actionMenu ) // Should not occur.
358  return;
359 
360  for( int ii = 0; ii < ACTION_PLUGINS::GetActionsCount(); ii++ )
361  {
362  wxMenuItem* item;
364  const wxBitmap& bitmap = ap->iconBitmap.IsOk() ? ap->iconBitmap : KiBitmap( puzzle_piece_xpm );
365 
366  item = AddMenuItem( actionMenu, wxID_ANY, ap->GetName(), ap->GetDescription(), bitmap );
367 
368  Connect( item->GetId(), wxEVT_COMMAND_MENU_SELECTED,
369  wxCommandEventHandler( PCB_EDIT_FRAME::OnActionPluginMenu ) );
370 
371  ACTION_PLUGINS::SetActionMenu( ii, item->GetId() );
372  }
373 }
374 
375 
376 void PCB_EDIT_FRAME::AddActionPluginTools()
377 {
378  bool need_separator = true;
379  const std::vector<ACTION_PLUGIN*>& orderedPlugins = GetOrderedActionPlugins();
380 
381  for( ACTION_PLUGIN* ap : orderedPlugins )
382  {
383  if( GetActionPluginButtonVisible( ap->GetPluginPath(), ap->GetShowToolbarButton() ) )
384  {
385  if( need_separator )
386  {
388  need_separator = false;
389  }
390 
391  // Add button
392  wxBitmap bitmap;
393 
394  if ( ap->iconBitmap.IsOk() )
395  bitmap = KiScaledBitmap( ap->iconBitmap, this );
396  else
397  bitmap = KiScaledBitmap( puzzle_piece_xpm, this );
398 
399  wxAuiToolBarItem* button = m_mainToolBar->AddTool(
400  wxID_ANY, wxEmptyString, bitmap, ap->GetName() );
401 
402  Connect( button->GetId(), wxEVT_COMMAND_MENU_SELECTED,
403  wxCommandEventHandler( PCB_EDIT_FRAME::OnActionPluginButton ) );
404 
405  // Link action plugin to button
406  ACTION_PLUGINS::SetActionButton( ap, button->GetId() );
407  }
408  }
409 }
410 
411 
412 std::vector<ACTION_PLUGIN*> PCB_EDIT_FRAME::GetOrderedActionPlugins()
413 {
414  std::vector<ACTION_PLUGIN*> plugins;
415  std::vector<ACTION_PLUGIN*> orderedPlugins;
416 
417  for( int i = 0; i < ACTION_PLUGINS::GetActionsCount(); i++ )
418  plugins.push_back( ACTION_PLUGINS::GetAction( i ) );
419 
420  // First add plugins that have entries in settings
421  for( const auto& pair : m_settings->m_VisibleActionPlugins )
422  {
423  auto loc = std::find_if( plugins.begin(), plugins.end(),
424  [pair] ( ACTION_PLUGIN* plugin )
425  {
426  return plugin->GetPluginPath() == pair.first;
427  } );
428 
429  if( loc != plugins.end() )
430  {
431  orderedPlugins.push_back( *loc );
432  plugins.erase( loc );
433  }
434  }
435 
436  // Now append new plugins that have not been configured yet
437  for( auto remaining_plugin : plugins )
438  orderedPlugins.push_back( remaining_plugin );
439 
440  return orderedPlugins;
441 }
442 
443 
444 bool PCB_EDIT_FRAME::GetActionPluginButtonVisible( const wxString& aPluginPath, bool aPluginDefault )
445 {
446  auto& settings = m_settings->m_VisibleActionPlugins;
447 
448  for( const auto& entry : settings )
449  {
450  if( entry.first == aPluginPath )
451  return entry.second;
452  }
453 
454  // Plugin is not in settings, return default.
455  return aPluginDefault;
456 }
457 
458 
459 #endif
virtual wxString GetName()=0
Function GetName.
wxString PyStringToWx(PyObject *aString)
void OnModify() override
Must be called after a board change to set the modified flag.
ACTION_PLUGIN This is the parent class from where any action plugin class must derive.
Definition: action_plugin.h:40
wxString GetName() override
Function GetName.
wxString CallRetStrMethod(const char *aMethod, PyObject *aArglist=NULL)
void SaveCopyInUndoList(EDA_ITEM *aItemToCopy, UNDO_REDO aTypeCommand) override
Create a new entry in undo list of commands.
Definition: undo_redo.cpp:177
ZONES & Zones()
Definition: board.h:309
Defines the structure of a menu based on ACTIONs.
Definition: action_menu.h:45
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:82
virtual bool GetShowToolbarButton()=0
Function GetShowToolbarButton.
wxMenuItem * AddMenuItem(wxMenu *aMenu, int aId, const wxString &aText, const wxBitmap &aImage, wxItemKind aType=wxITEM_NORMAL)
Create and insert a menu item with an icon into aMenu.
Definition: bitmap.cpp:221
void AddScaledSeparator(wxWindow *aWindow)
Add a separator that introduces space on either side to not squash the tools when scaled.
void ActivateGalCanvas() override
Set the #m_Pcb member in such as way as to ensure deleting any previous BOARD.
virtual wxString GetDescription()=0
Function GetDescription.
wxBitmap iconBitmap
Definition: action_plugin.h:50
virtual void PushCommandToUndoList(PICKED_ITEMS_LIST *aItem)
Add a command to undo in the undo list.
void PushItem(const ITEM_PICKER &aItem)
Push aItem to the top of the list.
ACTION_TOOLBAR * m_mainToolBar
const BITMAP_OPAQUE puzzle_piece_xpm[1]
void register_action()
Function register_action It's the standard method of a "ACTION_PLUGIN" to register itself into the AC...
PCBNEW_SETTINGS * m_settings
wxBitmap KiScaledBitmap(BITMAP_DEF aBitmap, wxWindow *aWindow)
Construct a wxBitmap from a memory record, scaling it if device DPI demands it.
Definition: bitmap.cpp:118
unsigned GetCount() const
virtual wxString GetPluginPath()=0
Function GetPluginPath.
wxBitmap KiBitmap(BITMAP_DEF aBitmap)
Construct a wxBitmap from a memory record, held in a BITMAP_DEF.
Definition: bitmap.cpp:82
static void deregister_action(PyObject *aPyAction)
void * GetObject() override
Function GetObject This method gets the pointer to the object from where this action constructs.
#define NULL
wxString GetDescription() override
Function GetDescription.
virtual void Run()=0
Function Run This method the the action.
static ACTION_PLUGIN * GetActionByMenu(int aMenu)
Function GetActionByMenu find action plugin associated to a menu id.
wxString GetPluginPath() override
Function GetPluginPath.
FOOTPRINTS & Footprints()
Definition: board.h:303
void Run() override
Function Run This method the the action.
static int GetActionsCount()
Function GetActionsCount.
EDA_ITEM * GetPickedItem(unsigned int aIdx) const
virtual PICKED_ITEMS_LIST * PopCommandFromUndoList()
Return the last command to undo and remove it from list, nothing is deleted.
ZONE handles a list of polygons defining a copper zone.
Definition: zone.h:57
PYTHON_ACTION_PLUGIN(PyObject *action)
wxString GetIconFileName() override
Function GetIconFileName.
A holder to handle information on schematic or board items.
bool GetShowToolbarButton() override
Function GetShowToolbarButton.
PyObject * CallMethod(const char *aMethod, PyObject *aArglist=NULL)
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
ITEM_PICKER GetItemWrapper(unsigned int aIdx) const
static void SetActionButton(ACTION_PLUGIN *aAction, int idButton)
Function SetActionButton Associate a button id to an action plugin.
static ACTION_PLUGIN * GetAction(const wxString &aName)
Function GetAction.
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:190
static void SetActionMenu(int aIndex, int idMenu)
Function SetActionMenu Associate a menu id to an action plugin.
#define _(s)
Definition: 3d_actions.cpp:33
Class PCBNEW_ACTION_PLUGINS.
wxString PyErrStringWithTraceback()
void ClearItemsList()
Delete only the list of pickers NOT the picked data itself.
bool ContainsItem(const EDA_ITEM *aItem) const
BOARD * GetBoard() const
static void register_action(PyObject *aPyAction)
static bool deregister_object(void *aObject)
Function deregister_object Anyone calls this method to deregister an object which builds a action,...
static ACTION_PLUGIN * GetActionByButton(int aButton)
Function GetActionByButton find action plugin associated to a button id.
DRAWINGS & Drawings()
Definition: board.h:306
static void SetActionRunning(bool aRunning)
Function SetActionRunning.
TRACKS & Tracks()
Definition: board.h:300
Definition: track.h:83
wxString GetCategoryName() override
Function GetCategoryName.