Hi,

In light of recent changes to the tool actions, here is an update to
the tool framework developer docs.

There are some minor changes to the tool itself, correcting a few
mistakes, and better TOC markdown.

Cheers,

John
From 24894cd080e98c6591e6b2ddaed1e02b88965404 Mon Sep 17 00:00:00 2001
From: John Beard <[email protected]>
Date: Thu, 23 Feb 2017 10:47:19 +0800
Subject: [PATCH] Update GAL tool documentation

Update to reference new PCB_ACTIONs (used to be COMMON_ACTION)

Also expand on use of BOARD_COMMIT.

Add doxygen TOC markup
---
 Documentation/development/tool-framework.md | 186 +++++++++++++++++++++-------
 1 file changed, 144 insertions(+), 42 deletions(-)

diff --git a/Documentation/development/tool-framework.md b/Documentation/development/tool-framework.md
index 6211debb0..b7ede166a 100644
--- a/Documentation/development/tool-framework.md
+++ b/Documentation/development/tool-framework.md
@@ -7,8 +7,11 @@ GAL canvases.
 
 # Introduction # {#intro}
 
-The GAL framework provides a powerful method of easily adding tools to
-KiCad. A GAL "tool" is a class which provides one or more "actions"
+The GAL (Graphics Abstraction Layer) framework provides a powerful
+method of easily adding tools to KiCad. Compared to the older "legacy"
+canvas, GAL tools are more flexible, powerful and much easier to write.
+
+A GAL "tool" is a class which provides one or more "actions"
 to perform. An action can be a simple one-off action (e.g. "zoom in"
 or "flip object"), or an interactive process (e.g. "manually edit
 polygon points").
@@ -29,11 +32,11 @@ Some examples of tools in the Pcbnew GAL are:
   (pcbnew/tools/drawing_tool.cpp,pcbnew/tools/drawing_tool.h)
 * The zoom tool - allows the user to zoom in and out
 
-## Major parts of a tool
+# Major parts of a tool # {#major-parts}
 
 There are two main aspects to tools: the actions and the the tool class.
 
-### Tool actions
+## Tool actions {#tool-actions}
 
 The `TOOL_ACTION` class acts as a handle for the GAL framework to
 call on actions provided by tools. Generally, every action, interactive
@@ -61,7 +64,7 @@ or not, has a `TOOL_ACTION` instance. This provides:
 * A parameter, which allows different actions to call the same function
   with different effects, for example "step left" and "step right".
 
-### The tool class
+## The tool class {#tool-class}
 
 GAL tools inherit the `TOOL_BASE` class. A Pcbnew tool will generally
 inherit from `PCB_TOOL`, which is a `TOOL_INTERACTIVE`, which is
@@ -72,12 +75,17 @@ The tool class for a tool can be fairly lightweight - much of the
 functionality is inherited from the tool's base classes. These base
 classes provide access to several things, particularly:
 
-* Access to the `PCB_EDIT_FRAME`, which can be used to modify the
-  viewport, set cursors and status bar content, etc.
+* Access to the parent frame (a `wxWindow`, which can be used to
+  modify the viewport, set cursors and status bar content, etc.
+    * Use the function `getEditFrame<T>()`, where `T` is the frame
+      subclass you want. In `PCB_TOOL`, this is likely `PCB_EDIT_FRAME`.
 * Access to the `TOOL_MANAGER` which can be used to access other tools'
   actions.
-* Access to the `BOARD` object which is used to modify the PCB content.
-* Access to the `KIGFX::VIEW`, which is used to manipulate the GAL canvas.
+* Access to the "model" (some sort of `EDA_ITEM`) which backs the tool.
+    * Access with  `getModel<T>()`. In `PCB_TOOL`, the model type `T` is
+     `BOARD`, which can be used to access and modify the PCB content.
+* Access to the `KIGFX::VIEW` and `KIGFX::VIEW_CONTROLS`, which are
+  used to manipulate the GAL canvas.
 
 The major parts of tool's implementation are the functions used by the
 `TOOL_MANAGER` to set up and manage the tool:
@@ -107,7 +115,7 @@ The major parts of tool's implementation are the functions used by the
       by any other code, but are invoked by the tool manager's coroutine
       framework according to the `SetTransitions()` map.
 
-#### Interactive actions
+### Interactive actions {#interactive-actions}
 
 The action handlers for an interactive actions handle repeated actions
 from the tool manager in a loop, until an action indicating that the
@@ -153,7 +161,7 @@ a cursor change and by setting a status string.
         return 0;
     }
 
-### The tool menu
+## The tool menu {#tool-menu}
 
 Top level tools, i.e. tools that the user enters directly, usually
 provide their own context menu. Tools that are called only from other
@@ -184,7 +192,58 @@ this menu will trigger the action - there is no further action
 needed in your tool's event loop.
 
 
-# Tutorial: Adding a new tool
+## Commit objects {#commits}
+
+The `COMMIT` class manages changes to `EDA_ITEMS`, which combines
+changes on any number of items into a single undo/redo action.
+When editing PCBs, changes to the PCB are managed by the derived
+`BOARD_COMMIT` class.
+
+This class takes either a `PCB_BASE_FRAME` or a `PCB_TOOL` as an
+argument. Using `PCB_TOOL` is more appropriate for a GAL tool, since
+there's no need to go though a frame class if not required.
+
+The procedure of a commit is:
+
+* Construct an appropriate `COMMIT` object
+* Before modifying any item, add it to the commit with `Modify( item )`
+  so that the current item state can be stored as an undo point.
+* When adding a new item, call `Add( item )`. The commit object now
+  owns that item, do not delete it.
+* When removing an item, call `Remove( item )`.
+* Finalise the commit with `Push( "Description" )`. If you performed
+  no modifications, additions or removals, this is a no-op, so you
+  don't need to check if you made any changes before pushing.
+
+If you want to abort a commit, you can just destruct it, without
+calling `Push()`. The underlying model won't be updated.
+
+As an example:
+
+    // Construct commit from current PCB_TOOL
+    BOARD_COMMIT commit( this );
+
+    BOARD_ITEM* modifiedItem = getSomeItemToModify();
+
+    // tell the commit we're going to change the item
+    commit.Modify( modifiedItem );
+
+    // update the item
+    modifiedItem->Move( x, y );
+
+    // create a new item
+    DRAWSEGMENT* newItem = new DRAWSEGMENT;
+
+    // ... set up item here
+
+    // add to commit
+    commit.Add( newItem );
+
+    // update the model and add the undo point
+    commit.Push( "Modified one item, added another" );
+
+
+# Tutorial: Adding a new tool {#tutorial}
 
 Without getting too heavily into the details of how the GAL tool framework
 is implemented under the surface, let's look at how you could add a
@@ -200,7 +259,7 @@ useless) functions:
     * A way to invoke the non-interactive "unfill all zones" tool from
       the PCB_EDITOR_CONTROL tool.
 
-## Add tool actions
+## Declare tool actions {#declare-actions}
 
 The first step is to add tool actions. We will implement two actions
 named:
@@ -211,13 +270,26 @@ named:
 The "unfill tool" already exists with the name
 `pcbnew.EditorControl.zoneUnfillAll`.
 
-In `pcbnew/tools/common_action.h`, we add the following to the
-`COMMON_ACTION` class, which declares our tools:
+This guide assumes we will be adding a tool to Pcbnew, but the
+procedure for other GAL-capable canvases will be similar.
+
+In `pcbnew/tools/pcb_actions.h`, we add the following to the
+`PCB_ACTIONS` class, which declares our tools:
 
     static TOOL_ACTION uselessMoveItemLeft;
     static TOOL_ACTION uselessFixedCircle;
 
-In `pcbnew/tools/common_action.cpp`, we then define the actions:
+Definitions of actions generally happen in the .cpp of the relevant tool.
+It doesn't actually matter where the defintion occurs (the declaration
+is enough to use the action), as long as it's linked in the end.
+Similar tools should always be defined together.
+
+In our case, since we're making a new tool, this will be in
+`pcbnew/tools/useless_tool.cpp`. If adding actions to existing tools,
+the prefix of the tool string (e.g. `"Pcbnew.UselessTool"`) will
+be a strong indicator as to where to define the tool.
+
+The tools definitions look like this:
 
     TOOL_ACTION COMMON_ACTIONS::uselessMoveItemLeft(
             "pcbnew.UselessTool.MoveItemLeft",
@@ -231,11 +303,12 @@ In `pcbnew/tools/common_action.cpp`, we then define the actions:
             add_circle_xpm );
 
 We have defined hotkeys for each action, and they are both global. This
-means you can use `Shift+Ctrl+L` and `Shift-Ctrl-R` to access each tool
+means you can use `Shift+Ctrl+L` and `Shift-Ctrl-C` to access each tool
 respectively.
 
 We defined an icon for one of the tools, which should appear in any
-menu the item is added to.
+menu the item is added to, along with the given label and explanatory
+tooltip.
 
 We now have two actions defined, but they are not connected to anything.
 We need to define a functions which implement the right actions.
@@ -246,7 +319,7 @@ and give you more scope for adding tool state.
 
 We will write our own tool to demonstrate the process.
 
-## Add tool class declaration
+## Add tool class declaration {#declare-tool-class}
 
 Add a new tool class header `pcbnew/tools/useless_tool.h` containing
 the following class:
@@ -280,7 +353,7 @@ the following class:
         TOOL_MENU m_menu;
     };
 
-## Implement tool class methods:
+## Implement tool class methods {#implement-tool}
 
 In the `pcbnew/tools/useless_tool.cpp`, implement the required methods.
 In this file, you might also add free function helpers, other classes,
@@ -293,22 +366,45 @@ Below you will find the contents of useless_tool.cpp:
 
     #include "useless_tool.h"
 
-    #include <wxPcbStruct.h>
     #include <class_draw_panel_gal.h>
     #include <view/view_controls.h>
     #include <view/view.h>
     #include <tool/tool_manager.h>
+    #include <board_commit.h>
 
+    // For frame ToolID values
     #include <pcbnew_id.h>
 
+    // For action icons
+    #include <bitmaps.h>
+
+    // Items tool can act on
     #include <class_board_item.h>
     #include <class_drawsegment.h>
-    #include <board_commit.h>
 
-    #include "common_actions.h"
+    // Access to other PCB actions and tools
+    #include "pcb_actions.h"
     #include "selection_tool.h"
 
 
+    /*
+     * Tool-specific action defintions
+     */
+    TOOL_ACTION PCB_ACTIONS::uselessMoveItemLeft(
+            "pcbnew.UselessTool.MoveItemLeft",
+            AS_GLOBAL, MD_CTRL + MD_SHIFT + int( 'L' ),
+            _( "Move item left" ), _( "Select and move item left" ) );
+
+    TOOL_ACTION PCB_ACTIONS::uselessFixedCircle(
+            "pcbnew.UselessTool.FixedCircle",
+            AS_GLOBAL, MD_CTRL + MD_SHIFT + int( 'C' ),
+            _( "Fixed circle" ), _( "Add a fixed size circle in a fixed place" ),
+            add_circle_xpm );
+
+    /*
+     * USELESS_TOOL implementation
+     */
+
     USELESS_TOOL::USELESS_TOOL() :
             PCB_TOOL( "pcbnew.UselessTool" ),
             m_menu( *this )
@@ -330,9 +426,9 @@ Below you will find the contents of useless_tool.cpp:
         auto& menu = m_menu.GetMenu();
 
         // add our own tool's action
-        menu.AddItem( COMMON_ACTIONS::uselessFixedCircle);
+        menu.AddItem( PCB_ACTIONS::uselessFixedCircle);
         // add the PCB_EDITOR_CONTROL's zone unfill all action
-        menu.AddItem( COMMON_ACTIONS::zoneUnfillAll);
+        menu.AddItem( PCB_ACTIONS::zoneUnfillAll);
 
         // Add standard zoom and grid tool actions
         m_menu.AddStandardSubMenus( *getEditFrame<PCB_BASE_FRAME>() );
@@ -340,7 +436,6 @@ Below you will find the contents of useless_tool.cpp:
         return true;
     }
 
-
     void USELESS_TOOL::moveLeftInt()
     {
         // we will call actions on the selection tool to get the current
@@ -349,8 +444,8 @@ Below you will find the contents of useless_tool.cpp:
         assert( selectionTool );
 
         // call the actions
-        m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear, true );
-        m_toolMgr->RunAction( COMMON_ACTIONS::selectionCursor, true );
+        m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
+        m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true );
         selectionTool->SanitizeSelection();
 
         const SELECTION& selection = selectionTool->GetSelection();
@@ -359,20 +454,25 @@ Below you will find the contents of useless_tool.cpp:
         if( selection.Empty() )
             return;
 
+        BOARD_COMMIT commit( this );
+
         // iterate BOARD_ITEM* container, moving each item
         for( auto item : selection )
         {
+            commit.Modify( item );
             item->Move( wxPoint(-5 * IU_PER_MM, 0) );
         }
-    }
 
+        // push commit - if selection were empty, this is a no-op
+        commit.Push( "Move left" );
+    }
 
     int USELESS_TOOL::moveLeft( const TOOL_EVENT& aEvent )
     {
         auto& frame = *getEditFrame<PCB_EDIT_FRAME>();
 
         // set tool hint and cursor (actually looks like a crosshair)
-        frame.SetToolID( ID_PCB_SHOW_1_RATSNEST_BUTT,
+        frame.SetToolID( ID_NO_TOOL_SELECTED,
                 wxCURSOR_PENCIL, _( "Select item to move left" ) );
 
         getViewControls()->ShowCursor( true );
@@ -412,8 +512,6 @@ Below you will find the contents of useless_tool.cpp:
 
     int USELESS_TOOL::fixedCircle( const TOOL_EVENT& aEvent )
     {
-        auto& frame = *getEditFrame<PCB_EDIT_FRAME>();
-
         // new circle to add (ideally use a smart pointer)
         DRAWSEGMENT* circle = new DRAWSEGMENT;
 
@@ -425,7 +523,7 @@ Below you will find the contents of useless_tool.cpp:
         circle->SetLayer(  LAYER_ID::F_SilkS );
 
         // commit the circle to the BOARD
-        BOARD_COMMIT commit( &frame );
+        BOARD_COMMIT commit( this );
         commit.Add( circle );
         commit.Push( _( "Draw a circle" ) );
 
@@ -435,11 +533,12 @@ Below you will find the contents of useless_tool.cpp:
 
     void USELESS_TOOL::SetTransitions()
     {
-        Go( &USELESS_TOOL::fixedCircle, COMMON_ACTIONS::uselessFixedCircle.MakeEvent() );
-        Go( &USELESS_TOOL::moveLeft,  COMMON_ACTIONS::uselessMoveItemLeft.MakeEvent() );
+        Go( &USELESS_TOOL::fixedCircle, PCB_ACTIONS::uselessFixedCircle.MakeEvent() );
+        Go( &USELESS_TOOL::moveLeft,    PCB_ACTIONS::uselessMoveItemLeft.MakeEvent() );
     }
 
-## Register the tool
+
+## Register the tool {#register-tool}
 
 The last step is to register the tool in the tool manager.
 
@@ -456,15 +555,18 @@ This is done by adding a new instance of the tool to the
         ....
     }
 
-## Build and run
+If your new tool applies in the module editor, you also need to do this
+in `FOOTPRINT_EDIT_FRAME::setupTools()`. Generally, each kind of
+`EDA_DRAW_FRAME` that can use GAL will have a place to do this.
+
+## Build and run {#tutorial-summary}
 
 When this is all done, you should have modified the following files:
 
 * `pcbnew/tools/common_actions.h` - action declarations
-* `pcbnew/tools/common_actions.cpp` - action definitions
-* `pcbnew/tools/useless_tool.h` - your tool header
-* `pcbnew/tools/useless_tool.cpp` - your tool implementation
-* `pcbnew/tools/tools_common.cpp` - registration of your tool
+* `pcbnew/tools/useless_tool.h` - tool header
+* `pcbnew/tools/useless_tool.cpp` - action definitions and tool implementation
+* `pcbnew/tools/tools_common.cpp` - registration of the tool
 * `pcbnew/CMakeLists.txt` - for building the new .cpp files
 
 When you run Pcbnew, you should be able to press `Shift+Ctrl+L` to
@@ -473,7 +575,7 @@ and "Select item to move left" appears in the bottom right corner.
 
 When you right-click, you get a menu, which contains an entry for
 our "create fixed circle" tool and one for the existing "unfill all
-zones" tool which we added to the menu. You can also use `Shift+Ctrl+R`
+zones" tool which we added to the menu. You can also use `Shift+Ctrl+C`
 to access the fixed circle action.
 
 Congratulations, you have just created your first KiCad tool!
-- 
2.11.0

_______________________________________________
Mailing list: https://launchpad.net/~kicad-developers
Post to     : [email protected]
Unsubscribe : https://launchpad.net/~kicad-developers
More help   : https://help.launchpad.net/ListHelp

Reply via email to