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  // pFunc is a new reference to the desired method
61  PyObject* pFunc = PyObject_GetAttrString( m_PyAction, aMethod );
62 
63  if( pFunc && PyCallable_Check( pFunc ) )
64  {
65  PyObject* result = PyObject_CallObject( pFunc, aArglist );
66 
67  if( PyErr_Occurred() )
68  {
69  wxMessageBox( PyErrStringWithTraceback(),
70  _( "Exception on python action plugin code" ),
71  wxICON_ERROR | wxOK );
72  }
73 
74  if( result )
75  {
76  Py_XDECREF( pFunc );
77  return result;
78  }
79  }
80  else
81  {
82  wxString msg = wxString::Format( _( "Method \"%s\" not found, or not callable" ), aMethod );
83  wxMessageBox( msg, _( "Unknown Method" ), wxICON_ERROR | wxOK );
84  }
85 
86  if( pFunc )
87  {
88  Py_XDECREF( pFunc );
89  }
90 
91  return NULL;
92 }
93 
94 
95 wxString PYTHON_ACTION_PLUGIN::CallRetStrMethod( const char* aMethod, PyObject* aArglist )
96 {
97  wxString ret;
98  PyLOCK lock;
99 
100  PyObject* result = CallMethod( aMethod, aArglist );
101 
102  ret = PyStringToWx( result );
103  Py_XDECREF( result );
104 
105  return ret;
106 }
107 
108 
110 {
111  PyLOCK lock;
112 
113  return CallRetStrMethod( "GetCategoryName" );
114 }
115 
116 
118 {
119  PyLOCK lock;
120 
121  return CallRetStrMethod( "GetName" );
122 }
123 
124 
126 {
127  PyLOCK lock;
128 
129  return CallRetStrMethod( "GetDescription" );
130 }
131 
132 
134 {
135  PyLOCK lock;
136 
137  PyObject* result = CallMethod( "GetShowToolbarButton");
138 
139  return PyObject_IsTrue(result);
140 }
141 
142 
144 {
145  PyLOCK lock;
146 
147  PyObject* arglist = Py_BuildValue( "(i)", static_cast<int>( aDark ) );
148 
149  wxString result = CallRetStrMethod( "GetIconFileName", arglist );
150 
151  Py_DECREF( arglist );
152 
153  return result;
154 }
155 
156 
158 {
159  PyLOCK lock;
160 
161  return CallRetStrMethod( "GetPluginPath" );
162 }
163 
164 
166 {
167  PyLOCK lock;
168 
169  CallMethod( "Run" );
170 }
171 
172 
174 {
175  return (void*) m_PyAction;
176 }
177 
178 
179 void PYTHON_ACTION_PLUGINS::register_action( PyObject* aPyAction )
180 {
181  PYTHON_ACTION_PLUGIN* fw = new PYTHON_ACTION_PLUGIN( aPyAction );
182 
183  fw->register_action();
184 }
185 
186 
187 void PYTHON_ACTION_PLUGINS::deregister_action( PyObject* aPyAction )
188 {
189  // deregister also destroys the previously created "PYTHON_ACTION_PLUGIN object"
190  ACTION_PLUGINS::deregister_object( (void*) aPyAction );
191 }
192 
193 
194 void PCB_EDIT_FRAME::OnActionPluginMenu( wxCommandEvent& aEvent )
195 {
196  ACTION_PLUGIN* actionPlugin = ACTION_PLUGINS::GetActionByMenu( aEvent.GetId() );
197 
198  if( actionPlugin )
199  RunActionPlugin( actionPlugin );
200 }
201 
202 
203 void PCB_EDIT_FRAME::OnActionPluginButton( wxCommandEvent& aEvent )
204 {
205  ACTION_PLUGIN* actionPlugin = ACTION_PLUGINS::GetActionByButton( aEvent.GetId() );
206 
207  if( actionPlugin )
208  RunActionPlugin( actionPlugin );
209 }
210 
212 {
213 
214  PICKED_ITEMS_LIST itemsList;
215  BOARD* currentPcb = GetBoard();
216  bool fromEmpty = false;
217 
218  // Append tracks:
219  for( PCB_TRACK* item : currentPcb->Tracks() )
220  {
221  ITEM_PICKER picker( nullptr, item, UNDO_REDO::CHANGED );
222  itemsList.PushItem( picker );
223  }
224 
225  // Append footprints:
226  for( FOOTPRINT* item : currentPcb->Footprints() )
227  {
228  ITEM_PICKER picker( nullptr, item, UNDO_REDO::CHANGED );
229  itemsList.PushItem( picker );
230  }
231 
232  // Append drawings
233  for( BOARD_ITEM* item : currentPcb->Drawings() )
234  {
235  ITEM_PICKER picker( nullptr, item, UNDO_REDO::CHANGED );
236  itemsList.PushItem( picker );
237  }
238 
239  // Append zones outlines
240  for( ZONE* zone : currentPcb->Zones() )
241  {
242  ITEM_PICKER picker( nullptr, zone, UNDO_REDO::CHANGED );
243  itemsList.PushItem( picker );
244  }
245 
246  if( itemsList.GetCount() > 0 )
248  else
249  fromEmpty = true;
250 
251  itemsList.ClearItemsList();
252 
253  BOARD_COMMIT commit( this );
254 
255  // Execute plugin itself...
257  aActionPlugin->Run();
259 
260  // Get back the undo buffer to fix some modifications
261  PICKED_ITEMS_LIST* oldBuffer = NULL;
262 
263  if( fromEmpty )
264  {
265  oldBuffer = new PICKED_ITEMS_LIST();
266  }
267  else
268  {
269  oldBuffer = PopCommandFromUndoList();
270  wxASSERT( oldBuffer );
271  }
272 
273  // Try do discover what was modified
274  PICKED_ITEMS_LIST deletedItemsList;
275 
276  // The list of existing items after running the action script
277  std::set<BOARD_ITEM*> currItemList;
278 
279  // Append tracks:
280  for( PCB_TRACK* item : currentPcb->Tracks() )
281  currItemList.insert( item );
282 
283  // Append footprints:
284  for( FOOTPRINT* item : currentPcb->Footprints() )
285  currItemList.insert( item );
286 
287  // Append drawings
288  for( BOARD_ITEM* item : currentPcb->Drawings() )
289  currItemList.insert( item );
290 
291  // Append zones outlines
292  for( ZONE* zone : currentPcb->Zones() )
293  currItemList.insert( zone );
294 
295  // Found deleted items
296  for( unsigned int i = 0; i < oldBuffer->GetCount(); i++ )
297  {
298  BOARD_ITEM* item = (BOARD_ITEM*) oldBuffer->GetPickedItem( i );
299  ITEM_PICKER picker( nullptr, item, UNDO_REDO::DELETED );
300 
301  wxASSERT( item );
302 
303  if( currItemList.find( item ) == currItemList.end() )
304  {
305  deletedItemsList.PushItem( picker );
306  commit.Removed( item );
307  }
308  }
309 
310  // Mark deleted elements in undolist
311 
312  for( unsigned int i = 0; i < deletedItemsList.GetCount(); i++ )
313  {
314  oldBuffer->PushItem( deletedItemsList.GetItemWrapper( i ) );
315  }
316  // Find new footprints
317  for( FOOTPRINT* item : currentPcb->Footprints() )
318  {
319  if( !oldBuffer->ContainsItem( item ) )
320  {
321  ITEM_PICKER picker( nullptr, item, UNDO_REDO::NEWITEM );
322  oldBuffer->PushItem( picker );
323  commit.Added( item );
324  }
325  }
326 
327  for( PCB_TRACK* item : currentPcb->Tracks() )
328  {
329  if( !oldBuffer->ContainsItem( item ) )
330  {
331  ITEM_PICKER picker( nullptr, item, UNDO_REDO::NEWITEM );
332  oldBuffer->PushItem( picker );
333  commit.Added( item );
334  }
335  }
336 
337  for( BOARD_ITEM* item : currentPcb->Drawings() )
338  {
339  if( !oldBuffer->ContainsItem( item ) )
340  {
341  ITEM_PICKER picker( nullptr, item, UNDO_REDO::NEWITEM );
342  oldBuffer->PushItem( picker );
343  commit.Added( item );
344  }
345  }
346 
347  for( ZONE* zone : currentPcb->Zones() )
348  {
349  if( !oldBuffer->ContainsItem( zone ) )
350  {
351  ITEM_PICKER picker( nullptr, zone, UNDO_REDO::NEWITEM );
352  oldBuffer->PushItem( picker );
353  commit.Added( zone );
354  }
355  }
356 
357 
358  if( oldBuffer->GetCount() )
359  {
360  OnModify();
361  PushCommandToUndoList( oldBuffer );
362  }
363  else
364  {
365  delete oldBuffer;
366  }
367 
368  commit.Push( _( "Apply action script" ) );
370 }
371 
372 
374 {
375  if( !actionMenu ) // Should not occur.
376  return;
377 
378  for( int ii = 0; ii < ACTION_PLUGINS::GetActionsCount(); ii++ )
379  {
380  wxMenuItem* item;
382  const wxBitmap& bitmap = ap->iconBitmap.IsOk() ? ap->iconBitmap : KiBitmap( BITMAPS::puzzle_piece );
383 
384  item = AddMenuItem( actionMenu, wxID_ANY, ap->GetName(), ap->GetDescription(), bitmap );
385 
386  Connect( item->GetId(), wxEVT_COMMAND_MENU_SELECTED,
387  wxCommandEventHandler( PCB_EDIT_FRAME::OnActionPluginMenu ) );
388 
389  ACTION_PLUGINS::SetActionMenu( ii, item->GetId() );
390  }
391 }
392 
393 
395 {
396  bool need_separator = true;
397  const std::vector<ACTION_PLUGIN*>& orderedPlugins = GetOrderedActionPlugins();
398 
399  for( ACTION_PLUGIN* ap : orderedPlugins )
400  {
401  if( GetActionPluginButtonVisible( ap->GetPluginPath(), ap->GetShowToolbarButton() ) )
402  {
403  if( need_separator )
404  {
406  need_separator = false;
407  }
408 
409  // Add button
410  wxBitmap bitmap;
411 
412  if ( ap->iconBitmap.IsOk() )
413  bitmap = KiScaledBitmap( ap->iconBitmap, this );
414  else
415  bitmap = KiScaledBitmap( BITMAPS::puzzle_piece, this );
416 
417  wxAuiToolBarItem* button = m_mainToolBar->AddTool(
418  wxID_ANY, wxEmptyString, bitmap, ap->GetName() );
419 
420  Connect( button->GetId(), wxEVT_COMMAND_MENU_SELECTED,
421  wxCommandEventHandler( PCB_EDIT_FRAME::OnActionPluginButton ) );
422 
423  // Link action plugin to button
424  ACTION_PLUGINS::SetActionButton( ap, button->GetId() );
425  }
426  }
427 }
428 
429 
430 std::vector<ACTION_PLUGIN*> PCB_EDIT_FRAME::GetOrderedActionPlugins()
431 {
432  std::vector<ACTION_PLUGIN*> plugins;
433  std::vector<ACTION_PLUGIN*> orderedPlugins;
434 
435  for( int i = 0; i < ACTION_PLUGINS::GetActionsCount(); i++ )
436  plugins.push_back( ACTION_PLUGINS::GetAction( i ) );
437 
438  // First add plugins that have entries in settings
439  for( const auto& pair : m_settings->m_VisibleActionPlugins )
440  {
441  auto loc = std::find_if( plugins.begin(), plugins.end(),
442  [pair] ( ACTION_PLUGIN* plugin )
443  {
444  return plugin->GetPluginPath() == pair.first;
445  } );
446 
447  if( loc != plugins.end() )
448  {
449  orderedPlugins.push_back( *loc );
450  plugins.erase( loc );
451  }
452  }
453 
454  // Now append new plugins that have not been configured yet
455  for( auto remaining_plugin : plugins )
456  orderedPlugins.push_back( remaining_plugin );
457 
458  return orderedPlugins;
459 }
460 
461 
462 bool PCB_EDIT_FRAME::GetActionPluginButtonVisible( const wxString& aPluginPath, bool aPluginDefault )
463 {
464  auto& settings = m_settings->m_VisibleActionPlugins;
465 
466  for( const auto& entry : settings )
467  {
468  if( entry.first == aPluginPath )
469  return entry.second;
470  }
471 
472  // Plugin is not in settings, return default.
473  return aPluginDefault;
474 }
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
wxString CallRetStrMethod(const char *aMethod, PyObject *aArglist=NULL)
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:180
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:254
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.
#define NULL
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.
static int 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.
#define _(s)
Handle a list of polygons defining a copper zone.
Definition: zone.h:57
PYTHON_ACTION_PLUGIN(PyObject *action)
A holder to handle information on schematic or board items.
bool GetShowToolbarButton() override
PyObject * CallMethod(const char *aMethod, PyObject *aArglist=NULL)
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
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
wxBitmap KiScaledBitmap(BITMAPS aBitmap, wxWindow *aWindow, int aHeight)
Construct a wxBitmap from a memory record, scaling it if device DPI demands it.
Definition: bitmap.cpp:148