You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

586 lines
15 KiB

  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. /**
  24. * @file pcbnew_action_plugins.cpp
  25. * @brief Class PCBNEW_PYTHON_ACTION_PLUGINS
  26. */
  27. #include "pcbnew_action_plugins.h"
  28. #include <python_scripting.h>
  29. #include <stdio.h>
  30. #include <macros.h>
  31. #include <pcbnew_id.h>
  32. #include <menus_helpers.h>
  33. #include <class_drawpanel.h> // m_canvas
  34. #include <class_board.h>
  35. #include <class_module.h>
  36. #include <class_track.h>
  37. #include <class_drawsegment.h>
  38. #include <class_zone.h>
  39. #include <board_commit.h>
  40. #include <kicad_device_context.h>
  41. PYTHON_ACTION_PLUGIN::PYTHON_ACTION_PLUGIN( PyObject* aAction )
  42. {
  43. PyLOCK lock;
  44. this->m_PyAction = aAction;
  45. Py_XINCREF( aAction );
  46. }
  47. PYTHON_ACTION_PLUGIN::~PYTHON_ACTION_PLUGIN()
  48. {
  49. PyLOCK lock;
  50. Py_XDECREF( this->m_PyAction );
  51. }
  52. PyObject* PYTHON_ACTION_PLUGIN::CallMethod( const char* aMethod, PyObject* aArglist )
  53. {
  54. PyLOCK lock;
  55. PyErr_Clear();
  56. // pFunc is a new reference to the desired method
  57. PyObject* pFunc = PyObject_GetAttrString( this->m_PyAction, aMethod );
  58. if( pFunc && PyCallable_Check( pFunc ) )
  59. {
  60. PyObject* result = PyObject_CallObject( pFunc, aArglist );
  61. if( PyErr_Occurred() )
  62. {
  63. wxMessageBox( PyErrStringWithTraceback(),
  64. _( "Exception on python action plugin code" ),
  65. wxICON_ERROR | wxOK );
  66. }
  67. if( result )
  68. {
  69. Py_XDECREF( pFunc );
  70. return result;
  71. }
  72. }
  73. else
  74. {
  75. wxString msg = wxString::Format( _( "Method \"%s\" not found, or not callable" ), aMethod );
  76. wxMessageBox( msg, _( "Unknown Method" ), wxICON_ERROR | wxOK );
  77. }
  78. if( pFunc )
  79. {
  80. Py_XDECREF( pFunc );
  81. }
  82. return NULL;
  83. }
  84. wxString PYTHON_ACTION_PLUGIN::CallRetStrMethod( const char* aMethod, PyObject* aArglist )
  85. {
  86. wxString ret;
  87. PyLOCK lock;
  88. PyObject* result = CallMethod( aMethod, aArglist );
  89. if( result )
  90. {
  91. const char* str_res = PyString_AsString( result );
  92. ret = FROM_UTF8( str_res );
  93. Py_DECREF( result );
  94. }
  95. return ret;
  96. }
  97. wxString PYTHON_ACTION_PLUGIN::GetCategoryName()
  98. {
  99. PyLOCK lock;
  100. return CallRetStrMethod( "GetCategoryName" );
  101. }
  102. wxString PYTHON_ACTION_PLUGIN::GetName()
  103. {
  104. PyLOCK lock;
  105. return CallRetStrMethod( "GetName" );
  106. }
  107. wxString PYTHON_ACTION_PLUGIN::GetDescription()
  108. {
  109. PyLOCK lock;
  110. return CallRetStrMethod( "GetDescription" );
  111. }
  112. bool PYTHON_ACTION_PLUGIN::GetShowToolbarButton()
  113. {
  114. PyLOCK lock;
  115. PyObject* result = CallMethod( "GetShowToolbarButton");
  116. return PyObject_IsTrue(result);
  117. }
  118. wxString PYTHON_ACTION_PLUGIN::GetIconFileName()
  119. {
  120. PyLOCK lock;
  121. return CallRetStrMethod( "GetIconFileName" );
  122. }
  123. wxString PYTHON_ACTION_PLUGIN::GetPluginPath()
  124. {
  125. PyLOCK lock;
  126. return CallRetStrMethod( "GetPluginPath" );
  127. }
  128. void PYTHON_ACTION_PLUGIN::Run()
  129. {
  130. PyLOCK lock;
  131. CallMethod( "Run" );
  132. }
  133. void* PYTHON_ACTION_PLUGIN::GetObject()
  134. {
  135. return (void*) m_PyAction;
  136. }
  137. void PYTHON_ACTION_PLUGINS::register_action( PyObject* aPyAction )
  138. {
  139. PYTHON_ACTION_PLUGIN* fw = new PYTHON_ACTION_PLUGIN( aPyAction );
  140. fw->register_action();
  141. }
  142. void PYTHON_ACTION_PLUGINS::deregister_action( PyObject* aPyAction )
  143. {
  144. // deregister also destroys the previously created "PYTHON_ACTION_PLUGIN object"
  145. ACTION_PLUGINS::deregister_object( (void*) aPyAction );
  146. }
  147. #if defined(KICAD_SCRIPTING) && defined(KICAD_SCRIPTING_ACTION_MENU)
  148. void PCB_EDIT_FRAME::OnActionPluginMenu( wxCommandEvent& aEvent )
  149. {
  150. ACTION_PLUGIN* actionPlugin = ACTION_PLUGINS::GetActionByMenu( aEvent.GetId() );
  151. if( actionPlugin )
  152. RunActionPlugin( actionPlugin );
  153. }
  154. void PCB_EDIT_FRAME::OnActionPluginButton( wxCommandEvent& aEvent )
  155. {
  156. ACTION_PLUGIN* actionPlugin = ACTION_PLUGINS::GetActionByButton( aEvent.GetId() );
  157. if( actionPlugin )
  158. RunActionPlugin( actionPlugin );
  159. }
  160. void PCB_EDIT_FRAME::RunActionPlugin( ACTION_PLUGIN* aActionPlugin )
  161. {
  162. PICKED_ITEMS_LIST itemsList;
  163. BOARD* currentPcb = GetBoard();
  164. bool fromEmpty = false;
  165. itemsList.m_Status = UR_CHANGED;
  166. OnModify();
  167. // Append tracks:
  168. for( BOARD_ITEM* item = currentPcb->m_Track; item != NULL; item = item->Next() )
  169. {
  170. ITEM_PICKER picker( item, UR_CHANGED );
  171. itemsList.PushItem( picker );
  172. }
  173. // Append modules:
  174. for( BOARD_ITEM* item = currentPcb->m_Modules; item != NULL; item = item->Next() )
  175. {
  176. ITEM_PICKER picker( item, UR_CHANGED );
  177. itemsList.PushItem( picker );
  178. }
  179. // Append drawings
  180. for( BOARD_ITEM* item = currentPcb->m_Drawings; item != NULL; item = item->Next() )
  181. {
  182. ITEM_PICKER picker( item, UR_CHANGED );
  183. itemsList.PushItem( picker );
  184. }
  185. // Append zones outlines
  186. for( int ii = 0; ii < currentPcb->GetAreaCount(); ii++ )
  187. {
  188. ITEM_PICKER picker( (EDA_ITEM*) currentPcb->GetArea(
  189. ii ), UR_CHANGED );
  190. itemsList.PushItem( picker );
  191. }
  192. // Append zones segm:
  193. for( BOARD_ITEM* item = currentPcb->m_SegZoneDeprecated; item != NULL; item = item->Next() )
  194. {
  195. ITEM_PICKER picker( item, UR_CHANGED );
  196. itemsList.PushItem( picker );
  197. }
  198. if( itemsList.GetCount() > 0 )
  199. SaveCopyInUndoList( itemsList, UR_CHANGED, wxPoint( 0.0, 0.0 ) );
  200. else
  201. fromEmpty = true;
  202. itemsList.ClearItemsList();
  203. // Execute plugin itself...
  204. ACTION_PLUGINS::SetActionRunning( true );
  205. aActionPlugin->Run();
  206. ACTION_PLUGINS::SetActionRunning( false );
  207. currentPcb->m_Status_Pcb = 0;
  208. // Get back the undo buffer to fix some modifications
  209. PICKED_ITEMS_LIST* oldBuffer = NULL;
  210. if( fromEmpty )
  211. {
  212. oldBuffer = new PICKED_ITEMS_LIST();
  213. oldBuffer->m_Status = UR_NEW;
  214. }
  215. else
  216. {
  217. oldBuffer = GetScreen()->PopCommandFromUndoList();
  218. wxASSERT( oldBuffer );
  219. }
  220. // Try do discover what was modified
  221. PICKED_ITEMS_LIST deletedItemsList;
  222. // Found deleted modules
  223. for( unsigned int i = 0; i < oldBuffer->GetCount(); i++ )
  224. {
  225. BOARD_ITEM* item = (BOARD_ITEM*) oldBuffer->GetPickedItem( i );
  226. ITEM_PICKER picker( item, UR_DELETED );
  227. wxASSERT( item );
  228. switch( item->Type() )
  229. {
  230. case PCB_NETINFO_T:
  231. case PCB_MARKER_T:
  232. case PCB_MODULE_T:
  233. case PCB_TRACE_T:
  234. case PCB_VIA_T:
  235. case PCB_LINE_T:
  236. case PCB_TEXT_T:
  237. case PCB_DIMENSION_T:
  238. case PCB_TARGET_T:
  239. case PCB_SEGZONE_T:
  240. // If item has a list it's mean that the element is on the board
  241. if( item->GetList() == NULL )
  242. {
  243. deletedItemsList.PushItem( picker );
  244. }
  245. break;
  246. case PCB_ZONE_AREA_T:
  247. {
  248. bool zoneFound = false;
  249. for( int ii = 0; ii < currentPcb->GetAreaCount(); ii++ )
  250. zoneFound |= currentPcb->GetArea( ii ) == item;
  251. if( !zoneFound )
  252. {
  253. deletedItemsList.PushItem( picker );
  254. }
  255. break;
  256. }
  257. default:
  258. wxString msg;
  259. msg.Printf( _( "(PCB_EDIT_FRAME::OnActionPlugin) needs work: "
  260. "BOARD_ITEM type (%d) not handled" ),
  261. item->Type() );
  262. wxFAIL_MSG( msg );
  263. break;
  264. }
  265. }
  266. // Mark deleted elements in undolist
  267. for( unsigned int i = 0; i < deletedItemsList.GetCount(); i++ )
  268. {
  269. oldBuffer->PushItem( deletedItemsList.GetItemWrapper( i ) );
  270. }
  271. // Find new modules
  272. for( BOARD_ITEM* item = currentPcb->m_Modules; item != NULL; item = item->Next() )
  273. {
  274. if( !oldBuffer->ContainsItem( item ) )
  275. {
  276. ITEM_PICKER picker( item, UR_NEW );
  277. oldBuffer->PushItem( picker );
  278. }
  279. }
  280. for( BOARD_ITEM* item = currentPcb->m_Track; item != NULL; item = item->Next() )
  281. {
  282. if( !oldBuffer->ContainsItem( item ) )
  283. {
  284. ITEM_PICKER picker( item, UR_NEW );
  285. oldBuffer->PushItem( picker );
  286. }
  287. }
  288. for( BOARD_ITEM* item = currentPcb->m_Drawings; item != NULL; item = item->Next() )
  289. {
  290. if( !oldBuffer->ContainsItem( item ) )
  291. {
  292. ITEM_PICKER picker( item, UR_NEW );
  293. oldBuffer->PushItem( picker );
  294. }
  295. }
  296. for( BOARD_ITEM* item = currentPcb->m_SegZoneDeprecated; item != NULL; item = item->Next() )
  297. {
  298. if( !oldBuffer->ContainsItem( item ) )
  299. {
  300. ITEM_PICKER picker( item, UR_NEW );
  301. oldBuffer->PushItem( picker );
  302. }
  303. }
  304. for( int ii = 0; ii < currentPcb->GetAreaCount(); ii++ )
  305. {
  306. if( !oldBuffer->ContainsItem( (EDA_ITEM*) currentPcb->GetArea( ii ) ) )
  307. {
  308. ITEM_PICKER picker( (EDA_ITEM*) currentPcb->GetArea(
  309. ii ), UR_NEW );
  310. oldBuffer->PushItem( picker );
  311. }
  312. }
  313. GetScreen()->PushCommandToUndoList( oldBuffer );
  314. if( IsGalCanvasActive() )
  315. {
  316. UseGalCanvas( GetGalCanvas() );
  317. }
  318. else
  319. {
  320. UpdateUserInterface();
  321. GetScreen()->SetModify();
  322. Refresh();
  323. }
  324. }
  325. void PCB_EDIT_FRAME::RebuildActionPluginMenus()
  326. {
  327. wxMenu* actionMenu = GetMenuBar()->FindItem( ID_TOOLBARH_PCB_ACTION_PLUGIN )->GetSubMenu();
  328. if( !actionMenu ) // Should not occur.
  329. return;
  330. // First, remove existing submenus, if they are too many
  331. wxMenuItemList list = actionMenu->GetMenuItems();
  332. // The first menuitems are the refresh menu and separator. do not count them
  333. int act_menu_count = -2;
  334. std::vector<wxMenuItem*> available_menus;
  335. for( auto iter = list.begin(); iter != list.end(); ++iter, act_menu_count++ )
  336. {
  337. if( act_menu_count < 0 )
  338. continue;
  339. wxMenuItem* item = *iter;
  340. if( act_menu_count < ACTION_PLUGINS::GetActionsCount() )
  341. {
  342. available_menus.push_back( item );
  343. continue;
  344. }
  345. // Remove menus which are not usable for our current plugin list
  346. Disconnect( item->GetId(), wxEVT_COMMAND_MENU_SELECTED,
  347. (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) &
  348. PCB_EDIT_FRAME::OnActionPluginMenu );
  349. actionMenu->Delete( item );
  350. }
  351. for( int ii = 0; ii < ACTION_PLUGINS::GetActionsCount(); ii++ )
  352. {
  353. wxMenuItem* item;
  354. ACTION_PLUGIN* ap = ACTION_PLUGINS::GetAction( ii );
  355. const wxBitmap& bitmap = ap->iconBitmap.IsOk() ? ap->iconBitmap : KiBitmap( hammer_xpm );
  356. if( ii < (int) available_menus.size() )
  357. {
  358. item = available_menus[ii];
  359. item->SetItemLabel( ap->GetName() );
  360. item->SetHelp( ap->GetDescription() );
  361. // On windows we need to set "unchecked" bitmap
  362. #if defined(__WXMSW__)
  363. item->SetBitmap( bitmap, false );
  364. #else
  365. item->SetBitmap( bitmap );
  366. #endif
  367. }
  368. else
  369. {
  370. item = AddMenuItem( actionMenu, wxID_ANY,
  371. ap->GetName(),
  372. ap->GetDescription(),
  373. bitmap );
  374. Connect( item->GetId(), wxEVT_COMMAND_MENU_SELECTED,
  375. (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) &
  376. PCB_EDIT_FRAME::OnActionPluginMenu );
  377. }
  378. ACTION_PLUGINS::SetActionMenu( ii, item->GetId() );
  379. }
  380. }
  381. void PCB_EDIT_FRAME::AddActionPluginTools()
  382. {
  383. bool need_separator = true;
  384. const auto& orderedPlugins = GetOrderedActionPlugins();
  385. for( const auto& ap : orderedPlugins )
  386. {
  387. if( GetActionPluginButtonVisible( ap->GetPluginPath(), ap->GetShowToolbarButton() ) )
  388. {
  389. if ( need_separator )
  390. {
  391. KiScaledSeparator( m_mainToolBar, this );
  392. need_separator = false;
  393. }
  394. // Add button
  395. wxBitmap bitmap;
  396. if ( ap->iconBitmap.IsOk() )
  397. bitmap = KiScaledBitmap( ap->iconBitmap, this );
  398. else
  399. bitmap = KiScaledBitmap( hammer_xpm, this );
  400. wxAuiToolBarItem* button = m_mainToolBar->AddTool(
  401. wxID_ANY, wxEmptyString, bitmap, ap->GetName() );
  402. Connect( button->GetId(), wxEVT_COMMAND_MENU_SELECTED,
  403. (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) &
  404. PCB_EDIT_FRAME::OnActionPluginButton );
  405. // Link action plugin to button
  406. ACTION_PLUGINS::SetActionButton( ap, button->GetId() );
  407. }
  408. }
  409. }
  410. void PCB_EDIT_FRAME::SetActionPluginSettings( const std::vector< std::pair<wxString, wxString> >& aPluginSettings )
  411. {
  412. m_configSettings.m_pluginSettings = aPluginSettings;
  413. }
  414. std::vector< std::pair<wxString, wxString> > PCB_EDIT_FRAME::GetActionPluginSettings()
  415. {
  416. return m_configSettings.m_pluginSettings;
  417. }
  418. std::vector<ACTION_PLUGIN*> PCB_EDIT_FRAME::GetOrderedActionPlugins()
  419. {
  420. std::vector<ACTION_PLUGIN*> orderedPlugins;
  421. const auto& pluginSettings = GetActionPluginSettings();
  422. // First add plugins that have entries in settings
  423. for( size_t ii = 0; ii < pluginSettings.size(); ii++ )
  424. {
  425. for( int jj = 0; jj < ACTION_PLUGINS::GetActionsCount(); jj++ )
  426. {
  427. if( ACTION_PLUGINS::GetAction( jj )->GetPluginPath() == pluginSettings[ii].first )
  428. orderedPlugins.push_back( ACTION_PLUGINS::GetAction( jj ) );
  429. }
  430. }
  431. // Now append new plugins that have not been configured yet
  432. for( int ii = 0; ii < ACTION_PLUGINS::GetActionsCount(); ii++ )
  433. {
  434. bool found = false;
  435. for( size_t jj = 0; jj < orderedPlugins.size(); jj++ )
  436. {
  437. if( ACTION_PLUGINS::GetAction( ii ) == orderedPlugins[jj] )
  438. found = true;
  439. }
  440. if ( !found )
  441. orderedPlugins.push_back( ACTION_PLUGINS::GetAction( ii ) );
  442. }
  443. return orderedPlugins;
  444. }
  445. bool PCB_EDIT_FRAME::GetActionPluginButtonVisible( const wxString& aPluginPath, bool aPluginDefault )
  446. {
  447. auto& settings = m_configSettings.m_pluginSettings;
  448. for(const auto& entry : settings )
  449. {
  450. if (entry.first == aPluginPath )
  451. return entry.second == wxT( "Visible" );
  452. }
  453. // Plugin is not in settings, return default.
  454. return aPluginDefault;
  455. }
  456. #endif