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 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 
29 #include "pcbnew_action_plugins.h"
30 #include <board_commit.h>
31 #include <class_board.h>
32 #include <class_drawsegment.h>
33 #include <class_module.h>
34 #include <class_track.h>
35 #include <class_zone.h>
36 #include <cstdio>
37 #include <macros.h>
38 #include <menus_helpers.h>
39 #include <pcbnew_id.h>
40 #include <pcbnew_settings.h>
41 #include <python_scripting.h>
42 #include <tool/action_menu.h>
43 #include <tool/action_toolbar.h>
44 
46 {
47  PyLOCK lock;
48 
49  this->m_PyAction = aAction;
50  Py_XINCREF( aAction );
51 }
52 
53 
55 {
56  PyLOCK lock;
57 
58  Py_XDECREF( this->m_PyAction );
59 }
60 
61 
62 PyObject* PYTHON_ACTION_PLUGIN::CallMethod( const char* aMethod, PyObject* aArglist )
63 {
64  PyLOCK lock;
65 
66  PyErr_Clear();
67  // pFunc is a new reference to the desired method
68  PyObject* pFunc = PyObject_GetAttrString( this->m_PyAction, aMethod );
69 
70  if( pFunc && PyCallable_Check( pFunc ) )
71  {
72  PyObject* result = PyObject_CallObject( pFunc, aArglist );
73 
74  if( PyErr_Occurred() )
75  {
76  wxMessageBox( PyErrStringWithTraceback(),
77  _( "Exception on python action plugin code" ),
78  wxICON_ERROR | wxOK );
79  }
80 
81  if( result )
82  {
83  Py_XDECREF( pFunc );
84  return result;
85  }
86  }
87  else
88  {
89  wxString msg = wxString::Format( _( "Method \"%s\" not found, or not callable" ), aMethod );
90  wxMessageBox( msg, _( "Unknown Method" ), wxICON_ERROR | wxOK );
91  }
92 
93  if( pFunc )
94  {
95  Py_XDECREF( pFunc );
96  }
97 
98  return NULL;
99 }
100 
101 
102 wxString PYTHON_ACTION_PLUGIN::CallRetStrMethod( const char* aMethod, PyObject* aArglist )
103 {
104  wxString ret;
105  PyLOCK lock;
106 
107  PyObject* result = CallMethod( aMethod, aArglist );
108 
109  ret = PyStringToWx( result );
110  Py_XDECREF( result );
111 
112  return ret;
113 }
114 
115 
117 {
118  PyLOCK lock;
119 
120  return CallRetStrMethod( "GetCategoryName" );
121 }
122 
123 
125 {
126  PyLOCK lock;
127 
128  return CallRetStrMethod( "GetName" );
129 }
130 
131 
133 {
134  PyLOCK lock;
135 
136  return CallRetStrMethod( "GetDescription" );
137 }
138 
139 
141 {
142  PyLOCK lock;
143 
144  PyObject* result = CallMethod( "GetShowToolbarButton");
145 
146  return PyObject_IsTrue(result);
147 }
148 
149 
151 {
152  PyLOCK lock;
153 
154  return CallRetStrMethod( "GetIconFileName" );
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 #if defined(KICAD_SCRIPTING) && defined(KICAD_SCRIPTING_ACTION_MENU)
196 
197 void PCB_EDIT_FRAME::OnActionPluginMenu( wxCommandEvent& aEvent )
198 {
199  ACTION_PLUGIN* actionPlugin = ACTION_PLUGINS::GetActionByMenu( aEvent.GetId() );
200 
201  if( actionPlugin )
202  RunActionPlugin( actionPlugin );
203 }
204 
205 void PCB_EDIT_FRAME::OnActionPluginButton( wxCommandEvent& aEvent )
206 {
207  ACTION_PLUGIN* actionPlugin = ACTION_PLUGINS::GetActionByButton( aEvent.GetId() );
208 
209  if( actionPlugin )
210  RunActionPlugin( actionPlugin );
211 }
212 
213 void PCB_EDIT_FRAME::RunActionPlugin( ACTION_PLUGIN* aActionPlugin )
214 {
215 
216  PICKED_ITEMS_LIST itemsList;
217  BOARD* currentPcb = GetBoard();
218  bool fromEmpty = false;
219 
220  itemsList.m_Status = UR_CHANGED;
221 
222  // Append tracks:
223  for( auto item : currentPcb->Tracks() )
224  {
225  ITEM_PICKER picker( item, UR_CHANGED );
226  itemsList.PushItem( picker );
227  }
228 
229  // Append modules:
230  for( auto item : currentPcb->Modules() )
231  {
232  ITEM_PICKER picker( item, UR_CHANGED );
233  itemsList.PushItem( picker );
234  }
235 
236  // Append drawings
237  for( auto item : currentPcb->Drawings() )
238  {
239  ITEM_PICKER picker( item, UR_CHANGED );
240  itemsList.PushItem( picker );
241  }
242 
243  // Append zones outlines
244  for( int ii = 0; ii < currentPcb->GetAreaCount(); ii++ )
245  {
246  ITEM_PICKER picker( (EDA_ITEM*) currentPcb->GetArea(
247  ii ), UR_CHANGED );
248  itemsList.PushItem( picker );
249  }
250 
251  if( itemsList.GetCount() > 0 )
252  SaveCopyInUndoList( itemsList, UR_CHANGED, wxPoint( 0.0, 0.0 ) );
253  else
254  fromEmpty = true;
255 
256  itemsList.ClearItemsList();
257 
258  // Execute plugin itself...
260  aActionPlugin->Run();
262 
263  // Get back the undo buffer to fix some modifications
264  PICKED_ITEMS_LIST* oldBuffer = NULL;
265 
266  if( fromEmpty )
267  {
268  oldBuffer = new PICKED_ITEMS_LIST();
269  oldBuffer->m_Status = UR_NEW;
270  }
271  else
272  {
273  oldBuffer = GetScreen()->PopCommandFromUndoList();
274  wxASSERT( oldBuffer );
275  }
276 
277  // Try do discover what was modified
278  PICKED_ITEMS_LIST deletedItemsList;
279 
280  // The list of existing items after running the action script
281  std::set<BOARD_ITEM*> currItemList;
282  // Append tracks:
283  for( auto item : currentPcb->Tracks() )
284  currItemList.insert( item );
285 
286  // Append modules:
287  for( auto item : currentPcb->Modules() )
288  currItemList.insert( item );
289 
290  // Append drawings
291  for( auto item : currentPcb->Drawings() )
292  currItemList.insert( item );
293 
294  // Append zones outlines
295  for( int ii = 0; ii < currentPcb->GetAreaCount(); ii++ )
296  currItemList.insert( currentPcb->GetArea( ii ) );
297 
298  // Found deleted modules
299  for( unsigned int i = 0; i < oldBuffer->GetCount(); i++ )
300  {
301  BOARD_ITEM* item = (BOARD_ITEM*) oldBuffer->GetPickedItem( i );
302  ITEM_PICKER picker( item, UR_DELETED );
303 
304  wxASSERT( item );
305 
306  if( currItemList.find( item ) == currItemList.end() )
307  deletedItemsList.PushItem( picker );
308  }
309 
310  // Mark deleted elements in undolist
311  for( unsigned int i = 0; i < deletedItemsList.GetCount(); i++ )
312  {
313  oldBuffer->PushItem( deletedItemsList.GetItemWrapper( i ) );
314  }
315 
316  // Find new modules
317  for( auto item : currentPcb->Modules() )
318  {
319  if( !oldBuffer->ContainsItem( item ) )
320  {
321  ITEM_PICKER picker( item, UR_NEW );
322  oldBuffer->PushItem( picker );
323  }
324  }
325 
326  for( auto item : currentPcb->Tracks() )
327  {
328  if( !oldBuffer->ContainsItem( item ) )
329  {
330  ITEM_PICKER picker( item, UR_NEW );
331  oldBuffer->PushItem( picker );
332  }
333  }
334 
335  for( auto item : currentPcb->Drawings() )
336  {
337  if( !oldBuffer->ContainsItem( item ) )
338  {
339  ITEM_PICKER picker( item, UR_NEW );
340  oldBuffer->PushItem( picker );
341  }
342  }
343 
344  for( int ii = 0; ii < currentPcb->GetAreaCount(); ii++ )
345  {
346  if( !oldBuffer->ContainsItem( (EDA_ITEM*) currentPcb->GetArea( ii ) ) )
347  {
348  ITEM_PICKER picker( (EDA_ITEM*) currentPcb->GetArea(
349  ii ), UR_NEW );
350  oldBuffer->PushItem( picker );
351  }
352  }
353 
354  if( oldBuffer->GetCount() )
355  {
356  OnModify();
357  GetScreen()->PushCommandToUndoList( oldBuffer );
358  }
359  else
360  {
361  delete oldBuffer;
362  }
363 
365 }
366 
367 
368 void PCB_EDIT_FRAME::buildActionPluginMenus( ACTION_MENU* actionMenu )
369 {
370  if( !actionMenu ) // Should not occur.
371  return;
372 
373  for( int ii = 0; ii < ACTION_PLUGINS::GetActionsCount(); ii++ )
374  {
375  wxMenuItem* item;
377  const wxBitmap& bitmap = ap->iconBitmap.IsOk() ? ap->iconBitmap : KiBitmap( hammer_xpm );
378 
379  item = AddMenuItem( actionMenu, wxID_ANY, ap->GetName(), ap->GetDescription(), bitmap );
380 
381  Connect( item->GetId(), wxEVT_COMMAND_MENU_SELECTED,
382  wxCommandEventHandler( PCB_EDIT_FRAME::OnActionPluginMenu ) );
383 
384  ACTION_PLUGINS::SetActionMenu( ii, item->GetId() );
385  }
386 }
387 
388 
389 void PCB_EDIT_FRAME::AddActionPluginTools()
390 {
391  bool need_separator = true;
392  const auto& orderedPlugins = GetOrderedActionPlugins();
393 
394  for( const auto& ap : orderedPlugins )
395  {
396  if( GetActionPluginButtonVisible( ap->GetPluginPath(), ap->GetShowToolbarButton() ) )
397  {
398 
399  if ( need_separator )
400  {
402  need_separator = false;
403  }
404 
405  // Add button
406  wxBitmap bitmap;
407 
408  if ( ap->iconBitmap.IsOk() )
409  bitmap = KiScaledBitmap( ap->iconBitmap, this );
410  else
411  bitmap = KiScaledBitmap( hammer_xpm, this );
412 
413  wxAuiToolBarItem* button = m_mainToolBar->AddTool(
414  wxID_ANY, wxEmptyString, bitmap, ap->GetName() );
415 
416  Connect( button->GetId(), wxEVT_COMMAND_MENU_SELECTED,
417  wxCommandEventHandler( PCB_EDIT_FRAME::OnActionPluginButton ) );
418 
419  // Link action plugin to button
420  ACTION_PLUGINS::SetActionButton( ap, button->GetId() );
421  }
422  }
423 }
424 
425 
426 std::vector<ACTION_PLUGIN*> PCB_EDIT_FRAME::GetOrderedActionPlugins()
427 {
428  std::vector<ACTION_PLUGIN*> plugins;
429  std::vector<ACTION_PLUGIN*> orderedPlugins;
430 
431  for( int i = 0; i < ACTION_PLUGINS::GetActionsCount(); i++ )
432  plugins.push_back( ACTION_PLUGINS::GetAction( i ) );
433 
434  // First add plugins that have entries in settings
435  for( const auto& pair : m_Settings->m_VisibleActionPlugins )
436  {
437  auto loc = std::find_if( plugins.begin(), plugins.end(),
438  [pair] ( ACTION_PLUGIN* plugin )
439  {
440  return plugin->GetPluginPath() == pair.first;
441  } );
442 
443  if( loc != plugins.end() )
444  {
445  orderedPlugins.push_back( *loc );
446  plugins.erase( loc );
447  }
448  }
449 
450  // Now append new plugins that have not been configured yet
451  for( auto remaining_plugin : plugins )
452  orderedPlugins.push_back( remaining_plugin );
453 
454  return orderedPlugins;
455 }
456 
457 
458 bool PCB_EDIT_FRAME::GetActionPluginButtonVisible( const wxString& aPluginPath, bool aPluginDefault )
459 {
460  auto& settings = m_Settings->m_VisibleActionPlugins;
461 
462  for( const auto& entry : settings )
463  {
464  if( entry.first == aPluginPath )
465  return entry.second;
466  }
467 
468  // Plugin is not in settings, return default.
469  return aPluginDefault;
470 }
471 
472 
473 #endif
virtual wxString GetName()=0
Function GetName.
wxString PyStringToWx(PyObject *aString)
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 OnModify() override
Function OnModify 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)
ACTION_MENU.
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:250
void ActivateGalCanvas() override
virtual wxString GetDescription()=0
Function GetDescription.
wxBitmap iconBitmap
Definition: action_plugin.h:50
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) ...
This file contains miscellaneous commonly used macros and functions.
virtual wxString GetPluginPath()=0
Function GetPluginPath.
virtual PICKED_ITEMS_LIST * PopCommandFromUndoList()
PopCommandFromUndoList return the last command to undo and remove it from list nothing is deleted.
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)
int GetAreaCount() const
Function GetAreaCount.
Definition: class_board.h:927
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:256
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.
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.
void SaveCopyInUndoList(BOARD_ITEM *aItemToCopy, UNDO_REDO_T aTypeCommand, const wxPoint &aTransformPoint=wxPoint(0, 0)) override
Function SaveCopyInUndoList Creates a new entry in undo list of commands.
Definition: undo_redo.cpp:182
PyObject * CallMethod(const char *aMethod, PyObject *aArglist=NULL)
void KiScaledSeparator(wxAuiToolBar *aToolbar, wxWindow *aWindow)
Add a separator to the given toolbar scaled the same way as KiScaledBitmap.
Definition: bitmap.cpp:167
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.
Class to handle a graphic segment.
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:205
BOARD holds information pertinent to a Pcbnew printed circuit board.
Definition: class_board.h:181
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.
EDA_ITEM is a base class for most all the KiCad significant classes, used in schematics and boards.
Definition: base_struct.h:163
wxString PyErrStringWithTraceback()
void ClearItemsList()
Function ClearItemsList deletes only the list of pickers, NOT the picked data itself.
PCB_SCREEN * GetScreen() const override
Return a pointer to a BASE_SCREEN or one of its derivatives.
bool ContainsItem(const EDA_ITEM *aItem) const
Function IsItemInList.
ZONE_CONTAINER * GetArea(int index) const
Function GetArea returns the Area (Zone Container) at a given index.
Definition: class_board.h:892
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:265
static void SetActionRunning(bool aRunning)
Function SetActionRunning.
TRACKS & Tracks()
Definition: class_board.h:247
wxString GetCategoryName() override
Function GetCategoryName.