You can create some great user experience by creating your own custom interaction mode. Let’s go through the steps to create one.
As an example I create a mode where the user can drag a cable in a perpendicular motion to a new location. The movie plays like this:
- When the user is near a cable, the cable will highlight.
- If the user clicks and holds the left mouse button, the highlit cable will be dragged.
- The cable will move with the motion of the mouse, but only in a perpendicular motion.
- When the mouse button is released, the cable is moved to the new location.

These are the steps to implement this:
1) Add the plugin to the config.xml of the application. The name of the plugin is "custom_mode" in this example, the name of the mode is drag_cable.
<config>
<plugins>
<plugin name="custom_mode" class_name="custom_interaction_mode_plugin"/>
<plugin name="maps" class_name="map_manager" >
<interaction_modes>
<mode name="select"/>
<mode name="geometry"/>
<mode name="zoom_in"/>
<mode name="zoom_out"/>
<mode name="pan"/>
<mode name="custom_mode.drag_cable"/>
<cycle_sequence modes="select,geometry"/>
</interaction_modes>
<!-- and the rest -->
</plugin>
</config>
2) Create a plugin custom_interaction_mode_plugin. Let it inherit from :custom_interaction_mode_mixin.
3) Implement the method get_interaction_mode_action(). Let it return the sw_action that represents the button in the toolbar.
_pragma(classify_level=basic, topic={demo})
_method custom_interaction_mode_plugin.get_interaction_mode_action(mode_name)
## Return the sw_action that defines the button that activates
## our mode.
_if mode_name _is :drag_cable
_then
_return sw_action.new(mode_name,
:engine, _self,
:tooltip, "Drag a cable to a new position",
:image, { mode_name, _self.module_name },
:value, mode_name )
_endif
_endmethod
$
This will add a 6th interaction mode to the toolbar.

4) Implement the method get_interaction_mode(). Let it return the interaction_mode. The interaction_mode has many options but the core is to define the actions and install a handler to handle the actions.
_pragma(classify_level=basic, topic={demo})
_method custom_interaction_mode_plugin.get_interaction_mode( name, logical_name )
## Return the interaction mode that will find and drag the
## cable. Set the cursors, move actions, event handlers and the lot.
_if logical_name _isnt :drag_cable _then _return _unset _endif
…
m << interaction_mode.new( :drag_cable, _unset )
…
# When moving around on the screen, the :move action will be
# send on every move.
m.set_move_actions(0, :move)
…
# Setup the drag actions, set the actions that are send during
# the drag.
m.set_drag_actions(:left, 0, {:start_drag, :drag, :end_drag})
…
# Installs the event handlers
m.add_event_handler( :action, _self, :|handle_drag_action()| )
_return m
_endmethod
$
The code above instructs the interaction mode to send events to handle_drag_mode(). When the mouse moves around it should send the action :move. When the user clicks and hold the mouse down, the action :start_drag is send, during the drag the action :drag is send and upon release the action :end_drag is send.
5) Implement the event handler to deal with all the actions flying around.
_pragma(classify_level=basic, topic={demo})
_method custom_interaction_mode_plugin.handle_drag_action(action,start_coord,end_coord,a_window)
## the master method that is invoked on every event once the
## the mode has started. Return :event_handled to indicate that
## that no other handlers need to do anthing anymore.
_local handled? << _true
_if action _is :move
_then
_self.handle_move_action(a_window, end_coord)
_else
_if .cable _isnt _unset
_then
_if action _is :start_drag
_then
_self.start_drag(a_window)
_elif action _is :press
_then
_elif action _is :drag
_then
_self.dragging(a_window, end_coord)
_elif action _is :end_drag
_then
_self.end_drag(a_window, end_coord)
_elif action _is :abort
_then
_self.abort_drag(a_window, end_coord)
_else
handled? << _false
_endif
_else
handled? << _false
_endif
_endif
_if handled? _then _return :event_handled _endif
_endmethod
$
This method is ugly, but more on that at the end of this page.
These are the fundaments of creating an interaction mode. There are a few things to consider.
Chaining
I want the user to be able to zoom and pan during my interaction mode. However I don’t want to copy all the zoom and pan sourcecode in my plugin. The way to deal with this is to chain the interaction_mode to an existing interaction_mode. All events that are not handled by my interaction mode will be handled by the chained interaction mode.
These are the steps to implement this:
1) Add an event handler to catch the start of the interaction mode.
…
m.add_event_handler( :start_interaction, _self, :|start()|)
…
2) Chain the custom mode to the pan mode.
_pragma(classify_level=basic, topic={demo})
_method custom_interaction_mode_plugin.start(mode)
## Make self dependent on the document to catch the refresh
## actions. Also chain my interaction handler to the :pan handler.
_local handler << mode.framework
_local chain_mode << handler.mode(:pan)
mode.chain_to << chain_mode
_endmethod
$
3) Let the action event handler return the symbol :event_handled if the event handler handed the event. The chained interaction mode will be skipped if the action was handled.
if handled? _then _return :event_handled _endif
That’s it!
Refresh
If the screen gets refreshed during the interaction, the highlight will be messed up. The interaction_mode has no build-in way to counteract this, so we have to program it.
These are the steps to implement this:
1) Add event handers to notice the start and stop of the interaction. Make the plugin dependent on the map_view when the interaction starts and remove the dependency when the interaction stops.
…
m.add_event_handler( :start_interaction, _self, :|start()|)
m.add_event_handler( :end_interaction, _self, :|end()|)
…
_pragma(classify_level=basic, topic={demo})
_method custom_interaction_mode_plugin.start(mode)
## Make self dependent on the document to catch the refresh
## actions. Also chain my interaction handler to the :pan handler.
_local handler << mode.framework
_local map_gui << handler.framework
map_gui.document.add_dependent(_self, :post_render)
_endmethod
$
_pragma(classify_level=basic, topic={demo})
_method custom_interaction_mode_plugin.end(mode)
## remove dependency on the document.
_local handler << mode.framework
_local map_gui << handler.framework
map_gui.document.remove_dependent(_self)
_endmethod
$
2) Implement the note_change() method to note the end of the refresh and redo the highlight.
_pragma(classify_level=basic, topic={demo})
_method custom_interaction_mode_plugin.note_change(p_map_view, p_what)
_if p_what _is :post_render
_then
_self.redraw_after_refresh(p_map_view.window)
_endif
_endmethod
$
Code
The complete sourcecode of the plugin is attached here. Load it into the Cambridge application and make the required changes to the config.xml of the application. Enjoy.
Better?
The reader with a software engineering background will notice that there is design pattern in this plugin screaming to get out: the state pattern. Check this page to see the refactored plugin.





