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-2020 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 <class_board.h>
26 #include <class_module.h>
27 #include <class_track.h>
28 #include <class_zone.h>
29 #include <menus_helpers.h>
30 #include <pcbnew_settings.h>
31 #include <python_scripting.h>
32 #include <tool/action_menu.h>
33 #include <tool/action_toolbar.h>
34 
36 {
37  PyLOCK lock;
38 
39  this->m_PyAction = aAction;
40  Py_XINCREF( aAction );
41 }
42 
43 
45 {
46  PyLOCK lock;
47 
48  Py_XDECREF( this->m_PyAction );
49 }
50 
51 
52 PyObject* PYTHON_ACTION_PLUGIN::CallMethod( const char* aMethod, PyObject* aArglist )
53 {
54  PyLOCK lock;
55 
56  PyErr_Clear();
57  // pFunc is a new reference to the desired method
58  PyObject* pFunc = PyObject_GetAttrString( this->m_PyAction, aMethod );
59 
60  if( pFunc && PyCallable_Check( pFunc ) )
61  {
62  PyObject* result = PyObject_CallObject( pFunc, aArglist );
63 
64  if( PyErr_Occurred() )
65  {
66  wxMessageBox( PyErrStringWithTraceback(),
67  _( "Exception on python action plugin code" ),
68  wxICON_ERROR | wxOK );
69  }
70 
71  if( result )
72  {
73  Py_XDECREF( pFunc );
74  return result;
75  }
76  }
77  else
78  {
79  wxString msg = wxString::Format( _( "Method \"%s\" not found, or not callable" ), aMethod );
80  wxMessageBox( msg, _( "Unknown Method" ), wxICON_ERROR | wxOK );
81  }
82 
83  if( pFunc )
84  {
85  Py_XDECREF( pFunc );
86  }
87 
88  return NULL;
89 }
90 
91 
92 wxString PYTHON_ACTION_PLUGIN::CallRetStrMethod( const char* aMethod, PyObject* aArglist )
93 {
94  wxString ret;
95  PyLOCK lock;
96 
97  PyObject* result = CallMethod( aMethod, aArglist );
98 
99  ret = PyStringToWx( result );
100  Py_XDECREF( result );
101 
102  return ret;
103 }
104 
105 
107 {
108  PyLOCK lock;
109 
110  return CallRetStrMethod( "GetCategoryName" );
111 }
112 
113 
115 {
116  PyLOCK lock;
117 
118  return CallRetStrMethod( "GetName" );
119 }
120 
121 
123 {
124  PyLOCK lock;
125 
126  return CallRetStrMethod( "GetDescription" );
127 }
128 
129 
131 {
132  PyLOCK lock;
133 
134  PyObject* result = CallMethod( "GetShowToolbarButton");
135 
136  return PyObject_IsTrue(result);
137 }
138 
139 
141 {
142  PyLOCK lock;
143 
144  return CallRetStrMethod( "GetIconFileName" );
145 }
146 
147 
149 {
150  PyLOCK lock;
151 
152  return CallRetStrMethod( "GetPluginPath" );
153 }
154 
155 
157 {
158  PyLOCK lock;
159 
160  CallMethod( "Run" );
161 }
162 
163 
165 {
166  return (void*) m_PyAction;
167 }
168 
169 
170 void PYTHON_ACTION_PLUGINS::register_action( PyObject* aPyAction )
171 {
172  PYTHON_ACTION_PLUGIN* fw = new PYTHON_ACTION_PLUGIN( aPyAction );
173 
174  fw->register_action();
175 }
176 
177 
178 void PYTHON_ACTION_PLUGINS::deregister_action( PyObject* aPyAction )
179 {
180  // deregister also destroys the previously created "PYTHON_ACTION_PLUGIN object"
181  ACTION_PLUGINS::deregister_object( (void*) aPyAction );
182 }
183 
184 
185 #if defined(KICAD_SCRIPTING) && defined(KICAD_SCRIPTING_ACTION_MENU)
186 
187 void PCB_EDIT_FRAME::OnActionPluginMenu( wxCommandEvent& aEvent )
188 {
189  ACTION_PLUGIN* actionPlugin = ACTION_PLUGINS::GetActionByMenu( aEvent.GetId() );
190 
191  if( actionPlugin )
192  RunActionPlugin( actionPlugin );
193 }
194 
195 void PCB_EDIT_FRAME::OnActionPluginButton( wxCommandEvent& aEvent )
196 {
197  ACTION_PLUGIN* actionPlugin = ACTION_PLUGINS::GetActionByButton( aEvent.GetId() );
198 
199  if( actionPlugin )
200  RunActionPlugin( actionPlugin );
201 }
202 
203 void PCB_EDIT_FRAME::RunActionPlugin( ACTION_PLUGIN* aActionPlugin )
204 {
205 
206  PICKED_ITEMS_LIST itemsList;
207  BOARD* currentPcb = GetBoard();
208  bool fromEmpty = false;
209 
210  itemsList.m_Status = UNDO_REDO::CHANGED;
211 
212  // Append tracks:
213  for( TRACK* item : currentPcb->Tracks() )
214  {
215  ITEM_PICKER picker( nullptr, item, UNDO_REDO::CHANGED );
216  itemsList.PushItem( picker );
217  }
218 
219  // Append modules:
220  for( MODULE* item : currentPcb->Modules() )
221  {
222  ITEM_PICKER picker( nullptr, item, UNDO_REDO::CHANGED );
223  itemsList.PushItem( picker );
224  }
225 
226  // Append drawings
227  for( BOARD_ITEM* item : currentPcb->Drawings() )
228  {
229  ITEM_PICKER picker( nullptr, item, UNDO_REDO::CHANGED );
230  itemsList.PushItem( picker );
231  }
232 
233  // Append zones outlines
234  for( ZONE_CONTAINER* zone : currentPcb->Zones() )
235  {
236  ITEM_PICKER picker( nullptr, zone, UNDO_REDO::CHANGED );
237  itemsList.PushItem( picker );
238  }
239 
240  if( itemsList.GetCount() > 0 )
241  SaveCopyInUndoList( itemsList, UNDO_REDO::CHANGED, wxPoint( 0.0, 0.0 ) );
242  else
243  fromEmpty = true;
244 
245  itemsList.ClearItemsList();
246 
247  // Execute plugin itself...
249  aActionPlugin->Run();
251 
252  // Get back the undo buffer to fix some modifications
253  PICKED_ITEMS_LIST* oldBuffer = NULL;
254 
255  if( fromEmpty )
256  {
257  oldBuffer = new PICKED_ITEMS_LIST();
258  oldBuffer->m_Status = UNDO_REDO::NEWITEM;
259  }
260  else
261  {
262  oldBuffer = PopCommandFromUndoList();
263  wxASSERT( oldBuffer );
264  }
265 
266  // Try do discover what was modified
267  PICKED_ITEMS_LIST deletedItemsList;
268 
269  // The list of existing items after running the action script
270  std::set<BOARD_ITEM*> currItemList;
271 
272  // Append tracks:
273  for( TRACK* item : currentPcb->Tracks() )
274  currItemList.insert( item );
275 
276  // Append modules:
277  for( MODULE* item : currentPcb->Modules() )
278  currItemList.insert( item );
279 
280  // Append drawings
281  for( BOARD_ITEM* item : currentPcb->Drawings() )
282  currItemList.insert( item );
283 
284  // Append zones outlines
285  for( ZONE_CONTAINER* zone : currentPcb->Zones() )
286  currItemList.insert( zone );
287 
288  // Found deleted modules
289  for( unsigned int i = 0; i < oldBuffer->GetCount(); i++ )
290  {
291  BOARD_ITEM* item = (BOARD_ITEM*) oldBuffer->GetPickedItem( i );
292  ITEM_PICKER picker( nullptr, item, UNDO_REDO::DELETED );
293 
294  wxASSERT( item );
295 
296  if( currItemList.find( item ) == currItemList.end() )
297  deletedItemsList.PushItem( picker );
298  }
299 
300  // Mark deleted elements in undolist
301  for( unsigned int i = 0; i < deletedItemsList.GetCount(); i++ )
302  {
303  oldBuffer->PushItem( deletedItemsList.GetItemWrapper( i ) );
304  }
305 
306  // Find new modules
307  for( MODULE* item : currentPcb->Modules() )
308  {
309  if( !oldBuffer->ContainsItem( item ) )
310  {
311  ITEM_PICKER picker( nullptr, item, UNDO_REDO::NEWITEM );
312  oldBuffer->PushItem( picker );
313  }
314  }
315 
316  for( TRACK* item : currentPcb->Tracks() )
317  {
318  if( !oldBuffer->ContainsItem( item ) )
319  {
320  ITEM_PICKER picker( nullptr, item, UNDO_REDO::NEWITEM );
321  oldBuffer->PushItem( picker );
322  }
323  }
324 
325  for( BOARD_ITEM* item : currentPcb->Drawings() )
326  {
327  if( !oldBuffer->ContainsItem( item ) )
328  {
329  ITEM_PICKER picker( nullptr, item, UNDO_REDO::NEWITEM );
330  oldBuffer->PushItem( picker );
331  }
332  }
333 
334  for( ZONE_CONTAINER* zone : currentPcb->Zones() )
335  {
336  if( !oldBuffer->ContainsItem( zone ) )
337  {
338  ITEM_PICKER picker( nullptr, zone, UNDO_REDO::NEWITEM );
339  oldBuffer->PushItem( picker );
340  }
341  }
342 
343  if( oldBuffer->GetCount() )
344  {
345  OnModify();
346  PushCommandToUndoList( oldBuffer );
347  }
348  else
349  {
350  delete oldBuffer;
351  }
352 
354 }
355 
356 
357 void PCB_EDIT_FRAME::buildActionPluginMenus( ACTION_MENU* actionMenu )
358 {
359  if( !actionMenu ) // Should not occur.
360  return;
361 
362  for( int ii = 0; ii < ACTION_PLUGINS::GetActionsCount(); ii++ )
363  {
364  wxMenuItem* item;
366  const wxBitmap& bitmap = ap->iconBitmap.IsOk() ? ap->iconBitmap : KiBitmap( hammer_xpm );
367 
368  item = AddMenuItem( actionMenu, wxID_ANY, ap->GetName(), ap->GetDescription(), bitmap );
369 
370  Connect( item->GetId(), wxEVT_COMMAND_MENU_SELECTED,
371  wxCommandEventHandler( PCB_EDIT_FRAME::OnActionPluginMenu ) );
372 
373  ACTION_PLUGINS::SetActionMenu( ii, item->GetId() );
374  }
375 }
376 
377 
378 void PCB_EDIT_FRAME::AddActionPluginTools()
379 {
380  bool need_separator = true;
381  const std::vector<ACTION_PLUGIN*>& orderedPlugins = GetOrderedActionPlugins();
382 
383  for( ACTION_PLUGIN* ap : orderedPlugins )
384  {
385  if( GetActionPluginButtonVisible( ap->GetPluginPath(), ap->GetShowToolbarButton() ) )
386  {
387  if( need_separator )
388  {
390  need_separator = false;
391  }
392 
393  // Add button
394  wxBitmap bitmap;
395 
396  if ( ap->iconBitmap.IsOk() )
397  bitmap = KiScaledBitmap( ap->iconBitmap, this );
398  else
399  bitmap = KiScaledBitmap( hammer_xpm, this );
400 
401  wxAuiToolBarItem* button = m_mainToolBar->AddTool(
402  wxID_ANY, wxEmptyString, bitmap, ap->GetName() );
403 
404  Connect( button->GetId(), wxEVT_COMMAND_MENU_SELECTED,
405  wxCommandEventHandler( PCB_EDIT_FRAME::OnActionPluginButton ) );
406 
407  // Link action plugin to button
408  ACTION_PLUGINS::SetActionButton( ap, button->GetId() );
409  }
410  }
411 }
412 
413 
414 std::vector<ACTION_PLUGIN*> PCB_EDIT_FRAME::GetOrderedActionPlugins()
415 {
416  std::vector<ACTION_PLUGIN*> plugins;
417  std::vector<ACTION_PLUGIN*> orderedPlugins;
418 
419  for( int i = 0; i < ACTION_PLUGINS::GetActionsCount(); i++ )
420  plugins.push_back( ACTION_PLUGINS::GetAction( i ) );
421 
422  // First add plugins that have entries in settings
423  for( const auto& pair : m_Settings->m_VisibleActionPlugins )
424  {
425  auto loc = std::find_if( plugins.begin(), plugins.end(),
426  [pair] ( ACTION_PLUGIN* plugin )
427  {
428  return plugin->GetPluginPath() == pair.first;
429  } );
430 
431  if( loc != plugins.end() )
432  {
433  orderedPlugins.push_back( *loc );
434  plugins.erase( loc );
435  }
436  }
437 
438  // Now append new plugins that have not been configured yet
439  for( auto remaining_plugin : plugins )
440  orderedPlugins.push_back( remaining_plugin );
441 
442  return orderedPlugins;
443 }
444 
445 
446 bool PCB_EDIT_FRAME::GetActionPluginButtonVisible( const wxString& aPluginPath, bool aPluginDefault )
447 {
448  auto& settings = m_Settings->m_VisibleActionPlugins;
449 
450  for( const auto& entry : settings )
451  {
452  if( entry.first == aPluginPath )
453  return entry.second;
454  }
455 
456  // Plugin is not in settings, return default.
457  return aPluginDefault;
458 }
459 
460 
461 #endif
virtual wxString GetName()=0
Function GetName.
wxString PyStringToWx(PyObject *aString)
void OnModify() override
Function OnModify must be called after a board change to set the modified flag.
ZONE_CONTAINER handles a list of polygons defining a copper zone.
Definition: class_zone.h:61
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)
Defines the structure of a menu based on ACTIONs.
Definition: action_menu.h:43
BOARD_ITEM is a base class for any item which can be embedded within the BOARD container class,...
virtual bool GetShowToolbarButton()=0
Function GetShowToolbarButton.
wxMenuItem * AddMenuItem(wxMenu *aMenu, int aId, const wxString &aText, const wxBitmap &aImage, wxItemKind aType=wxITEM_NORMAL)
Function AddMenuItem is an inline helper function to create and insert a menu item with an icon into ...
Definition: bitmap.cpp:232
void AddScaledSeparator(wxWindow *aWindow)
Add a separator that introduces space on either side to not squash the tools when scaled.
void ActivateGalCanvas() override
virtual wxString GetDescription()=0
Function GetDescription.
wxBitmap iconBitmap
Definition: action_plugin.h:50
virtual void PushCommandToUndoList(PICKED_ITEMS_LIST *aItem)
Function PushCommandToUndoList add a command to undo in undo list delete the very old commands when t...
void PushItem(const ITEM_PICKER &aItem)
Function PushItem pushes aItem to the top of the list.
ACTION_TOOLBAR * m_mainToolBar
void register_action()
Function register_action It's the standard method of a "ACTION_PLUGIN" to register itself into the AC...
wxBitmap KiScaledBitmap(BITMAP_DEF aBitmap, wxWindow *aWindow)
Construct a wxBitmap from a memory record, scaling it if device DPI demands it.
Definition: bitmap.cpp:116
unsigned GetCount() const
Function GetCount.
A single base class (TRACK) represents both tracks and vias, with subclasses for curved tracks (ARC) ...
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:80
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.
MODULES & Modules()
Definition: class_board.h:249
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.
void Run() override
Function Run This method the the action.
static int GetActionsCount()
Function GetActionsCount.
EDA_ITEM * GetPickedItem(unsigned int aIdx) const
Function GetPickedItem.
virtual PICKED_ITEMS_LIST * PopCommandFromUndoList()
PopCommandFromUndoList return the last command to undo and remove it from list nothing is deleted.
const BITMAP_OPAQUE hammer_xpm[1]
Definition: hammer.cpp:74
PYTHON_ACTION_PLUGIN(PyObject *action)
wxString GetIconFileName() override
Function GetIconFileName.
PICKED_ITEMS_LIST is a holder to handle information on schematic or board items.
bool GetShowToolbarButton() override
Function GetShowToolbarButton.
PyObject * CallMethod(const char *aMethod, PyObject *aArglist=NULL)
PCBNEW_SETTINGS * m_Settings
ITEM_PICKER GetItemWrapper(unsigned int aIdx) const
Function GetItemWrapper.
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.
ZONE_CONTAINERS & Zones()
Definition: class_board.h:254
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, CPTREE &aTree)
Function Format outputs a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:201
BOARD holds information pertinent to a Pcbnew printed circuit board.
Definition: class_board.h:178
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.
void SaveCopyInUndoList(EDA_ITEM *aItemToCopy, UNDO_REDO aTypeCommand, const wxPoint &aTransformPoint=wxPoint(0, 0)) override
Function SaveCopyInUndoList Creates a new entry in undo list of commands.
Definition: undo_redo.cpp:184
wxString PyErrStringWithTraceback()
void ClearItemsList()
Function ClearItemsList deletes only the list of pickers, NOT the picked data itself.
bool ContainsItem(const EDA_ITEM *aItem) const
Function IsItemInList.
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: class_board.h:252
static void SetActionRunning(bool aRunning)
Function SetActionRunning.
TRACKS & Tracks()
Definition: class_board.h:246
wxString GetCategoryName() override
Function GetCategoryName.