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 <board_commit.h>
28 #include <footprint.h>
29 #include <pcb_track.h>
30 #include <zone.h>
31 #include <menus_helpers.h>
32 #include <pcbnew_settings.h>
33 #include <tool/action_menu.h>
34 #include <tool/action_toolbar.h>
35 #include <wx/msgdlg.h>
36 #include "../../scripting/python_scripting.h"
37 
39 {
40  PyLOCK lock;
41 
42  m_PyAction = aAction;
43  Py_XINCREF( aAction );
44 }
45 
46 
48 {
49  PyLOCK lock;
50 
51  Py_XDECREF( m_PyAction );
52 }
53 
54 
55 PyObject* PYTHON_ACTION_PLUGIN::CallMethod( const char* aMethod, PyObject* aArglist )
56 {
57  PyLOCK lock;
58 
59  PyErr_Clear();
60 
61  // pFunc is a new reference to the desired method
62  PyObject* pFunc = PyObject_GetAttrString( m_PyAction, aMethod );
63 
64  if( pFunc && PyCallable_Check( pFunc ) )
65  {
66  PyObject* result = PyObject_CallObject( pFunc, aArglist );
67 
68  if( PyErr_Occurred() )
69  {
70  wxMessageBox( PyErrStringWithTraceback(),
71  _( "Exception on python action plugin code" ),
72  wxICON_ERROR | wxOK );
73  }
74 
75  if( result )
76  {
77  Py_XDECREF( pFunc );
78  return result;
79  }
80  }
81  else
82  {
83  wxString msg = wxString::Format( _( "Method \"%s\" not found, or not callable" ), aMethod );
84  wxMessageBox( msg, _( "Unknown Method" ), wxICON_ERROR | wxOK );
85  }
86 
87  if( pFunc )
88  {
89  Py_XDECREF( pFunc );
90  }
91 
92  return nullptr;
93 }
94 
95 
96 wxString PYTHON_ACTION_PLUGIN::CallRetStrMethod( const char* aMethod, PyObject* aArglist )
97 {
98  wxString ret;
99  PyLOCK lock;
100 
101  PyObject* result = CallMethod( aMethod, aArglist );
102 
103  ret = PyStringToWx( result );
104  Py_XDECREF( result );
105 
106  return ret;
107 }
108 
109 
111 {
112  PyLOCK lock;
113 
114  return CallRetStrMethod( "GetCategoryName" );
115 }
116 
117 
119 {
120  PyLOCK lock;
121 
122  return CallRetStrMethod( "GetName" );
123 }
124 
125 
127 {
128  PyLOCK lock;
129 
130  return CallRetStrMethod( "GetDescription" );
131 }
132 
133 
135 {
136  PyLOCK lock;
137 
138  PyObject* result = CallMethod( "GetShowToolbarButton");
139 
140  return PyObject_IsTrue(result);
141 }
142 
143 
145 {
146  PyLOCK lock;
147 
148  PyObject* arglist = Py_BuildValue( "(i)", static_cast<int>( aDark ) );
149 
150  wxString result = CallRetStrMethod( "GetIconFileName", arglist );
151 
152  Py_DECREF( arglist );
153 
154  return result;
155 }
156 
157 
159 {
160  PyLOCK lock;
161 
162  return CallRetStrMethod( "GetPluginPath" );
163 }
164 
165 
167 {
168  PyLOCK lock;
169 
170  CallMethod( "Run" );
171 }
172 
173 
175 {
176  return (void*) m_PyAction;
177 }
178 
179 
180 void PYTHON_ACTION_PLUGINS::register_action( PyObject* aPyAction )
181 {
182  PYTHON_ACTION_PLUGIN* fw = new PYTHON_ACTION_PLUGIN( aPyAction );
183 
184  fw->register_action();
185 }
186 
187 
188 void PYTHON_ACTION_PLUGINS::deregister_action( PyObject* aPyAction )
189 {
190  // deregister also destroys the previously created "PYTHON_ACTION_PLUGIN object"
191  ACTION_PLUGINS::deregister_object( (void*) aPyAction );
192 }
193 
194 
195 void PCB_EDIT_FRAME::OnActionPluginMenu( wxCommandEvent& aEvent )
196 {
197  ACTION_PLUGIN* actionPlugin = ACTION_PLUGINS::GetActionByMenu( aEvent.GetId() );
198 
199  if( actionPlugin )
200  RunActionPlugin( actionPlugin );
201 }
202 
203 
204 void PCB_EDIT_FRAME::OnActionPluginButton( wxCommandEvent& aEvent )
205 {
206  ACTION_PLUGIN* actionPlugin = ACTION_PLUGINS::GetActionByButton( aEvent.GetId() );
207 
208  if( actionPlugin )
209  RunActionPlugin( actionPlugin );
210 }
211 
213 {
214 
215  PICKED_ITEMS_LIST itemsList;
216  BOARD* currentPcb = GetBoard();
217  bool fromEmpty = false;
218 
219  // Append tracks:
220  for( PCB_TRACK* item : currentPcb->Tracks() )
221  {
222  ITEM_PICKER picker( nullptr, item, UNDO_REDO::CHANGED );
223  itemsList.PushItem( picker );
224  }
225 
226  // Append footprints:
227  for( FOOTPRINT* item : currentPcb->Footprints() )
228  {
229  ITEM_PICKER picker( nullptr, item, UNDO_REDO::CHANGED );
230  itemsList.PushItem( picker );
231  }
232 
233  // Append drawings
234  for( BOARD_ITEM* item : currentPcb->Drawings() )
235  {
236  ITEM_PICKER picker( nullptr, item, UNDO_REDO::CHANGED );
237  itemsList.PushItem( picker );
238  }
239 
240  // Append zones outlines
241  for( ZONE* zone : currentPcb->Zones() )
242  {
243  ITEM_PICKER picker( nullptr, zone, UNDO_REDO::CHANGED );
244  itemsList.PushItem( picker );
245  }
246 
247  if( itemsList.GetCount() > 0 )
249  else
250  fromEmpty = true;
251 
252  itemsList.ClearItemsList();
253 
254  BOARD_COMMIT commit( this );
255 
256  // Execute plugin itself...
258  aActionPlugin->Run();
260 
261  // Get back the undo buffer to fix some modifications
262  PICKED_ITEMS_LIST* oldBuffer = nullptr;
263 
264  if( fromEmpty )
265  {
266  oldBuffer = new PICKED_ITEMS_LIST();
267  }
268  else
269  {
270  oldBuffer = PopCommandFromUndoList();
271  wxASSERT( oldBuffer );
272  }
273 
274  // Try do discover what was modified
275  PICKED_ITEMS_LIST deletedItemsList;
276 
277  // The list of existing items after running the action script
278  std::set<BOARD_ITEM*> currItemList;
279 
280  // Append tracks:
281  for( PCB_TRACK* item : currentPcb->Tracks() )
282  currItemList.insert( item );
283 
284  // Append footprints:
285  for( FOOTPRINT* item : currentPcb->Footprints() )
286  currItemList.insert( item );
287 
288  // Append drawings
289  for( BOARD_ITEM* item : currentPcb->Drawings() )
290  currItemList.insert( item );
291 
292  // Append zones outlines
293  for( ZONE* zone : currentPcb->Zones() )
294  currItemList.insert( zone );
295 
296  // Found deleted items
297  for( unsigned int i = 0; i < oldBuffer->GetCount(); i++ )
298  {
299  BOARD_ITEM* item = (BOARD_ITEM*) oldBuffer->GetPickedItem( i );
300  ITEM_PICKER picker( nullptr, item, UNDO_REDO::DELETED );
301 
302  wxASSERT( item );
303 
304  if( currItemList.find( item ) == currItemList.end() )
305  {
306  deletedItemsList.PushItem( picker );
307  commit.Removed( item );
308  }
309  }
310 
311  // Mark deleted elements in undolist
312 
313  for( unsigned int i = 0; i < deletedItemsList.GetCount(); i++ )
314  {
315  oldBuffer->PushItem( deletedItemsList.GetItemWrapper( i ) );
316  }
317 
318  // Find new footprints
319  for( FOOTPRINT* item : currentPcb->Footprints() )
320  {
321  if( !oldBuffer->ContainsItem( item ) )
322  {
323  ITEM_PICKER picker( nullptr, item, UNDO_REDO::NEWITEM );
324  oldBuffer->PushItem( picker );
325  commit.Added( item );
326  }
327  }
328 
329  for( PCB_TRACK* item : currentPcb->Tracks() )
330  {
331  if( !oldBuffer->ContainsItem( item ) )
332  {
333  ITEM_PICKER picker( nullptr, item, UNDO_REDO::NEWITEM );
334  oldBuffer->PushItem( picker );
335  commit.Added( item );
336  }
337  }
338 
339  for( BOARD_ITEM* item : currentPcb->Drawings() )
340  {
341  if( !oldBuffer->ContainsItem( item ) )
342  {
343  ITEM_PICKER picker( nullptr, item, UNDO_REDO::NEWITEM );
344  oldBuffer->PushItem( picker );
345  commit.Added( item );
346  }
347  }
348 
349  for( ZONE* zone : currentPcb->Zones() )
350  {
351  if( !oldBuffer->ContainsItem( zone ) )
352  {
353  ITEM_PICKER picker( nullptr, zone, UNDO_REDO::NEWITEM );
354  oldBuffer->PushItem( picker );
355  commit.Added( zone );
356  }
357  }
358 
359 
360  if( oldBuffer->GetCount() )
361  {
362  OnModify();
363  PushCommandToUndoList( oldBuffer );
364  }
365  else
366  {
367  delete oldBuffer;
368  }
369 
370  commit.Push( _( "Apply action script" ) );
372 }
373 
374 
376 {
377  if( !actionMenu ) // Should not occur.
378  return;
379 
380  for( int ii = 0; ii < ACTION_PLUGINS::GetActionsCount(); ii++ )
381  {
382  wxMenuItem* item;
384  const wxBitmap& bitmap = ap->iconBitmap.IsOk() ? ap->iconBitmap :
386 
387  item = AddMenuItem( actionMenu, wxID_ANY, ap->GetName(), ap->GetDescription(), bitmap );
388 
389  Connect( item->GetId(), wxEVT_COMMAND_MENU_SELECTED,
390  wxCommandEventHandler( PCB_EDIT_FRAME::OnActionPluginMenu ) );
391 
392  ACTION_PLUGINS::SetActionMenu( ii, item->GetId() );
393  }
394 }
395 
396 
398 {
399  bool need_separator = true;
400  const std::vector<ACTION_PLUGIN*>& orderedPlugins = GetOrderedActionPlugins();
401 
402  for( ACTION_PLUGIN* ap : orderedPlugins )
403  {
404  if( GetActionPluginButtonVisible( ap->GetPluginPath(), ap->GetShowToolbarButton() ) )
405  {
406  if( need_separator )
407  {
409  need_separator = false;
410  }
411 
412  // Add button
413  wxBitmap bitmap;
414 
415  if ( ap->iconBitmap.IsOk() )
416  bitmap = KiScaledBitmap( ap->iconBitmap, this );
417  else
418  bitmap = KiScaledBitmap( BITMAPS::puzzle_piece, this );
419 
420  wxAuiToolBarItem* button = m_mainToolBar->AddTool( wxID_ANY, wxEmptyString,
421  bitmap, ap->GetName() );
422 
423  Connect( button->GetId(), wxEVT_COMMAND_MENU_SELECTED,
424  wxCommandEventHandler( PCB_EDIT_FRAME::OnActionPluginButton ) );
425 
426  // Link action plugin to button
427  ACTION_PLUGINS::SetActionButton( ap, button->GetId() );
428  }
429  }
430 }
431 
432 
433 std::vector<ACTION_PLUGIN*> PCB_EDIT_FRAME::GetOrderedActionPlugins()
434 {
435  std::vector<ACTION_PLUGIN*> plugins;
436  std::vector<ACTION_PLUGIN*> orderedPlugins;
437 
438  for( int i = 0; i < ACTION_PLUGINS::GetActionsCount(); i++ )
439  plugins.push_back( ACTION_PLUGINS::GetAction( i ) );
440 
441  // First add plugins that have entries in settings
442  for( const auto& pair : m_settings->m_VisibleActionPlugins )
443  {
444  auto loc = std::find_if( plugins.begin(), plugins.end(),
445  [pair] ( ACTION_PLUGIN* plugin )
446  {
447  return plugin->GetPluginPath() == pair.first;
448  } );
449 
450  if( loc != plugins.end() )
451  {
452  orderedPlugins.push_back( *loc );
453  plugins.erase( loc );
454  }
455  }
456 
457  // Now append new plugins that have not been configured yet
458  for( auto remaining_plugin : plugins )
459  orderedPlugins.push_back( remaining_plugin );
460 
461  return orderedPlugins;
462 }
463 
464 
465 bool PCB_EDIT_FRAME::GetActionPluginButtonVisible( const wxString& aPluginPath,
466  bool aPluginDefault )
467 {
468  auto& settings = m_settings->m_VisibleActionPlugins;
469 
470  for( const auto& entry : settings )
471  {
472  if( entry.first == aPluginPath )
473  return entry.second;
474  }
475 
476  // Plugin is not in settings, return default.
477  return aPluginDefault;
478 }
virtual wxString GetName()=0
void OnModify() override
Must be called after a board change to set the modified flag.
This is the parent class from where any action plugin class must derive.
Definition: action_plugin.h:38
wxString GetName() override
COMMIT & Added(EDA_ITEM *aItem)
Remove a new item from the model.
Definition: commit.h:84
void SaveCopyInUndoList(EDA_ITEM *aItemToCopy, UNDO_REDO aTypeCommand) override
Create a new entry in undo list of commands.
Definition: undo_redo.cpp:179
ZONES & Zones()
Definition: board.h:239
Defines the structure of a menu based on ACTIONs.
Definition: action_menu.h:48
void AddActionPluginTools()
Append action plugin buttons to main toolbar.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:80
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:257
void buildActionPluginMenus(ACTION_MENU *aActionMenu)
Fill action menu with all registered action plugins.
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
wxBitmap iconBitmap
wxString GetIconFileName(bool aDark) override
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.
void RunActionPlugin(ACTION_PLUGIN *aActionPlugin)
Execute action plugin's Run() method and updates undo buffer.
ACTION_TOOLBAR * m_mainToolBar
void register_action()
It's the standard method of a "ACTION_PLUGIN" to register itself into the ACTION_PLUGINS singleton ma...
PCBNEW_SETTINGS * m_settings
bool GetActionPluginButtonVisible(const wxString &aPluginPath, bool aPluginDefault)
Return true if button visibility action plugin setting was set to true or it is unset and plugin defa...
unsigned GetCount() const
void OnActionPluginMenu(wxCommandEvent &aEvent)
Launched by the menu when an action is called.
COMMIT & Removed(EDA_ITEM *aItem)
Modify a given item in the model.
Definition: commit.h:96
static void deregister_action(PyObject *aPyAction)
void * GetObject() override
This method gets the pointer to the object from where this action constructs.
wxString GetDescription() override
virtual void Run()=0
This method the the action.
static ACTION_PLUGIN * GetActionByMenu(int aMenu)
Find action plugin associated to a menu ID.
wxString GetPluginPath() override
FOOTPRINTS & Footprints()
Definition: board.h:233
std::vector< ACTION_PLUGIN * > GetOrderedActionPlugins()
Return ordered list of plugins in sequence in which they should appear on toolbar or in settings.
void Run() override
This method the the action.
PyObject * CallMethod(const char *aMethod, PyObject *aArglist=nullptr)
static int GetActionsCount()
EDA_ITEM * GetPickedItem(unsigned int aIdx) const
wxString CallRetStrMethod(const char *aMethod, PyObject *aArglist=nullptr)
virtual PICKED_ITEMS_LIST * PopCommandFromUndoList()
Return the last command to undo and remove it from list, nothing is deleted.
#define _(s)
Handle a list of polygons defining a copper zone.
Definition: zone.h:56
PYTHON_ACTION_PLUGIN(PyObject *action)
A holder to handle information on schematic or board items.
bool GetShowToolbarButton() override
wxBitmap KiBitmap(BITMAPS aBitmap, int aHeightTag)
Construct a wxBitmap from an image identifier Returns the image from the active theme if the image ha...
Definition: bitmap.cpp:105
wxBitmap KiScaledBitmap(BITMAPS aBitmap, wxWindow *aWindow, int aHeight, bool aQuantized)
Construct a wxBitmap from a memory record, scaling it if device DPI demands it.
Definition: bitmap.cpp:148
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)
Associate a button id to an action plugin.
static ACTION_PLUGIN * GetAction(const wxString &aName)
ACTION_PLUGIN_SETTINGS_LIST m_VisibleActionPlugins
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:190
static void SetActionMenu(int aIndex, int idMenu)
Associate a menu id to an action plugin.
Class PCBNEW_ACTION_PLUGINS.
void ClearItemsList()
Delete only the list of pickers NOT the picked data itself.
virtual void Push(const wxString &aMessage=wxT("A commit"), bool aCreateUndoEntry=true, bool aSetDirtyBit=true) override
Revert the commit by restoring the modified items state.
bool ContainsItem(const EDA_ITEM *aItem) const
void OnActionPluginButton(wxCommandEvent &aEvent)
Launched by the button when an action is called.
BOARD * GetBoard() const
static void register_action(PyObject *aPyAction)
static bool deregister_object(void *aObject)
Deregister an object which builds a action.
static ACTION_PLUGIN * GetActionByButton(int aButton)
Find action plugin associated to a button ID.
DRAWINGS & Drawings()
Definition: board.h:236
static void SetActionRunning(bool aRunning)
TRACKS & Tracks()
Definition: board.h:230
wxString GetCategoryName() override