← Back to team overview

kicad-developers team mailing list archive

[PATCH] Update tool framework documention

 

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 <john.j.beard@xxxxxxxxx>
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


Follow ups