← Back to team overview

kicad-developers team mailing list archive

Re: [PATCH] RFC: toolbar button support for action plugins

 

I fixed whitespace issue and formatting (at least what I caught).
Fixed a bug that would not load plugin list properly if one plugin was
removed and another was added at the same time.
Also made plugin buttons scale same as the main buttons.

Please see updated patch attached.

> You should protect array dereference indices to prevent overflow.  It
would be a good idea to define a specific "no icon" index (-1 maybe?  or 0)
that returns a default icon (0) or prevents an icon from being loaded (-1).

I am not sure what place in code are you referring to here. Nowhere do I
reference icons by index. Icons are fields of action plugins and I get them
from a map, not a vector.

Would you prefer if I opened a PR on github? I know it won't be merged,
just to make exchanging comments on code easier during review.

Andrew

On Wed, Aug 22, 2018 at 2:03 PM Seth Hillbrand <seth@xxxxxxxxxxxxx> wrote:

> Thanks Andrew, I'll have a chance to test this in detail this weekend.
> The movie looks really nice.
>
> Short comments:
>
> - You should protect array dereference indices to prevent overflow.  It
> would be a good idea to define a specific "no icon" index (-1 maybe?  or 0)
> that returns a default icon (0) or prevents an icon from being loaded (-1).
>
> - It looks like your editor doesn't automatically clear trailing
> whitespace at the end of lines.  Can you check if there's an option in it
> that does that for you?
>
> - There are a couple small spacing errors (single line between function
> defs, space before closing parens)
>
> Overall, excellent work!  This feels like a nice addition for people who
> regularly use plugins.
>
> -Seth
>
> Am Mi., 22. Aug. 2018 um 04:28 Uhr schrieb Andrew Lutsenko <
> anlutsenko@xxxxxxxxx>:
>
>> Hi Seth,
>>
>> I built the settings dialog for action plugins. You can reorder and
>> enable/disable buttons for each plugin individually.
>>
>> Short demo:
>> https://i.imgur.com/33iJC7b.gif
>>
>> Squashed patch is attached. I've tested it on win, debian8 and debian9.
>> If it's easier to review diff can be viewed here as well:
>>
>> https://github.com/KiCad/kicad-source-mirror/compare/master...qu1ck:plugin-icon
>>
>> Also I've attached few dummy plugins to play with, 3 out of 4 have icons.
>>
>> Let me know if you have any comments.
>>
>> Thanks,
>> Andrew
>>
>> On Fri, Aug 17, 2018 at 3:02 PM Andrew Lutsenko <anlutsenko@xxxxxxxxx>
>> wrote:
>>
>>> Hi Seth,
>>>
>>> That makes sense. I will keep working on this feature and will ping this
>>> thread again once user configuration is implemented.
>>>
>>> Thanks,
>>> Andrew
>>>
>>> On Fri, Aug 17, 2018 at 10:03 AM Seth Hillbrand <seth@xxxxxxxxxxxxx>
>>> wrote:
>>>
>>>> Hi Andrew-
>>>>
>>>> I like the patch idea and your implementation approach is good.
>>>>
>>>> The coding style policy is located at
>>>> https://kicad-source-mirror.readthedocs.io/en/stable/Documentation/development/coding-style-policy/
>>>> We're not totally consistent but we try to ensure any new code follows it
>>>> and clean up the old code as we go.
>>>>
>>>> On the errors, please don't throw an error to the user.  It may be
>>>> useful to insert a wxLog() call that we could utilize in the future but
>>>> that is ignored for now.
>>>>
>>>> I'm opposed to merging patches that are partially complete.  And I
>>>> would consider the lack of user control over this feature problematic.
>>>> This is not a judgement on the patch as you've implemented it.  I really
>>>> like the functionality and think KiCad users will appreciate it as well.
>>>> However, a partially implemented feature creates the opportunity for
>>>> problems down the road that will distract developer time from the other
>>>> tasks they are working on.  If you do not have the time to fully implement
>>>> user control over this feature (I completely understand competing time
>>>> pressures), you may consider opening a bug report and attaching your patch
>>>> there for interested future developers.  Squashing the patchset for review
>>>> is also a good idea.
>>>>
>>>> Overall this looks really promising.
>>>>
>>>> Best-
>>>> Seth
>>>>
>>>> Am Do., 16. Aug. 2018 um 23:02 Uhr schrieb Andrew Lutsenko <
>>>> anlutsenko@xxxxxxxxx>:
>>>>
>>>>> Hi Seth
>>>>>
>>>>> I just checked out new preferences in pcbnew, looks much more
>>>>> organized than before.
>>>>> I totally can add a new tab there "pcbnew->Action plugins" and list
>>>>> the plugins there with option
>>>>> to remove toolbar icon. But that is a non-negligible amount of work.
>>>>> Will you hold off on merging
>>>>> my current patches until I implement that too?
>>>>> By default plugins will not show any buttons on toolbar, plugin
>>>>> writers will have to explicitly update
>>>>> their plugins and provide an icon for them to show up so you will not
>>>>> run into an issue with full
>>>>> toolbar for a while. See my screenshot in second email of the chain,
>>>>> it has 4 plugins but only
>>>>> 2 of them register with an icon and toolbar button.
>>>>>
>>>>> > headers get 1 space between function defs
>>>>> I tried to follow existing style in each file and didn't notice that
>>>>> it's not consistent across different files.
>>>>> action_plugin.h has two new lines between most functions but I can
>>>>> change it to one.
>>>>>
>>>>> What do you think about throwing an error to user if icon failed to
>>>>> load? Andrey Kuznetsov made a
>>>>> point that user can't do anything about it anyway. I agree that asking
>>>>> users to fix plugin icon declaration
>>>>> is a bit much and developer will be able to see that icon didn't load
>>>>> without the error too.
>>>>>
>>>>> Andrew
>>>>>
>>>>> On Thu, Aug 16, 2018 at 10:22 PM Seth Hillbrand <seth@xxxxxxxxxxxxx>
>>>>> wrote:
>>>>>
>>>>>> Hi Andrew-
>>>>>>
>>>>>> I like the idea.  Aside from minor formatting (headers get 1 space
>>>>>> between function defs, need a space before the if block), the patch looks
>>>>>> good.
>>>>>>
>>>>>> However, I wouldn't want everything showing on my toolbar (speaking
>>>>>> as someone who has 10 plugins installed, 5 of which get regular use).  I'd
>>>>>> prefer the option to be configurable.  This should probably be in the
>>>>>> preferences pane that Jeff has been re-working.
>>>>>>
>>>>>> -Seth
>>>>>>
>>>>>> Am Do., 16. Aug. 2018 um 22:11 Uhr schrieb Andrew Lutsenko <
>>>>>> anlutsenko@xxxxxxxxx>:
>>>>>>
>>>>>>> Hi Clemens,
>>>>>>>
>>>>>>> See sample plugin attached. Extract it into kicad's
>>>>>>> share/scripting/plugins folder.
>>>>>>> One of other scanned directories that are documented in
>>>>>>> kicadplugins.i
>>>>>>> <https://github.com/KiCad/kicad-source-mirror/blob/6fdc5972f8431b4d5831a32649e67bfe20d05de8/scripting/kicadplugins.i#L180> should
>>>>>>> work too.
>>>>>>>
>>>>>>> Or are you asking to update docs in the repo?
>>>>>>> Documentation/development/pcbnew-plugins.md seems like the right
>>>>>>> place.
>>>>>>> I will update it once committers agree with the path I've chosen to
>>>>>>> implement this.
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> On Thu, Aug 16, 2018 at 4:48 AM Clemens Koller <cko@xxxxxxxxx>
>>>>>>> wrote:
>>>>>>>
>>>>>>>> Hello,  Andrew!
>>>>>>>>
>>>>>>>> I am somehow missing the Python example you are giving in your
>>>>>>>> patch.
>>>>>>>> Can you add this a simple example to the sources to get a
>>>>>>>> basic/dummy skeleton working right from scratch?
>>>>>>>>
>>>>>>>> Regards,
>>>>>>>>
>>>>>>>> Clemens
>>>>>>>>
>>>>>>>>
>>>>>>>> On 15/08/2018 11.31, Andrew Lutsenko wrote:
>>>>>>>> > Hi KiCad devs,
>>>>>>>> >
>>>>>>>> > I am proposing an addition to plugin system.
>>>>>>>> > Probably most will agree that menus suck. Toolbars suck less :)
>>>>>>>> >
>>>>>>>> > In my plugin I added a dirty hack to modify top toolbar from
>>>>>>>> plugin init code to add a button
>>>>>>>> > that calls plugins run() method. It is broken on linux X11 and is
>>>>>>>> not a sustainable way others
>>>>>>>> > can add buttons in their plugins. But having a button was quite
>>>>>>>> popular among users so I
>>>>>>>> > decided to implement this functionality directly in pcbnew.
>>>>>>>> >
>>>>>>>> > I introduced one more field plugin writers can define in
>>>>>>>> defaults() that contains path to png icon
>>>>>>>> > and if that string is not empty, pcbnew will attempt to load that
>>>>>>>> icon and add a button to top
>>>>>>>> > toolbar with action that calls the same run() method. I traced in
>>>>>>>> code how plugin action menu
>>>>>>>> > is generated and added similar logic for buttons.
>>>>>>>> >
>>>>>>>> > Here is how the result looks like:
>>>>>>>> >
>>>>>>>> > https://i.imgur.com/f3xg1FE.gif
>>>>>>>> >
>>>>>>>> > Sample dummy plugin __init__.py code:
>>>>>>>> >
>>>>>>>> > import os
>>>>>>>> > import pcbnew
>>>>>>>> > import wx
>>>>>>>> >
>>>>>>>> > class Plugin1(pcbnew.ActionPlugin):
>>>>>>>> >
>>>>>>>> >     def defaults(self):
>>>>>>>> >         self.name <http://self.name> = "Dummy Plugin 1"
>>>>>>>> >         self.category = "Read PCB"
>>>>>>>> >         self.description = ""
>>>>>>>> >         self.icon_file_name =
>>>>>>>> os.path.join(os.path.dirname(__file__), 'icon.png')
>>>>>>>> >
>>>>>>>> >     def Run(self):
>>>>>>>> >         wx.MessageBox("Plugin 1")
>>>>>>>> >
>>>>>>>> > Plugin1().register()
>>>>>>>> >
>>>>>>>> > It's as simple as that.
>>>>>>>> >
>>>>>>>> > The patch is attached. It probably needs some error checking but
>>>>>>>> seems to be working great.
>>>>>>>> > Tested in win64 so far.
>>>>>>>> > I'm open to suggestions on how to get it to good state, I will
>>>>>>>> also test on linux asap.
>>>>>>>> >
>>>>>>>> > Regards,
>>>>>>>> > Andrew
>>>>>>>> >
>>>>>>>> > _______________________________________________
>>>>>>>> > Mailing list: https://launchpad.net/~kicad-developers
>>>>>>>> > Post to     : kicad-developers@xxxxxxxxxxxxxxxxxxx
>>>>>>>> > Unsubscribe : https://launchpad.net/~kicad-developers
>>>>>>>> > More help   : https://help.launchpad.net/ListHelp
>>>>>>>> >
>>>>>>>>
>>>>>>>> _______________________________________________
>>>>>>>> Mailing list: https://launchpad.net/~kicad-developers
>>>>>>>> Post to     : kicad-developers@xxxxxxxxxxxxxxxxxxx
>>>>>>>> Unsubscribe : https://launchpad.net/~kicad-developers
>>>>>>>> More help   : https://help.launchpad.net/ListHelp
>>>>>>>>
>>>>>>> _______________________________________________
>>>>>>> Mailing list: https://launchpad.net/~kicad-developers
>>>>>>> Post to     : kicad-developers@xxxxxxxxxxxxxxxxxxx
>>>>>>> Unsubscribe : https://launchpad.net/~kicad-developers
>>>>>>> More help   : https://help.launchpad.net/ListHelp
>>>>>>>
>>>>>>
From 7a494de7f02a5c840cd2bd333d3fd93b66272a8f Mon Sep 17 00:00:00 2001
From: qu1ck <anlutsenko@xxxxxxxxx>
Date: Wed, 15 Aug 2018 02:26:32 -0700
Subject: [PATCH] Add toolbar buttons for action plugins

Add icons to action menu items as well

Fix backwards compatibility for plugins that don't define icon

Fix compilation errors for action plugin icon code

Fixes compilation with actio menu option off.
Fixes compilation error on non windows platforms.

Make showing toolbar button optional for action plugins

Add "Action Plugins" panel stub in pcbnew settings

Add action plugin grid form and stub

Load action plugin list in pcbnew prefs

Add category, description and path to action plugin grid

Store and read action plugin settings display buttons accordingly

Add action plugin ordering buttons in preferences

Don't show error on icon load failure

Fix build with actions menu disabled

Adjust action plugins grid properties

Fix bug with plugin order when one is removed and another added

Also fix formatting

Scale plugin icons on toolbar
---
 common/bitmap.cpp                             |  19 +
 common/widgets/grid_icon_text_helpers.cpp     |  41 +-
 include/bitmap_types.h                        |   8 +
 include/widgets/grid_icon_text_helpers.h      |  19 +-
 pcbnew/CMakeLists.txt                         |   9 +-
 pcbnew/action_plugin.cpp                      |  54 ++
 pcbnew/action_plugin.h                        |  60 +-
 .../dialogs/panel_pcbnew_action_plugins.cpp   | 203 ++++++
 pcbnew/dialogs/panel_pcbnew_action_plugins.h  |  59 ++
 .../panel_pcbnew_action_plugins_base.cpp      |  96 +++
 .../panel_pcbnew_action_plugins_base.fbp      | 578 ++++++++++++++++++
 .../panel_pcbnew_action_plugins_base.h        |  56 ++
 pcbnew/pcb_edit_frame.cpp                     |   2 +
 pcbnew/pcb_edit_frame.h                       |  39 +-
 pcbnew/pcb_general_settings.cpp               |  45 ++
 pcbnew/pcb_general_settings.h                 |   5 +
 pcbnew/pcbnew_config.cpp                      |   4 +
 pcbnew/swig/pcbnew_action_plugins.cpp         | 165 ++++-
 pcbnew/swig/pcbnew_action_plugins.h           |   3 +
 pcbnew/tool_pcb_editor.cpp                    |   4 +
 scripting/kicadplugins.i                      |  23 +-
 21 files changed, 1478 insertions(+), 14 deletions(-)
 create mode 100644 pcbnew/dialogs/panel_pcbnew_action_plugins.cpp
 create mode 100644 pcbnew/dialogs/panel_pcbnew_action_plugins.h
 create mode 100644 pcbnew/dialogs/panel_pcbnew_action_plugins_base.cpp
 create mode 100644 pcbnew/dialogs/panel_pcbnew_action_plugins_base.fbp
 create mode 100644 pcbnew/dialogs/panel_pcbnew_action_plugins_base.h

diff --git a/common/bitmap.cpp b/common/bitmap.cpp
index 031b2cc55..bce8344c5 100644
--- a/common/bitmap.cpp
+++ b/common/bitmap.cpp
@@ -144,6 +144,25 @@ wxBitmap KiScaledBitmap( BITMAP_DEF aBitmap, EDA_BASE_FRAME* aWindow )
 }
 
 
+wxBitmap KiScaledBitmap( const wxBitmap& aBitmap, EDA_BASE_FRAME* aWindow )
+{
+    const int scale = get_scale_factor( aWindow );
+
+    if( scale == 4)
+    {
+        return wxBitmap( aBitmap );
+    }
+    else
+    {
+        wxImage image = aBitmap.ConvertToImage();
+        image.Rescale( scale * image.GetWidth() / 4, scale * image.GetHeight() / 4,
+            wxIMAGE_QUALITY_BILINEAR );
+
+        return wxBitmap( image );
+    }
+}
+
+
 void KiScaledSeparator( wxAuiToolBar* aToolbar, EDA_BASE_FRAME* aWindow )
 {
     const int scale = get_scale_factor( aWindow );
diff --git a/common/widgets/grid_icon_text_helpers.cpp b/common/widgets/grid_icon_text_helpers.cpp
index 8d348c7d0..feb04bb34 100644
--- a/common/widgets/grid_icon_text_helpers.cpp
+++ b/common/widgets/grid_icon_text_helpers.cpp
@@ -27,7 +27,7 @@
 #include <wx/dc.h>
 
 
-//---- Grid helpers: custom wxGridCellRenderer ------------------------------------------
+//---- Grid helpers: custom wxGridCellRenderer that renders icon and a label ------------
 
 
 GRID_CELL_ICON_TEXT_RENDERER::GRID_CELL_ICON_TEXT_RENDERER( const std::vector<BITMAP_DEF>& icons,
@@ -68,6 +68,45 @@ void GRID_CELL_ICON_TEXT_RENDERER::Draw( wxGrid& aGrid, wxGridCellAttr& aAttr, w
     aGrid.DrawTextRectangle( aDC, value, rect, wxALIGN_LEFT, wxALIGN_CENTRE );
 }
 
+//---- Grid helpers: custom wxGridCellRenderer that renders just an icon ----------------
+//
+// Note: this renderer is supposed to be used with read only cells
+
+GRID_CELL_ICON_RENDERER::GRID_CELL_ICON_RENDERER(const wxBitmap& icon) : m_icon( icon )
+{
+}
+
+
+void GRID_CELL_ICON_RENDERER::Draw( wxGrid& aGrid, wxGridCellAttr& aAttr, wxDC& aDC,
+                                    const wxRect& aRect, int aRow, int aCol, bool isSelected )
+{
+    wxRect rect = aRect;
+    rect.Inflate( -1 );
+
+    // erase background
+    wxGridCellRenderer::Draw( aGrid, aAttr, aDC, aRect, aRow, aCol, isSelected );
+
+    // Draw icon
+    if( m_icon.IsOk() )
+    {
+        aDC.DrawBitmap( m_icon,
+                        rect.GetLeft() + (rect.GetWidth() - m_icon.GetWidth()) / 2,
+                        rect.GetTop() + (rect.GetHeight() - m_icon.GetHeight()) / 2,
+                        true );
+    }
+}
+
+
+wxSize GRID_CELL_ICON_RENDERER::GetBestSize(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, int row, int col)
+{
+    return wxSize( m_icon.GetWidth() + 6, m_icon.GetHeight() + 4 );
+}
+
+
+wxGridCellRenderer* GRID_CELL_ICON_RENDERER::Clone() const
+{
+    return new GRID_CELL_ICON_RENDERER( m_icon );
+}
 
 
 //---- Grid helpers: custom wxGridCellEditor ------------------------------------------
diff --git a/include/bitmap_types.h b/include/bitmap_types.h
index 8657df496..f703ddbbd 100644
--- a/include/bitmap_types.h
+++ b/include/bitmap_types.h
@@ -73,6 +73,14 @@ wxBitmap KiBitmap( BITMAP_DEF aBitmap );
  */
 wxBitmap KiScaledBitmap( BITMAP_DEF aBitmap, EDA_BASE_FRAME* aWindow );
 
+/**
+ * Function KiScaledBitmap
+ * Overload of the above function that takes another wxBitmap as a parameter
+ *
+ * @param aBitmap bitmap definition
+ * @param aWindow target window for scaling context
+ */
+wxBitmap KiScaledBitmap( const wxBitmap& aBitmap, EDA_BASE_FRAME* aWindow );
 
 /**
  * Function KiScaledSeparator
diff --git a/include/widgets/grid_icon_text_helpers.h b/include/widgets/grid_icon_text_helpers.h
index 9e1482959..e859f117b 100644
--- a/include/widgets/grid_icon_text_helpers.h
+++ b/include/widgets/grid_icon_text_helpers.h
@@ -34,7 +34,7 @@
 class wxGrid;
 
 
-//---- Grid helpers: custom wxGridCellRenderer ------------------------------------------
+//---- Grid helpers: custom wxGridCellRenderer that renders icon and a label ------------
 
 class GRID_CELL_ICON_TEXT_RENDERER : public wxGridCellStringRenderer
 {
@@ -49,6 +49,23 @@ private:
     const wxArrayString&           m_names;
 };
 
+//---- Grid helpers: custom wxGridCellRenderer that renders just an icon ----------------
+//
+// Note: use with read only cells
+
+class GRID_CELL_ICON_RENDERER : public wxGridCellRenderer
+{
+public:
+    GRID_CELL_ICON_RENDERER( const wxBitmap& icon );
+
+    void Draw( wxGrid& aGrid, wxGridCellAttr& aAttr, wxDC& aDC,
+               const wxRect& aRect, int aRow, int aCol, bool isSelected ) override;
+    wxSize GetBestSize(wxGrid & grid, wxGridCellAttr & attr, wxDC & dc, int row, int col) override;
+    wxGridCellRenderer* Clone() const override;
+
+private:
+    const wxBitmap& m_icon;
+};
 
 //---- Grid helpers: custom wxGridCellEditor ------------------------------------------
 //
diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt
index d9f55dd4f..a6667033b 100644
--- a/pcbnew/CMakeLists.txt
+++ b/pcbnew/CMakeLists.txt
@@ -152,8 +152,6 @@ set( PCBNEW_DIALOGS
     dialogs/panel_modedit_settings_base.cpp
     dialogs/panel_pcbnew_display_options.cpp
     dialogs/panel_pcbnew_display_options_base.cpp
-    dialogs/panel_pcbnew_display_options.cpp
-    dialogs/panel_pcbnew_display_options_base.cpp
     dialogs/panel_pcbnew_settings.cpp
     dialogs/panel_pcbnew_settings_base.cpp
     dialogs/panel_setup_mask_and_paste.cpp
@@ -174,6 +172,13 @@ set( PCBNEW_DIALOGS
     ${GITHUB_3DLIBRARIES_WIZARD}
     )
 
+if( KICAD_SCRIPTING AND KICAD_SCRIPTING_ACTION_MENU )
+    set( PCBNEW_DIALOGS ${PCBNEW_DIALOGS}
+        dialogs/panel_pcbnew_action_plugins.cpp
+        dialogs/panel_pcbnew_action_plugins_base.cpp
+        )
+endif()
+
 set( PCBNEW_IMPORT_DXF
     import_dxf/dialog_dxf_import.cpp
     import_dxf/dialog_dxf_import_base.cpp
diff --git a/pcbnew/action_plugin.cpp b/pcbnew/action_plugin.cpp
index c10d0fafe..08c23fd8e 100644
--- a/pcbnew/action_plugin.cpp
+++ b/pcbnew/action_plugin.cpp
@@ -79,6 +79,46 @@ int ACTION_PLUGINS::GetActionMenu( int aIndex )
 }
 
 
+ACTION_PLUGIN* ACTION_PLUGINS::GetActionByButton( int aButton )
+{
+    int max = GetActionsCount();
+
+    for( int i = 0; i < max; i++ )
+    {
+        if( m_actionsList[i]->m_actionButtonId == aButton )
+            return m_actionsList[i];
+    }
+
+    return NULL;
+}
+
+
+void ACTION_PLUGINS::SetActionButton( ACTION_PLUGIN* aAction, int idButton )
+{
+    aAction->m_actionButtonId = idButton;
+}
+
+
+int ACTION_PLUGINS::GetActionButton( int aIndex )
+{
+    return m_actionsList[aIndex]->m_actionButtonId;
+}
+
+
+ACTION_PLUGIN* ACTION_PLUGINS::GetActionByPath(const wxString& aPath)
+{
+    for( int i = 0; i < GetActionsCount() ; i++ )
+    {
+        if( m_actionsList[ i ]->GetPluginPath() == aPath)
+        {
+            return m_actionsList[ i ];
+        }
+    }
+
+    return NULL;
+}
+
+
 ACTION_PLUGIN* ACTION_PLUGINS::GetAction( const wxString& aName )
 {
     int max = GetActionsCount();
@@ -127,6 +167,20 @@ void ACTION_PLUGINS::register_action( ACTION_PLUGIN* aAction )
         }
     }
 
+    // Load icon if supplied
+    if (!aAction->GetIconFileName().IsEmpty())
+    {
+        {
+            wxLogNull eat_errors;
+            aAction->iconBitmap.LoadFile( aAction->GetIconFileName() , wxBITMAP_TYPE_PNG );
+        }
+
+        if ( !aAction->iconBitmap.IsOk() )
+        {
+            wxLogVerbose( "Failed to load icon " + aAction->GetIconFileName() + " for action plugin " );
+        }
+    }
+
     m_actionsList.push_back( aAction );
 }
 
diff --git a/pcbnew/action_plugin.h b/pcbnew/action_plugin.h
index ff3ed1455..c2314e033 100644
--- a/pcbnew/action_plugin.h
+++ b/pcbnew/action_plugin.h
@@ -44,9 +44,16 @@ public:
     // m_actionMenuId set to 0 means the corresponding menuitem to call this
     // action is not yet created
     int m_actionMenuId;
+    // Same for button id
+    int m_actionButtonId;
+    // Icon for the action button and menu entry
+    wxBitmap iconBitmap;
+    // If show_on_toolbar is true a button will be added to top toolbar
+    bool show_on_toolbar;
 
 public:
-    ACTION_PLUGIN() : m_actionMenuId( 0 ) {}
+    ACTION_PLUGIN() : m_actionMenuId( 0 ), m_actionButtonId( 0 ),
+                      show_on_toolbar( false ) {}
     virtual ~ACTION_PLUGIN();
 
     /**
@@ -68,6 +75,24 @@ public:
      */
     virtual wxString GetDescription() = 0;
 
+    /**
+     * Function GetShowToolbarButton
+     * @return true if button should be shown on top toolbar
+     */
+    virtual bool GetShowToolbarButton() = 0;
+
+    /**
+     * Function GetIconFileName
+     * @return a path to icon for the action plugin button
+     */
+    virtual wxString GetIconFileName() = 0;
+
+    /**
+     * Function GetPluginPath
+     * @return a path this plugin was loaded from
+     */
+    virtual wxString GetPluginPath() = 0;
+
     /**
      * Function GetObject
      * This method gets the pointer to the object from where this action constructs
@@ -137,7 +162,6 @@ public:
      */
     static void SetActionMenu( int aIndex, int idMenu );
 
-
     /**
      * Function GetActionMenu
      * Provide menu id for a plugin index
@@ -146,7 +170,6 @@ public:
      */
     static int GetActionMenu( int aIndex );
 
-
     /**
      * Function GetActionByMenu
      * find action plugin associated to a menu id
@@ -155,6 +178,37 @@ public:
      */
     static ACTION_PLUGIN* GetActionByMenu( int aMenu );
 
+    /**
+     * Function SetActionButton
+     * Associate a button id to an action plugin
+     * @param aAction is the action
+     * @param idButton is the associated menuitem id
+     */
+    static void SetActionButton( ACTION_PLUGIN* aAction, int idButton );
+
+    /**
+     * Function GetActionButton
+     * Provide button id for a plugin index
+     * @param aIndex is the action index
+     * @return associated menuitem id
+     */
+    static int GetActionButton( int aIndex );
+
+    /**
+     * Function GetActionByButton
+     * find action plugin associated to a button id
+     * @param aMenu is the button id (defined with SetActionButton)
+     * @return the associated ACTION_PLUGIN (or null if not found)
+     */
+    static ACTION_PLUGIN* GetActionByButton( int aButton );
+
+    /**
+     * Function GetActionByPath
+     * find action plugin by module path
+     * @param aPath the path of plugin
+     * @return the corresponding ACTION_PLUGIN (or null if not found)
+     */
+    static ACTION_PLUGIN* GetActionByPath( const wxString& aPath );
 
     /**
      * Function GetAction
diff --git a/pcbnew/dialogs/panel_pcbnew_action_plugins.cpp b/pcbnew/dialogs/panel_pcbnew_action_plugins.cpp
new file mode 100644
index 000000000..601406769
--- /dev/null
+++ b/pcbnew/dialogs/panel_pcbnew_action_plugins.cpp
@@ -0,0 +1,203 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2018 Andrew Lutsenko, anlutsenko at gmail dot com
+ * Copyright (C) 1992-2018 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <pcb_edit_frame.h>
+#include <panel_pcbnew_action_plugins.h>
+#include <widgets/paged_dialog.h>
+#include <widgets/grid_icon_text_helpers.h>
+#include <bitmaps.h>
+#include <action_plugin.h>
+#include <grid_tricks.h>
+
+
+PANEL_PCBNEW_ACTION_PLUGINS::PANEL_PCBNEW_ACTION_PLUGINS( PCB_EDIT_FRAME* aFrame, PAGED_DIALOG* aWindow ) :
+    PANEL_PCBNEW_ACTION_PLUGINS_BASE( aWindow->GetTreebook() ),
+    m_frame( aFrame )
+{
+    m_genericIcon = KiBitmap( hammer_xpm );
+    m_grid->PushEventHandler( new GRID_TRICKS( m_grid ) );
+
+    m_moveUpButton->SetBitmap( KiBitmap( up_xpm ) );
+    m_moveDownButton->SetBitmap( KiBitmap( down_xpm ) );
+    m_reloadButton->SetBitmap( KiBitmap( reload_xpm ) );
+}
+
+
+PANEL_PCBNEW_ACTION_PLUGINS::~PANEL_PCBNEW_ACTION_PLUGINS()
+{
+    m_grid->PopEventHandler( true );
+}
+
+
+void PANEL_PCBNEW_ACTION_PLUGINS::OnGridCellClick( wxGridEvent& event )
+{
+    SelectRow( event.GetRow() );
+}
+
+
+void PANEL_PCBNEW_ACTION_PLUGINS::SelectRow( int aRow )
+{
+    m_grid->ClearSelection();
+    m_grid->SelectRow( aRow );
+}
+
+
+void PANEL_PCBNEW_ACTION_PLUGINS::OnMoveUpButtonClick( wxCommandEvent& event )
+{
+    auto selectedRows = m_grid->GetSelectedRows();
+
+    // If nothing is selected or multiple rows are selected don't do anything.
+    if( selectedRows.size() != 1)
+    {
+        return;
+    }
+
+    int selectedRow = selectedRows[ 0 ];
+
+    // If first row is selected, then it can't go any further up.
+    if( selectedRow == 0 )
+    {
+        return;
+    }
+
+    SwapRows( selectedRow, selectedRow - 1 );
+
+    SelectRow( selectedRow - 1 );
+}
+
+
+void PANEL_PCBNEW_ACTION_PLUGINS::OnMoveDownButtonClick( wxCommandEvent& event )
+{
+    auto selectedRows = m_grid->GetSelectedRows();
+
+    // If nothing is selected or multiple rows are selected don't do anything.
+    if( selectedRows.size() != 1)
+    {
+        return;
+    }
+
+    int selectedRow = selectedRows[ 0 ];
+
+    // If last row is selected, then it can't go any further down.
+    if( selectedRow + 1 == m_grid->GetNumberRows() )
+    {
+        return;
+    }
+
+    SwapRows( selectedRow, selectedRow + 1 );
+
+    SelectRow( selectedRow + 1 );
+}
+
+
+void PANEL_PCBNEW_ACTION_PLUGINS::SwapRows( int aRowA, int aRowB )
+{
+    m_grid->Freeze();
+
+    // Swap all columns except icon
+    wxString tempStr;
+
+    for( int column = 1; column < m_grid->GetNumberCols(); column++ )
+    {
+        tempStr = m_grid->GetCellValue( aRowA, column );
+        m_grid->SetCellValue( aRowA, column, m_grid->GetCellValue( aRowB, column ) );
+        m_grid->SetCellValue( aRowB, column, tempStr );
+    }
+
+    // Swap icon column renderers
+    auto cellRenderer = m_grid->GetCellRenderer( aRowA, 0 );
+    m_grid->SetCellRenderer( aRowA, 0, m_grid->GetCellRenderer( aRowB, 0) );
+    m_grid->SetCellRenderer( aRowB, 0, cellRenderer );
+
+    m_grid->Thaw();
+}
+
+
+void PANEL_PCBNEW_ACTION_PLUGINS::OnReloadButtonClick( wxCommandEvent& event )
+{
+    m_frame->PythonPluginsReload();
+    TransferDataToWindow();
+}
+
+
+bool PANEL_PCBNEW_ACTION_PLUGINS::TransferDataFromWindow()
+{
+    std::vector< std::pair<wxString, wxString> > pluginSettings;
+
+    for( int ii = 0; ii < m_grid->GetNumberRows(); ii++ )
+    {
+        pluginSettings.push_back( std::make_pair(
+                                    m_grid->GetCellValue( ii, 5 ),
+                                    m_grid->GetCellValue( ii, 1 ) == wxT("1") ? wxT( "Visible" ) : wxT( "Hidden" )
+                                ) );
+    }
+
+    m_frame->SetActionPluginSettings( pluginSettings );
+
+    return true;
+}
+
+
+bool PANEL_PCBNEW_ACTION_PLUGINS::TransferDataToWindow()
+{
+    m_grid->Freeze();
+    m_grid->DeleteRows( 0, m_grid->GetNumberRows() );
+
+    const auto& orderedPlugins = m_frame->GetOrderedActionPlugins();
+    m_grid->AppendRows( orderedPlugins.size() );
+
+    for( const auto& entry : orderedPlugins )
+    {
+        int row = entry.first;
+        ACTION_PLUGIN* ap = entry.second;
+
+        // Icon
+        m_grid->SetCellRenderer( row, 0, new GRID_CELL_ICON_RENDERER(
+                                 ap->iconBitmap.IsOk() ? ap->iconBitmap : m_genericIcon ) );
+
+        // Toolbar button checkbox
+        m_grid->SetCellRenderer( row, 1, new wxGridCellBoolRenderer() );
+        m_grid->SetCellAlignment( row, 1, wxALIGN_CENTER, wxALIGN_CENTER );
+
+        bool showButton = m_frame->GetActionPluginButtonVisible(
+                ap->GetPluginPath(), ap->GetShowToolbarButton() );
+
+        m_grid->SetCellValue( row, 1, showButton ? wxT( "1" ) : wxEmptyString );
+
+        // Name
+        m_grid->SetCellValue( row, 2, ap->GetName() );
+
+        // Category
+        m_grid->SetCellValue( row, 3, ap->GetCategoryName() );
+
+        // Description
+        m_grid->SetCellValue( row, 4, ap->GetDescription() );
+
+        // Path
+        m_grid->SetCellValue( row, 5, ap->GetPluginPath() );
+    }
+
+    m_grid->AutoSizeColumns();
+    m_grid->AutoSizeRows();
+
+    m_grid->Thaw();
+
+    return true;
+}
diff --git a/pcbnew/dialogs/panel_pcbnew_action_plugins.h b/pcbnew/dialogs/panel_pcbnew_action_plugins.h
new file mode 100644
index 000000000..e36b06cae
--- /dev/null
+++ b/pcbnew/dialogs/panel_pcbnew_action_plugins.h
@@ -0,0 +1,59 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2018 Andrew Lutsenko, anlutsenko at gmail dot com
+ * Copyright (C) 1992-2018 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "panel_pcbnew_action_plugins_base.h"
+
+class PANEL_PCBNEW_ACTION_PLUGINS : public PANEL_PCBNEW_ACTION_PLUGINS_BASE
+{
+public:
+    PANEL_PCBNEW_ACTION_PLUGINS ( PCB_EDIT_FRAME* aFrame, PAGED_DIALOG* aWindow );
+
+    bool TransferDataFromWindow() override;
+    bool TransferDataToWindow() override;
+    ~PANEL_PCBNEW_ACTION_PLUGINS() override;
+
+    /**
+     * Selects a whole row
+     */
+    void OnGridCellClick( wxGridEvent& event ) override;
+
+    /**
+     * Moves plugin up in the grid
+     */
+    void OnMoveUpButtonClick( wxCommandEvent& event ) override;
+
+    /**
+     * Moves plugin down in the grid
+     */
+    void OnMoveDownButtonClick( wxCommandEvent& event ) override;
+
+    /**
+     * Reloads plugins and updates grid
+     */
+    void OnReloadButtonClick( wxCommandEvent& event ) override;
+
+private:
+    PCB_EDIT_FRAME*    m_frame;
+    wxBitmap m_genericIcon;
+
+    void SwapRows( int aRowA, int aRowB );
+    void SelectRow( int aRow );
+};
+
diff --git a/pcbnew/dialogs/panel_pcbnew_action_plugins_base.cpp b/pcbnew/dialogs/panel_pcbnew_action_plugins_base.cpp
new file mode 100644
index 000000000..3519b91cf
--- /dev/null
+++ b/pcbnew/dialogs/panel_pcbnew_action_plugins_base.cpp
@@ -0,0 +1,96 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version Jul 11 2018)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO *NOT* EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#include "panel_pcbnew_action_plugins_base.h"
+
+///////////////////////////////////////////////////////////////////////////
+
+PANEL_PCBNEW_ACTION_PLUGINS_BASE::PANEL_PCBNEW_ACTION_PLUGINS_BASE( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style ) : wxPanel( parent, id, pos, size, style )
+{
+	wxBoxSizer* bPanelSizer;
+	bPanelSizer = new wxBoxSizer( wxHORIZONTAL );
+	
+	wxBoxSizer* bGridSizer;
+	bGridSizer = new wxBoxSizer( wxVERTICAL );
+	
+	m_grid = new wxGrid( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_SIMPLE );
+	
+	// Grid
+	m_grid->CreateGrid( 3, 6 );
+	m_grid->EnableEditing( false );
+	m_grid->EnableGridLines( true );
+	m_grid->EnableDragGridSize( false );
+	m_grid->SetMargins( 0, 0 );
+	
+	// Columns
+	m_grid->AutoSizeColumns();
+	m_grid->EnableDragColMove( false );
+	m_grid->EnableDragColSize( true );
+	m_grid->SetColLabelSize( 22 );
+	m_grid->SetColLabelValue( 0, wxT("Icon") );
+	m_grid->SetColLabelValue( 1, wxT("Show button") );
+	m_grid->SetColLabelValue( 2, wxT("Name") );
+	m_grid->SetColLabelValue( 3, wxT("Category") );
+	m_grid->SetColLabelValue( 4, wxT("Description") );
+	m_grid->SetColLabelValue( 5, wxT("Path") );
+	m_grid->SetColLabelAlignment( wxALIGN_CENTRE, wxALIGN_CENTRE );
+	
+	// Rows
+	m_grid->EnableDragRowSize( true );
+	m_grid->SetRowLabelSize( 0 );
+	m_grid->SetRowLabelAlignment( wxALIGN_CENTRE, wxALIGN_CENTRE );
+	
+	// Label Appearance
+	
+	// Cell Defaults
+	m_grid->SetDefaultCellAlignment( wxALIGN_LEFT, wxALIGN_CENTRE );
+	bGridSizer->Add( m_grid, 1, wxALL|wxEXPAND, 5 );
+	
+	
+	bPanelSizer->Add( bGridSizer, 1, wxALIGN_LEFT|wxEXPAND|wxLEFT, 0 );
+	
+	wxBoxSizer* bButtonsSizer;
+	bButtonsSizer = new wxBoxSizer( wxVERTICAL );
+	
+	m_moveUpButton = new wxBitmapButton( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 );
+	m_moveUpButton->SetMinSize( wxSize( 32,32 ) );
+	
+	bButtonsSizer->Add( m_moveUpButton, 0, wxALIGN_TOP|wxALL, 5 );
+	
+	m_moveDownButton = new wxBitmapButton( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 );
+	m_moveDownButton->SetMinSize( wxSize( 32,32 ) );
+	
+	bButtonsSizer->Add( m_moveDownButton, 0, wxALL, 5 );
+	
+	m_reloadButton = new wxBitmapButton( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 );
+	m_reloadButton->SetMinSize( wxSize( 32,32 ) );
+	
+	bButtonsSizer->Add( m_reloadButton, 0, wxALL, 5 );
+	
+	
+	bPanelSizer->Add( bButtonsSizer, 0, wxALIGN_RIGHT|wxALIGN_TOP, 0 );
+	
+	
+	this->SetSizer( bPanelSizer );
+	this->Layout();
+	
+	// Connect Events
+	m_grid->Connect( wxEVT_GRID_CELL_LEFT_CLICK, wxGridEventHandler( PANEL_PCBNEW_ACTION_PLUGINS_BASE::OnGridCellClick ), NULL, this );
+	m_moveUpButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PCBNEW_ACTION_PLUGINS_BASE::OnMoveUpButtonClick ), NULL, this );
+	m_moveDownButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PCBNEW_ACTION_PLUGINS_BASE::OnMoveDownButtonClick ), NULL, this );
+	m_reloadButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PCBNEW_ACTION_PLUGINS_BASE::OnReloadButtonClick ), NULL, this );
+}
+
+PANEL_PCBNEW_ACTION_PLUGINS_BASE::~PANEL_PCBNEW_ACTION_PLUGINS_BASE()
+{
+	// Disconnect Events
+	m_grid->Disconnect( wxEVT_GRID_CELL_LEFT_CLICK, wxGridEventHandler( PANEL_PCBNEW_ACTION_PLUGINS_BASE::OnGridCellClick ), NULL, this );
+	m_moveUpButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PCBNEW_ACTION_PLUGINS_BASE::OnMoveUpButtonClick ), NULL, this );
+	m_moveDownButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PCBNEW_ACTION_PLUGINS_BASE::OnMoveDownButtonClick ), NULL, this );
+	m_reloadButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PCBNEW_ACTION_PLUGINS_BASE::OnReloadButtonClick ), NULL, this );
+	
+}
diff --git a/pcbnew/dialogs/panel_pcbnew_action_plugins_base.fbp b/pcbnew/dialogs/panel_pcbnew_action_plugins_base.fbp
new file mode 100644
index 000000000..a5329cb6c
--- /dev/null
+++ b/pcbnew/dialogs/panel_pcbnew_action_plugins_base.fbp
@@ -0,0 +1,578 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<wxFormBuilder_Project>
+    <FileVersion major="1" minor="14" />
+    <object class="Project" expanded="1">
+        <property name="class_decoration"></property>
+        <property name="code_generation">C++</property>
+        <property name="disconnect_events">1</property>
+        <property name="disconnect_mode">source_name</property>
+        <property name="disconnect_php_events">0</property>
+        <property name="disconnect_python_events">0</property>
+        <property name="embedded_files_path">res</property>
+        <property name="encoding">UTF-8</property>
+        <property name="event_generation">connect</property>
+        <property name="file">panel_pcbnew_action_plugins_base</property>
+        <property name="first_id">1000</property>
+        <property name="help_provider">none</property>
+        <property name="indent_with_spaces">0</property>
+        <property name="internationalize">0</property>
+        <property name="name">PanelPcbnewActionPlugins</property>
+        <property name="namespace"></property>
+        <property name="path">.</property>
+        <property name="precompiled_header"></property>
+        <property name="relative_path">1</property>
+        <property name="skip_lua_events">1</property>
+        <property name="skip_php_events">1</property>
+        <property name="skip_python_events">1</property>
+        <property name="ui_table">UI</property>
+        <property name="use_enum">1</property>
+        <property name="use_microsoft_bom">0</property>
+        <object class="Panel" expanded="1">
+            <property name="aui_managed">0</property>
+            <property name="aui_manager_style">wxAUI_MGR_DEFAULT</property>
+            <property name="bg"></property>
+            <property name="context_help"></property>
+            <property name="context_menu">1</property>
+            <property name="enabled">1</property>
+            <property name="event_handler">impl_virtual</property>
+            <property name="fg"></property>
+            <property name="font"></property>
+            <property name="hidden">0</property>
+            <property name="id">wxID_ANY</property>
+            <property name="maximum_size"></property>
+            <property name="minimum_size"></property>
+            <property name="name">PANEL_PCBNEW_ACTION_PLUGINS_BASE</property>
+            <property name="pos"></property>
+            <property name="size">485,200</property>
+            <property name="subclass">; forward_declare</property>
+            <property name="tooltip"></property>
+            <property name="window_extra_style"></property>
+            <property name="window_name"></property>
+            <property name="window_style">wxTAB_TRAVERSAL</property>
+            <event name="OnAuiPaneActivated"></event>
+            <event name="OnAuiPaneButton"></event>
+            <event name="OnAuiPaneClose"></event>
+            <event name="OnAuiPaneMaximize"></event>
+            <event name="OnAuiPaneRestore"></event>
+            <event name="OnAuiRender"></event>
+            <event name="OnAux1DClick"></event>
+            <event name="OnAux1Down"></event>
+            <event name="OnAux1Up"></event>
+            <event name="OnAux2DClick"></event>
+            <event name="OnAux2Down"></event>
+            <event name="OnAux2Up"></event>
+            <event name="OnChar"></event>
+            <event name="OnCharHook"></event>
+            <event name="OnEnterWindow"></event>
+            <event name="OnEraseBackground"></event>
+            <event name="OnInitDialog"></event>
+            <event name="OnKeyDown"></event>
+            <event name="OnKeyUp"></event>
+            <event name="OnKillFocus"></event>
+            <event name="OnLeaveWindow"></event>
+            <event name="OnLeftDClick"></event>
+            <event name="OnLeftDown"></event>
+            <event name="OnLeftUp"></event>
+            <event name="OnMiddleDClick"></event>
+            <event name="OnMiddleDown"></event>
+            <event name="OnMiddleUp"></event>
+            <event name="OnMotion"></event>
+            <event name="OnMouseEvents"></event>
+            <event name="OnMouseWheel"></event>
+            <event name="OnPaint"></event>
+            <event name="OnRightDClick"></event>
+            <event name="OnRightDown"></event>
+            <event name="OnRightUp"></event>
+            <event name="OnSetFocus"></event>
+            <event name="OnSize"></event>
+            <event name="OnUpdateUI"></event>
+            <object class="wxBoxSizer" expanded="1">
+                <property name="minimum_size"></property>
+                <property name="name">bPanelSizer</property>
+                <property name="orient">wxHORIZONTAL</property>
+                <property name="permission">none</property>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">0</property>
+                    <property name="flag">wxALIGN_LEFT|wxEXPAND|wxLEFT</property>
+                    <property name="proportion">1</property>
+                    <object class="wxBoxSizer" expanded="1">
+                        <property name="minimum_size"></property>
+                        <property name="name">bGridSizer</property>
+                        <property name="orient">wxVERTICAL</property>
+                        <property name="permission">none</property>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxALL|wxEXPAND</property>
+                            <property name="proportion">1</property>
+                            <object class="wxGrid" expanded="1">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="autosize_cols">1</property>
+                                <property name="autosize_rows">0</property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="cell_bg"></property>
+                                <property name="cell_font"></property>
+                                <property name="cell_horiz_alignment">wxALIGN_LEFT</property>
+                                <property name="cell_text"></property>
+                                <property name="cell_vert_alignment">wxALIGN_CENTRE</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="col_label_horiz_alignment">wxALIGN_CENTRE</property>
+                                <property name="col_label_size">22</property>
+                                <property name="col_label_values">&quot;Icon&quot; &quot;Show button&quot; &quot;Name&quot; &quot;Category&quot; &quot;Description&quot; &quot;Path&quot;</property>
+                                <property name="col_label_vert_alignment">wxALIGN_CENTRE</property>
+                                <property name="cols">6</property>
+                                <property name="column_sizes"></property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="drag_col_move">0</property>
+                                <property name="drag_col_size">1</property>
+                                <property name="drag_grid_size">0</property>
+                                <property name="drag_row_size">1</property>
+                                <property name="editing">0</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font"></property>
+                                <property name="grid_line_color"></property>
+                                <property name="grid_lines">1</property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label_bg"></property>
+                                <property name="label_font"></property>
+                                <property name="label_text"></property>
+                                <property name="margin_height">0</property>
+                                <property name="margin_width">0</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_grid</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="row_label_horiz_alignment">wxALIGN_CENTRE</property>
+                                <property name="row_label_size">0</property>
+                                <property name="row_label_values"></property>
+                                <property name="row_label_vert_alignment">wxALIGN_CENTRE</property>
+                                <property name="row_sizes"></property>
+                                <property name="rows">3</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="subclass">; forward_declare</property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style">wxBORDER_SIMPLE</property>
+                                <event name="OnAux1DClick"></event>
+                                <event name="OnAux1Down"></event>
+                                <event name="OnAux1Up"></event>
+                                <event name="OnAux2DClick"></event>
+                                <event name="OnAux2Down"></event>
+                                <event name="OnAux2Up"></event>
+                                <event name="OnChar"></event>
+                                <event name="OnCharHook"></event>
+                                <event name="OnEnterWindow"></event>
+                                <event name="OnEraseBackground"></event>
+                                <event name="OnGridCellChange"></event>
+                                <event name="OnGridCellLeftClick">OnGridCellClick</event>
+                                <event name="OnGridCellLeftDClick"></event>
+                                <event name="OnGridCellRightClick"></event>
+                                <event name="OnGridCellRightDClick"></event>
+                                <event name="OnGridCmdCellChange"></event>
+                                <event name="OnGridCmdCellLeftClick"></event>
+                                <event name="OnGridCmdCellLeftDClick"></event>
+                                <event name="OnGridCmdCellRightClick"></event>
+                                <event name="OnGridCmdCellRightDClick"></event>
+                                <event name="OnGridCmdColSize"></event>
+                                <event name="OnGridCmdEditorCreated"></event>
+                                <event name="OnGridCmdEditorHidden"></event>
+                                <event name="OnGridCmdEditorShown"></event>
+                                <event name="OnGridCmdLabelLeftClick"></event>
+                                <event name="OnGridCmdLabelLeftDClick"></event>
+                                <event name="OnGridCmdLabelRightClick"></event>
+                                <event name="OnGridCmdLabelRightDClick"></event>
+                                <event name="OnGridCmdRangeSelect"></event>
+                                <event name="OnGridCmdRowSize"></event>
+                                <event name="OnGridCmdSelectCell"></event>
+                                <event name="OnGridColSize"></event>
+                                <event name="OnGridEditorCreated"></event>
+                                <event name="OnGridEditorHidden"></event>
+                                <event name="OnGridEditorShown"></event>
+                                <event name="OnGridLabelLeftClick"></event>
+                                <event name="OnGridLabelLeftDClick"></event>
+                                <event name="OnGridLabelRightClick"></event>
+                                <event name="OnGridLabelRightDClick"></event>
+                                <event name="OnGridRangeSelect"></event>
+                                <event name="OnGridRowSize"></event>
+                                <event name="OnGridSelectCell"></event>
+                                <event name="OnKeyDown"></event>
+                                <event name="OnKeyUp"></event>
+                                <event name="OnKillFocus"></event>
+                                <event name="OnLeaveWindow"></event>
+                                <event name="OnLeftDClick"></event>
+                                <event name="OnLeftDown"></event>
+                                <event name="OnLeftUp"></event>
+                                <event name="OnMiddleDClick"></event>
+                                <event name="OnMiddleDown"></event>
+                                <event name="OnMiddleUp"></event>
+                                <event name="OnMotion"></event>
+                                <event name="OnMouseEvents"></event>
+                                <event name="OnMouseWheel"></event>
+                                <event name="OnPaint"></event>
+                                <event name="OnRightDClick"></event>
+                                <event name="OnRightDown"></event>
+                                <event name="OnRightUp"></event>
+                                <event name="OnSetFocus"></event>
+                                <event name="OnSize"></event>
+                                <event name="OnUpdateUI"></event>
+                            </object>
+                        </object>
+                    </object>
+                </object>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">0</property>
+                    <property name="flag">wxALIGN_RIGHT|wxALIGN_TOP</property>
+                    <property name="proportion">0</property>
+                    <object class="wxBoxSizer" expanded="1">
+                        <property name="minimum_size"></property>
+                        <property name="name">bButtonsSizer</property>
+                        <property name="orient">wxVERTICAL</property>
+                        <property name="permission">none</property>
+                        <object class="sizeritem" expanded="0">
+                            <property name="border">5</property>
+                            <property name="flag">wxALIGN_TOP|wxALL</property>
+                            <property name="proportion">0</property>
+                            <object class="wxBitmapButton" expanded="0">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="bitmap"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="current"></property>
+                                <property name="default">0</property>
+                                <property name="default_pane">0</property>
+                                <property name="disabled"></property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="focus"></property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Move Up</property>
+                                <property name="margins"></property>
+                                <property name="markup">0</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size">32,32</property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_moveUpButton</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="position"></property>
+                                <property name="pressed"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style"></property>
+                                <property name="subclass">; forward_declare</property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="validator_data_type"></property>
+                                <property name="validator_style">wxFILTER_NONE</property>
+                                <property name="validator_type">wxDefaultValidator</property>
+                                <property name="validator_variable"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <event name="OnAux1DClick"></event>
+                                <event name="OnAux1Down"></event>
+                                <event name="OnAux1Up"></event>
+                                <event name="OnAux2DClick"></event>
+                                <event name="OnAux2Down"></event>
+                                <event name="OnAux2Up"></event>
+                                <event name="OnButtonClick">OnMoveUpButtonClick</event>
+                                <event name="OnChar"></event>
+                                <event name="OnCharHook"></event>
+                                <event name="OnEnterWindow"></event>
+                                <event name="OnEraseBackground"></event>
+                                <event name="OnKeyDown"></event>
+                                <event name="OnKeyUp"></event>
+                                <event name="OnKillFocus"></event>
+                                <event name="OnLeaveWindow"></event>
+                                <event name="OnLeftDClick"></event>
+                                <event name="OnLeftDown"></event>
+                                <event name="OnLeftUp"></event>
+                                <event name="OnMiddleDClick"></event>
+                                <event name="OnMiddleDown"></event>
+                                <event name="OnMiddleUp"></event>
+                                <event name="OnMotion"></event>
+                                <event name="OnMouseEvents"></event>
+                                <event name="OnMouseWheel"></event>
+                                <event name="OnPaint"></event>
+                                <event name="OnRightDClick"></event>
+                                <event name="OnRightDown"></event>
+                                <event name="OnRightUp"></event>
+                                <event name="OnSetFocus"></event>
+                                <event name="OnSize"></event>
+                                <event name="OnUpdateUI"></event>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxALL</property>
+                            <property name="proportion">0</property>
+                            <object class="wxBitmapButton" expanded="1">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="bitmap"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="current"></property>
+                                <property name="default">0</property>
+                                <property name="default_pane">0</property>
+                                <property name="disabled"></property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="focus"></property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Move Down</property>
+                                <property name="margins"></property>
+                                <property name="markup">0</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size">32,32</property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_moveDownButton</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="position"></property>
+                                <property name="pressed"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style"></property>
+                                <property name="subclass">; forward_declare</property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="validator_data_type"></property>
+                                <property name="validator_style">wxFILTER_NONE</property>
+                                <property name="validator_type">wxDefaultValidator</property>
+                                <property name="validator_variable"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <event name="OnAux1DClick"></event>
+                                <event name="OnAux1Down"></event>
+                                <event name="OnAux1Up"></event>
+                                <event name="OnAux2DClick"></event>
+                                <event name="OnAux2Down"></event>
+                                <event name="OnAux2Up"></event>
+                                <event name="OnButtonClick">OnMoveDownButtonClick</event>
+                                <event name="OnChar"></event>
+                                <event name="OnCharHook"></event>
+                                <event name="OnEnterWindow"></event>
+                                <event name="OnEraseBackground"></event>
+                                <event name="OnKeyDown"></event>
+                                <event name="OnKeyUp"></event>
+                                <event name="OnKillFocus"></event>
+                                <event name="OnLeaveWindow"></event>
+                                <event name="OnLeftDClick"></event>
+                                <event name="OnLeftDown"></event>
+                                <event name="OnLeftUp"></event>
+                                <event name="OnMiddleDClick"></event>
+                                <event name="OnMiddleDown"></event>
+                                <event name="OnMiddleUp"></event>
+                                <event name="OnMotion"></event>
+                                <event name="OnMouseEvents"></event>
+                                <event name="OnMouseWheel"></event>
+                                <event name="OnPaint"></event>
+                                <event name="OnRightDClick"></event>
+                                <event name="OnRightDown"></event>
+                                <event name="OnRightUp"></event>
+                                <event name="OnSetFocus"></event>
+                                <event name="OnSize"></event>
+                                <event name="OnUpdateUI"></event>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxALL</property>
+                            <property name="proportion">0</property>
+                            <object class="wxBitmapButton" expanded="1">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="bitmap"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="current"></property>
+                                <property name="default">0</property>
+                                <property name="default_pane">0</property>
+                                <property name="disabled"></property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="focus"></property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Reload Plugins</property>
+                                <property name="margins"></property>
+                                <property name="markup">0</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size">32,32</property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_reloadButton</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="position"></property>
+                                <property name="pressed"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style"></property>
+                                <property name="subclass">; forward_declare</property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="validator_data_type"></property>
+                                <property name="validator_style">wxFILTER_NONE</property>
+                                <property name="validator_type">wxDefaultValidator</property>
+                                <property name="validator_variable"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <event name="OnAux1DClick"></event>
+                                <event name="OnAux1Down"></event>
+                                <event name="OnAux1Up"></event>
+                                <event name="OnAux2DClick"></event>
+                                <event name="OnAux2Down"></event>
+                                <event name="OnAux2Up"></event>
+                                <event name="OnButtonClick">OnReloadButtonClick</event>
+                                <event name="OnChar"></event>
+                                <event name="OnCharHook"></event>
+                                <event name="OnEnterWindow"></event>
+                                <event name="OnEraseBackground"></event>
+                                <event name="OnKeyDown"></event>
+                                <event name="OnKeyUp"></event>
+                                <event name="OnKillFocus"></event>
+                                <event name="OnLeaveWindow"></event>
+                                <event name="OnLeftDClick"></event>
+                                <event name="OnLeftDown"></event>
+                                <event name="OnLeftUp"></event>
+                                <event name="OnMiddleDClick"></event>
+                                <event name="OnMiddleDown"></event>
+                                <event name="OnMiddleUp"></event>
+                                <event name="OnMotion"></event>
+                                <event name="OnMouseEvents"></event>
+                                <event name="OnMouseWheel"></event>
+                                <event name="OnPaint"></event>
+                                <event name="OnRightDClick"></event>
+                                <event name="OnRightDown"></event>
+                                <event name="OnRightUp"></event>
+                                <event name="OnSetFocus"></event>
+                                <event name="OnSize"></event>
+                                <event name="OnUpdateUI"></event>
+                            </object>
+                        </object>
+                    </object>
+                </object>
+            </object>
+        </object>
+    </object>
+</wxFormBuilder_Project>
diff --git a/pcbnew/dialogs/panel_pcbnew_action_plugins_base.h b/pcbnew/dialogs/panel_pcbnew_action_plugins_base.h
new file mode 100644
index 000000000..53f03922c
--- /dev/null
+++ b/pcbnew/dialogs/panel_pcbnew_action_plugins_base.h
@@ -0,0 +1,56 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version Jul 11 2018)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO *NOT* EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef __PANEL_PCBNEW_ACTION_PLUGINS_BASE_H__
+#define __PANEL_PCBNEW_ACTION_PLUGINS_BASE_H__
+
+#include <wx/artprov.h>
+#include <wx/xrc/xmlres.h>
+#include <wx/colour.h>
+#include <wx/settings.h>
+#include <wx/string.h>
+#include <wx/font.h>
+#include <wx/grid.h>
+#include <wx/gdicmn.h>
+#include <wx/sizer.h>
+#include <wx/bmpbuttn.h>
+#include <wx/bitmap.h>
+#include <wx/image.h>
+#include <wx/icon.h>
+#include <wx/button.h>
+#include <wx/panel.h>
+
+///////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////////////
+/// Class PANEL_PCBNEW_ACTION_PLUGINS_BASE
+///////////////////////////////////////////////////////////////////////////////
+class PANEL_PCBNEW_ACTION_PLUGINS_BASE : public wxPanel 
+{
+	private:
+	
+	protected:
+		wxGrid* m_grid;
+		wxBitmapButton* m_moveUpButton;
+		wxBitmapButton* m_moveDownButton;
+		wxBitmapButton* m_reloadButton;
+		
+		// Virtual event handlers, overide them in your derived class
+		virtual void OnGridCellClick( wxGridEvent& event ) { event.Skip(); }
+		virtual void OnMoveUpButtonClick( wxCommandEvent& event ) { event.Skip(); }
+		virtual void OnMoveDownButtonClick( wxCommandEvent& event ) { event.Skip(); }
+		virtual void OnReloadButtonClick( wxCommandEvent& event ) { event.Skip(); }
+		
+	
+	public:
+		
+		PANEL_PCBNEW_ACTION_PLUGINS_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 485,200 ), long style = wxTAB_TRAVERSAL ); 
+		~PANEL_PCBNEW_ACTION_PLUGINS_BASE();
+	
+};
+
+#endif //__PANEL_PCBNEW_ACTION_PLUGINS_BASE_H__
diff --git a/pcbnew/pcb_edit_frame.cpp b/pcbnew/pcb_edit_frame.cpp
index 9149d4bde..49f20b8f4 100644
--- a/pcbnew/pcb_edit_frame.cpp
+++ b/pcbnew/pcb_edit_frame.cpp
@@ -1249,6 +1249,8 @@ void PCB_EDIT_FRAME::PythonPluginsReload()
         // Action plugins can be modified, therefore the plugins menu
         // must be updated:
         RebuildActionPluginMenus();
+        // Recreate top toolbar to add action plugin buttons
+        ReCreateHToolbar();
     #endif
 #endif
 }
diff --git a/pcbnew/pcb_edit_frame.h b/pcbnew/pcb_edit_frame.h
index 417935357..dd033b416 100644
--- a/pcbnew/pcb_edit_frame.h
+++ b/pcbnew/pcb_edit_frame.h
@@ -25,13 +25,14 @@
 #define  WXPCB_STRUCT_H_
 
 #include <unordered_map>
+#include <map>
 #include "pcb_base_edit_frame.h"
 #include "config_params.h"
 #include "undo_redo_container.h"
 #include "zones.h"
 
-
 /*  Forward declarations of classes. */
+class ACTION_PLUGIN;
 class PCB_SCREEN;
 class BOARD;
 class BOARD_COMMIT;
@@ -121,6 +122,12 @@ protected:
      */
     void RebuildActionPluginMenus();
 
+    /**
+     * Function AddActionPluginTools
+     * Append action plugin buttons to main toolbar
+     */
+    void AddActionPluginTools();
+
     /**
      * Function OnActionPlugin
      * Launched by the menu when an action is called
@@ -379,6 +386,36 @@ public:
     // Configurations:
     void Process_Config( wxCommandEvent& event );
 
+#if defined(KICAD_SCRIPTING) && defined(KICAD_SCRIPTING_ACTION_MENU)
+
+    /**
+     * Function SetActionPluginSettings
+     * Set a set of plugins that have visible buttons on toolbar
+     * Plugins are identified by their module path
+     */
+    void SetActionPluginSettings( const std::vector< std::pair<wxString, wxString> >& aPluginsWithButtons );
+
+    /**
+     * Function GetActionPluginSettings
+     * Get a set of plugins that have visible buttons on toolbar
+     */
+    std::vector< std::pair<wxString, wxString> > GetActionPluginSettings();
+
+    /**
+     * Function GetActionPluginButtonVisible
+     * Returns true if button visibility action plugin setting was set to true
+     * or it is unset and plugin defaults to true.
+     */
+    bool GetActionPluginButtonVisible( const wxString& aPluginPath, bool aPluginDefault );
+
+    /**
+     * Function GetOrderedActionPlugins
+     * Returns ordered set of plugins in sequence in which they should appear on toolbar or in settings
+     */
+    std::map< int, ACTION_PLUGIN* > GetOrderedActionPlugins();
+
+#endif
+
     /**
      * Function GetProjectFileParameters
      * returns a project file parameter list for Pcbnew.
diff --git a/pcbnew/pcb_general_settings.cpp b/pcbnew/pcb_general_settings.cpp
index a8c70cd0a..76d386759 100644
--- a/pcbnew/pcb_general_settings.cpp
+++ b/pcbnew/pcb_general_settings.cpp
@@ -22,6 +22,7 @@
  */
 
 #include <pcb_general_settings.h>
+#include <wx/tokenzr.h>
 
 PCB_GENERAL_SETTINGS::PCB_GENERAL_SETTINGS( FRAME_T aFrameType )
     : m_frameType( aFrameType ), m_colorsSettings( aFrameType )
@@ -54,6 +55,32 @@ PCB_GENERAL_SETTINGS::PCB_GENERAL_SETTINGS( FRAME_T aFrameType )
 void PCB_GENERAL_SETTINGS::Load( wxConfigBase* aCfg )
 {
     m_colorsSettings.Load( aCfg );
+
+#if defined(KICAD_SCRIPTING) && defined(KICAD_SCRIPTING_ACTION_MENU)
+
+    m_pluginSettings.clear();
+
+    wxString pluginSettings = aCfg->Read( "ActionPluginButtons" );
+
+    wxStringTokenizer pluginSettingsTokenizer = wxStringTokenizer( pluginSettings, ";" );
+
+    while( pluginSettingsTokenizer.HasMoreTokens() )
+    {
+        wxString plugin = pluginSettingsTokenizer.GetNextToken();
+        wxStringTokenizer pluginTokenizer = wxStringTokenizer( plugin, "=" );
+
+        if ( pluginTokenizer.CountTokens() != 2 )
+        {
+            // Bad config
+            continue;
+        }
+
+        plugin = pluginTokenizer.GetNextToken();
+        m_pluginSettings.push_back( std::make_pair( plugin, pluginTokenizer.GetNextToken() ) );
+    }
+
+#endif
+
     SETTINGS::Load( aCfg );
 }
 
@@ -61,6 +88,24 @@ void PCB_GENERAL_SETTINGS::Load( wxConfigBase* aCfg )
 void PCB_GENERAL_SETTINGS::Save( wxConfigBase* aCfg )
 {
     m_colorsSettings.Save( aCfg );
+
+#if defined(KICAD_SCRIPTING) && defined(KICAD_SCRIPTING_ACTION_MENU)
+
+    wxString pluginSettings;
+
+    for( auto const& entry : m_pluginSettings )
+    {
+        if( !pluginSettings.IsEmpty() )
+        {
+            pluginSettings = pluginSettings + wxT( ";" );
+        }
+        pluginSettings = pluginSettings + entry.first + wxT( "=" ) + entry.second;
+    }
+
+    aCfg->Write( "ActionPluginButtons" , pluginSettings );
+
+#endif
+
     SETTINGS::Save( aCfg );
 }
 
diff --git a/pcbnew/pcb_general_settings.h b/pcbnew/pcb_general_settings.h
index bc8f95369..fbcbe90ef 100644
--- a/pcbnew/pcb_general_settings.h
+++ b/pcbnew/pcb_general_settings.h
@@ -25,6 +25,7 @@
 #define __PCBNEW_GENERAL_SETTINGS_H
 
 #include <colors_design_settings.h>
+#include <vector>
 
 class wxConfigBase;
 class wxString;
@@ -65,6 +66,10 @@ public:
     MAGNETIC_PAD_OPTION_VALUES  m_magneticPads  = CAPTURE_CURSOR_IN_TRACK_TOOL;
     MAGNETIC_PAD_OPTION_VALUES  m_magneticTracks = CAPTURE_CURSOR_IN_TRACK_TOOL;
 
+#if defined(KICAD_SCRIPTING) && defined(KICAD_SCRIPTING_ACTION_MENU)
+    std::vector< std::pair<wxString, wxString> > m_pluginSettings;  // Settings for action plugins
+#endif
+
 protected:
     const FRAME_T m_frameType;
     COLORS_DESIGN_SETTINGS m_colorsSettings;
diff --git a/pcbnew/pcbnew_config.cpp b/pcbnew/pcbnew_config.cpp
index ae164fd4c..b6025a526 100644
--- a/pcbnew/pcbnew_config.cpp
+++ b/pcbnew/pcbnew_config.cpp
@@ -45,6 +45,7 @@
 #include <panel_hotkeys_editor.h>
 #include <panel_pcbnew_settings.h>
 #include <panel_pcbnew_display_options.h>
+#include <panel_pcbnew_action_plugins.h>
 #include <fp_lib_table.h>
 #include <worksheet_shape_builder.h>
 #include <class_board.h>
@@ -97,6 +98,9 @@ void PCB_EDIT_FRAME::InstallPreferences( PAGED_DIALOG* aParent )
 
     book->AddPage( new PANEL_PCBNEW_SETTINGS( this, aParent ), _( "Pcbnew" ) );
     book->AddSubPage( new PANEL_PCBNEW_DISPLAY_OPTIONS( this, aParent ), _( "Display Options" ) );
+#if defined(KICAD_SCRIPTING) && defined(KICAD_SCRIPTING_ACTION_MENU)
+    book->AddSubPage( new PANEL_PCBNEW_ACTION_PLUGINS( this, aParent ), _( "Action Plugins" ) );
+#endif
 }
 
 
diff --git a/pcbnew/swig/pcbnew_action_plugins.cpp b/pcbnew/swig/pcbnew_action_plugins.cpp
index 4fb232ea8..41b42125d 100644
--- a/pcbnew/swig/pcbnew_action_plugins.cpp
+++ b/pcbnew/swig/pcbnew_action_plugins.cpp
@@ -140,6 +140,32 @@ wxString PYTHON_ACTION_PLUGIN::GetDescription()
 }
 
 
+bool PYTHON_ACTION_PLUGIN::GetShowToolbarButton()
+{
+    PyLOCK lock;
+
+    PyObject* result = CallMethod( "GetShowToolbarButton");
+
+    return PyObject_IsTrue(result);
+}
+
+
+wxString PYTHON_ACTION_PLUGIN::GetIconFileName()
+{
+    PyLOCK lock;
+
+    return CallRetStrMethod( "GetIconFileName" );
+}
+
+
+wxString PYTHON_ACTION_PLUGIN::GetPluginPath()
+{
+    PyLOCK lock;
+
+    return CallRetStrMethod( "GetPluginPath" );
+}
+
+
 void PYTHON_ACTION_PLUGIN::Run()
 {
     PyLOCK lock;
@@ -177,6 +203,12 @@ void PCB_EDIT_FRAME::OnActionPlugin( wxCommandEvent& aEvent )
 
     ACTION_PLUGIN* actionPlugin = ACTION_PLUGINS::GetActionByMenu( id );
 
+    if ( !actionPlugin )
+    {
+      // Try looking by button
+      actionPlugin = ACTION_PLUGINS::GetActionByButton( id );
+    }
+
     if( actionPlugin )
     {
         PICKED_ITEMS_LIST itemsList;
@@ -416,19 +448,28 @@ void PCB_EDIT_FRAME::RebuildActionPluginMenus()
     for( int ii = 0; ii < ACTION_PLUGINS::GetActionsCount(); ii++ )
     {
         wxMenuItem* item;
+        ACTION_PLUGIN* ap = ACTION_PLUGINS::GetAction( ii );
+        const wxBitmap& bitmap = ap->iconBitmap.IsOk() ? ap->iconBitmap : KiBitmap( hammer_xpm );
 
         if( ii < (int) available_menus.size() )
         {
             item = available_menus[ii];
-            item->SetItemLabel( ACTION_PLUGINS::GetAction( ii )->GetName() );
-            item->SetHelp( ACTION_PLUGINS::GetAction( ii )->GetDescription() );
+            item->SetItemLabel( ap->GetName() );
+            item->SetHelp( ap->GetDescription() );
+
+            // On windows we need to set "unchecked" bitmap
+            #if defined(__WXMSW__)
+            item->SetBitmap( bitmap, false );
+            #else
+            item->SetBitmap( bitmap );
+            #endif
         }
         else
         {
             item = AddMenuItem( actionMenu, wxID_ANY,
-                    ACTION_PLUGINS::GetAction( ii )->GetName(),
-                    ACTION_PLUGINS::GetAction( ii )->GetDescription(),
-                    KiBitmap( hammer_xpm ) );
+                    ap->GetName(),
+                    ap->GetDescription(),
+                    bitmap );
 
             Connect( item->GetId(), wxEVT_COMMAND_MENU_SELECTED,
                     (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) &
@@ -440,4 +481,118 @@ void PCB_EDIT_FRAME::RebuildActionPluginMenus()
 }
 
 
+void PCB_EDIT_FRAME::AddActionPluginTools()
+{
+    bool need_separator = true;
+    const auto& orderedPlugins = GetOrderedActionPlugins();
+
+    for( const auto& entry : orderedPlugins )
+    {
+        ACTION_PLUGIN* ap = entry.second;
+
+        if( GetActionPluginButtonVisible( ap->GetPluginPath(), ap->GetShowToolbarButton() ) )
+        {
+
+            if ( need_separator )
+            {
+                KiScaledSeparator( m_mainToolBar, this );
+                need_separator = false;
+            }
+
+            // Add button
+            wxBitmap bitmap;
+
+            if ( ap->iconBitmap.IsOk() )
+            {
+                bitmap = KiScaledBitmap( ap->iconBitmap, this );
+            }
+            else
+            {
+                bitmap = KiScaledBitmap( hammer_xpm, this );
+            }
+
+            wxAuiToolBarItem* button = m_mainToolBar->AddTool(
+                    wxID_ANY, wxEmptyString, bitmap, ap->GetName() );
+
+            Connect( button->GetId(), wxEVT_COMMAND_MENU_SELECTED,
+                    (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) &
+                    PCB_EDIT_FRAME::OnActionPlugin );
+
+            // Link action plugin to button
+            ACTION_PLUGINS::SetActionButton( ap, button->GetId() );
+        }
+    }
+}
+
+
+void PCB_EDIT_FRAME::SetActionPluginSettings( const std::vector< std::pair<wxString, wxString> >& aPluginSettings )
+{
+    m_configSettings.m_pluginSettings = aPluginSettings;
+    ReCreateHToolbar();
+}
+
+
+std::vector< std::pair<wxString, wxString> > PCB_EDIT_FRAME::GetActionPluginSettings()
+{
+    return m_configSettings.m_pluginSettings;
+}
+
+
+std::map< int, ACTION_PLUGIN* > PCB_EDIT_FRAME::GetOrderedActionPlugins()
+{
+    std::map<ACTION_PLUGIN*, int> pluginToPosition;
+
+    const auto& pluginSettings = GetActionPluginSettings();
+
+    int position = 0;
+
+    // First add plugins that have entires in settings
+    for( size_t ii = 0; ii < pluginSettings.size(); ii++ )
+    {
+        ACTION_PLUGIN* ap = ACTION_PLUGINS::GetActionByPath( pluginSettings[ ii ].first );
+
+        if( ap )
+        {
+            pluginToPosition.insert( { ap , position++ } );
+        }
+    }
+
+    // Now append new plugins that have not been configured yet to the end
+    for( int ii = 0; ii < ACTION_PLUGINS::GetActionsCount(); ii++ )
+    {
+        if( pluginToPosition.find( ACTION_PLUGINS::GetAction( ii ) ) == pluginToPosition.end() )
+        {
+            pluginToPosition.insert( { ACTION_PLUGINS::GetAction( ii ), position++ } );
+        }
+    }
+
+    // Inverted map is what we want since it will be automatically ordered by position
+    std::map<int, ACTION_PLUGIN* > orderedPlugins;
+
+    for( const auto& entry : pluginToPosition )
+    {
+        orderedPlugins.insert( { entry.second, entry.first } );
+    }
+
+    return orderedPlugins;
+}
+
+
+bool PCB_EDIT_FRAME::GetActionPluginButtonVisible( const wxString& aPluginPath, bool aPluginDefault )
+{
+    auto& settings = m_configSettings.m_pluginSettings;
+
+    for(const auto& entry : settings )
+    {
+        if (entry.first == aPluginPath )
+        {
+            return entry.second == wxT( "Visible" );
+        }
+    }
+
+    // Plugin is not in settings, return default.
+    return  aPluginDefault;
+}
+
+
 #endif
diff --git a/pcbnew/swig/pcbnew_action_plugins.h b/pcbnew/swig/pcbnew_action_plugins.h
index 08dfa14e6..a1d6d9ff0 100644
--- a/pcbnew/swig/pcbnew_action_plugins.h
+++ b/pcbnew/swig/pcbnew_action_plugins.h
@@ -47,6 +47,9 @@ public:
     wxString    GetCategoryName() override;
     wxString    GetName() override;
     wxString    GetDescription() override;
+    bool        GetShowToolbarButton() override;
+    wxString    GetIconFileName() override;
+    wxString    GetPluginPath() override;
     void        Run() override;
     void*       GetObject() override;
 };
diff --git a/pcbnew/tool_pcb_editor.cpp b/pcbnew/tool_pcb_editor.cpp
index 66b622357..b5c64c3a1 100644
--- a/pcbnew/tool_pcb_editor.cpp
+++ b/pcbnew/tool_pcb_editor.cpp
@@ -321,6 +321,10 @@ void PCB_EDIT_FRAME::ReCreateHToolbar()
         m_mainToolBar->AddTool( ID_TOOLBARH_PCB_SCRIPTING_CONSOLE, wxEmptyString,
                                 KiScaledBitmap( py_script_xpm, this ),
                                 _( "Show/Hide the Python Scripting console" ), wxITEM_CHECK );
+
+#if defined(KICAD_SCRIPTING) && defined(KICAD_SCRIPTING_ACTION_MENU)
+        AddActionPluginTools();
+#endif
     }
 #endif
 
diff --git a/scripting/kicadplugins.i b/scripting/kicadplugins.i
index 8c1781d50..fb92f1f25 100644
--- a/scripting/kicadplugins.i
+++ b/scripting/kicadplugins.i
@@ -260,7 +260,7 @@ def LoadPlugins(bundlepath=None):
             if module == '__init__.py' or module[-3:] != '.py':
                 continue
 
-            LoadOnePlugin(plugins_dir, module);
+            LoadOnePlugin(plugins_dir, module)
 
 
 class KiCadPlugin:
@@ -268,6 +268,9 @@ class KiCadPlugin:
         pass
 
     def register(self):
+        import inspect
+        import os
+
         if isinstance(self,FilePlugin):
             pass # register to file plugins in C++
 
@@ -276,6 +279,13 @@ class KiCadPlugin:
             return
 
         if isinstance(self,ActionPlugin):
+            """
+            Get path to .py or .pyc that has definition of plugin class.
+            If path is binary but source also exists, assume definition is in source.
+            """
+            self.__plugin_path = inspect.getfile(self.__class__)
+            if self.__plugin_path.endswith('.pyc') and os.path.isfile(self.__plugin_path[:-1]):
+                self.__plugin_path = self.__plugin_path[:-1]
             PYTHON_ACTION_PLUGINS.register_action(self)
             return
 
@@ -295,6 +305,9 @@ class KiCadPlugin:
 
         return
 
+    def GetPluginPath( self ):
+        return self.__plugin_path
+
 
 class FilePlugin(KiCadPlugin):
     def __init__(self):
@@ -633,6 +646,8 @@ class FootprintWizardPlugin(KiCadPlugin, object):
 class ActionPlugin(KiCadPlugin, object):
     def __init__( self ):
         KiCadPlugin.__init__( self )
+        self.icon_file_name = ""
+        self.show_toolbar_button = False
         self.defaults()
 
     def defaults( self ):
@@ -649,6 +664,12 @@ class ActionPlugin(KiCadPlugin, object):
     def GetDescription( self ):
         return self.description
 
+    def GetShowToolbarButton( self ):
+        return self.show_toolbar_button
+
+    def GetIconFileName( self ):
+        return self.icon_file_name
+
     def Run(self):
         return
 
-- 
2.18.0.1017.ga543ac7ca45-goog


Follow ups

References