The Artima Developer Community
Sponsored Link

Agile Buzz Forum
Pollock Changes - Part 5

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
James Robertson

Posts: 29924
Nickname: jarober61
Registered: Jun, 2003

David Buck, Smalltalker at large
Pollock Changes - Part 5 Posted: Sep 6, 2005 10:58 AM
Reply to this message Reply

This post originated from an RSS feed registered with Agile Buzz by James Robertson.
Original Post: Pollock Changes - Part 5
Feed Title: Pollock
Feed URL: http://www.cincomsmalltalk.com/rssBlog/pollock-rss.xml
Feed Description: Pollock - the next VW GUI
Latest Agile Buzz Posts
Latest Agile Buzz Posts by James Robertson
Latest Posts From Pollock

Advertisement

Today we discuss the new HotKey/Keystroke system.

Overview

Keymap and Actions is a whole new keyboard handling system that replaces the prior KeyboardPolicy system. So, to start with, the KeyboardPolicy system that was in Pollock is totally gone.

The design was created by Vassili and has the whole Splash/Tools system in mind.

There are three main objects in this system that the developer sees. They are named, Keymap, Action and Keystroke. The basic idea is that you take a Keystroke (or a series of Keystrokes that make a keystroke sequence) and map it to an Action which is held onto by a Keymap. At runtime, the Agent looks up the keystroke/sequence in the Keymap, and matches it to an Action, and executes that Action.

Keystroke

A Keystroke can be defined with the following creation methods:

	Keystroke key: <aSymbolOrCharacter>
	Keystroke key: <aSymbolOrCharacter> modifiedBy: <aCollectionOfSymbol>

	Keystroke alt: <aSymbolOrCharacter>
	Keystroke ctl: <aSymbolOrCharacter>
	Keystroke cmd: <aSymbolOrCharacter>
	Keystroke meta: <aSymbolOrCharacter>
	Keystroke shift: <aSymbolOrCharacter>

"<aSymbolOrCharacter>" Can be either a symbol that represents any "meta" keystroke, such as #Left, #Right, #AltL, #Home, etc., or a Character such as $A, $b, Character esc, etc.

"<aCollectionOfSymbol>" Is a collection, usually an immediate array, of the following symbols: #alt, #control, #shift, #meta, #command.

Note that we are already getting ready for new keyboard handling that will be coming in future, which will do the real thing for Mac OSs in terms of #command not mapping to #alt, and mapping to a unique modifier.

Additionally, there are the following helper creation methods, that create for you many of the typical special keystrokes:

	Keystroke backspace
	Keystroke backTab
	Keystroke del
	Keystroke enter
	Keystroke esc
	Keystroke return
	Keystroke space
	Keystroke tab

If you want to define a Keystroke sequence, such as if you wanted to create Emacs or other bizarre mappings, you simply chain Keystrokes together, separated by a comma:

	Keystroke esc, (Keystroke ctrl: $b)

This, for instance, is used in the TextEditor to make a selection Bold.

Action

Actions are objects that have an ID, an action to execute, plus an optional enabled value. The executable action can either be a Block or a MessageSend. Simple creation methods are provided so that you don't have to get into the internals of creating a MessageSend, and to create a Block version. The enabled value, what we are calling a Boolean Supplier (currently a Boolean, or Block that answers a boolean, or a ObservedValue that answers a boolean), is used to determine, at runtime, if the action is executable.

	Action id: <aSymbol> action: <aBlockOrMessageSend>
	Action id: <aSymbol> action: <aBlockOrMessageSend> enabled: <aBooleanSupplier>
	Action id: <aSymbol> receiver: <anObject> selector: <aSymbol>
	Action id: <aSymbol> receiver: <anObject> selector: <aSymbol> argument: <anObject>
	Action id: <aSymbol> receiver: <anObject> selector: <aSymbol> arguments: <aCollection>
	Action id: <aSymbol> receiver: <anObject> selector: <aSymbol> argument: <anObject> enabled: <aBooleanSupplier>
	Action id: <aSymbol> receiver: <anObject> selector: <aSymbol> arguments: <aCollection> enabled: <aBooleanSupplier>

Keymap

These two object come together in a Keymap.

Each Pane, Form, Window and UserInterface has it's own Keymap. A Keymap is a runtime created object, which contains the mapping between a Keystroke and an Action for the owning object.

You add mappings by using the binding manipulation instance methods of Keymap:

	map: <aKeystrokeOrKeystrokeSequence> to: <aSymbol>
	map: <aKeystrokeOrKeystrokeSequence> to: <aSymbol> onPlatforms: <aCollectionOfSymbol>
	map: <aKeystrokeOrKeystrokeSequence> to: <aSymbol> onPlatformsExcept: <aCollectionOfSymbol>

<aSymbol> represents the Action ID for an Action. <aCollectionOfSymbol> represents one or more symbols that can be answered by the OSHandle currentOS method (#win32, #mac or #unix).

With this, you can define the following:

	aKeymap
		map: (Keystroke key: Graphics.TextConstants.Ctrla) 
		to: #selectAll
		onPlatformsExcept: #(#mac).
	aKeymap
		map: (Keystroke cmd: $a) 
		to: #selectAll
		onPlatforms: #(#mac).

All Together Now

How these three classes fit together is where the fun comes in.

First, we need to get into detail about the Keymap. Keymaps can be chained to one another. A Keymap also has an owner. The owner is the object which actions for the keystrokes are looked up within. A Keymap is created with these methods:

	Keymap for: <anObject>
	Keymap withNextKeymap: <aKeymap>
	Keymap withNextKeymap: <aKeymap> for: <anObject>

Each Pane, Window and UserInterface has an instance variable named Keymap. During keystroke lookup, a the pane with focus looks to it's Keymap. If it can't find a matching keystroke in the main Keymap, it walks down the "next Keymap" chain looking for a match. Unless you use one of the withNextKeymap: methods, or set the nextKeymap, by default a Keymap's nextKeymap value is nil. The lookup stops when it gets to the nextKeymap that is nil. More on this below.

Panes (including Forms), Windows and UserInterfaces all have a Class Instance variable named commonKeymap. All these classes also have a Class initialize method, which initializes the commonKeymap for the specific subclass.

The Keymap value for each instance is created as a new Keymap with a copy of the commonKeymap as it's nextKeymap. Thus, all instances of a pane share a copy of the commonKeymap, but it is the "nextKeymap" allowing you to add more Keymaps for any instance in the actual instances' Keymap. You can thus override a keymapping in the commonKeymap by giving the local Keymap a different mapping, or just add your own special key mappings.

In Splash and the Tools, we will add things like "Code Editing" Keymaps as "nextKeymaps" to the commonKeymaps for you. Also, you can build your own "Pool" of Keymaps, and add them to your UserInterface and so on.

All Panes (including Forms), Windows and UserInterfaces all have an instance variable named actions. When instantiated, each of these goes to the class side to get their basic actions.

Basically, the "system" Actions and Keymaps are on the class side of these objects. For Actions, the method is #createActionsFor:

	#createActionsFor: <anObject>

Here is the example for Button:

	createActionsFor: instance 
		| actions |
		actions := OrderedCollection new.
		self addTabAndCancelActionsTo: actions for: instance.
		actions add: (Action 
			id: #emulatePressButton
			receiver: instance
			selector: #pressButtonAgent).
		^actions

There are a handful of helper action methods in AbstractPane:

	addCancelAndDefaultActionsTo: actions for: instance 
		actions add: (Action 
			id: #doDefaultAction 
			action: [instance keyboardProcessor doDefaultAction]).
		actions add: (Action 
			id: #doCancelAction 
			action: [instance keyboardProcessor doCancelAction])

	addTabActionsTo: actions for: instance 
		actions add: (Action 
			id: #nextPaneInTabOrder
			action: 
				[instance keyboardProcessor nextFieldFrom: instance.
				instance triggerEvent: #tabbed]).
		actions add: (Action 
			id: #previousPaneInTabOrder
			action: 
				[instance keyboardProcessor previousFieldFrom: instance.
				instance triggerEvent: #backTabbed])

	addTabAndCancelActionsTo: actions for: instance 
		self addTabActionsTo: actions for: instance.
		self addCancelAndDefaultActionsTo: actions for: instance

As you may imagine, these are used a lot.

For Keymaps, the method is addBindingsTo: <aKeymap> on the class side. Here is the example from Button:

	addBindingsTo: aKeymap 
		aKeymap
			map: Keystroke tab to: #nextPaneInTabOrder;
			map: (Keystroke ctrl: Character tab) to: #nextPaneInTabOrder;
			map: Keystroke backTab to: #previousPaneInTabOrder;
			map: (Keystroke key: Character tab modifiedBy: #(#shift #control)) to: #previousPaneInTabOrder;
			map: (Keystroke key: #BackTab) to: #previousPaneInTabOrder;
			map: (Keystroke shift: #BackTab) to: #previousPaneInTabOrder;
			map: Keystroke enter to: #doDefaultAction;
			map: Keystroke return to: #doDefaultAction;
			map: Keystroke space to: #emulatePressButton;
			map: Keystroke esc to: #doCancelAction;
		yourself.

At runtime, you can add to a Keymap by sending:

	<aPaneWindowOrUserInterface> keymap map: <aKestroke> to: ...

At runtime, you can add actions by sending:

	<aPaneWindowOrUserInterface> addAction: <anAction>

Like A Pretty Red Bow

How it all ties together (the nitty gritty details).

When you press a key, the system, as per usual, first looks to find the current keyboard consumer. Having found it, it sends #processKeyboardEvent: to that pane's Agent.

From there, it first tries to see if there is a KeyboardHook for that instance, and if so, tries to execute that keyboard hook. Note, because of the new system, keyboard hooks shouldn't be needed since you can add your own keymapings to instances. I left it in because, well, I'm sure you'd all complain if I didn't, and of course, I might be wrong that they aren't needed.

Next, it does the keystroke mapping lookup for that pane. Next it tries any shortcut keys. These are currently only used for Mnemonics on RadioButtons, CheckBoxes, Labels and GroupBoxs, and in a Window, for Accelerators in the MenuBar.

Next, if no one has processed the keystroke, the pane's enclosing pane (if there is one) is asked to do the same thing.

Finally, if no one anywhere in the hierarchy of widgets handles the keystroke, and if the keystroke is NOT a shortcut key and NOT a symbol, it is sent off to the local character key handler. This last is where keys for entering text and lookup in a list box occurs.

If you're interested in how it works, look at #processKeyboardEvent: in AbstractAgent.

The general overview is this. First, a keystroke is attempted to execute within the scope of the current widget in focus. Then, it moves to the enclosing pane. That pane if it is a Window or a Form then gives each of it's widgets a chance to respond. If none of those widgets act, then if the pane is a Form, it acts on any local actions that it has associated with that keystroke. If that Form has a UserInterface, it then get's a chance. If that pane is a Window, and that Window has a MenuBar, then the Menus attempt to do their thing. If after all that, the Window has a UserInterface (which is only not the case if you are for some reason not using the full UserInterface system of Pollock) finally the UserInterface gets a chance.

Thus, you can add a key binding that is associated with just a pane, a form that contains many panes, a UserInterface in a Form, a Window or a UserInterface that may be used in multiple windows (or forms).

This new system will totally got rid of all of the special processKeyboardEvent: methods, except two. The base one, in AbstractAgent will replaced all of the "If This If That" case code typically associated with keystroke processing.

Menus

Menu items also use Actions, and are tied into the larger action system. Unlike Wrapper, and unlike previous versions of Pollock, they no longer have the selector/block behavior that was directly embedded in the menu item itself. Nor do they have the old notion of an action performer. Instead, using Actions, they do lookup just like the way keystroke lookup does it. First trying the pane that currently has focus, and going from there, looking up actions (instead of just keystrokes) along the way.

Much More To Come

Next time we'll talk about the refactored events that all panes trigger, as well as the biggest change in Pollock: The death of Controllers.

And So It Goes
Sames

Read: Pollock Changes - Part 5

Topic: Keeping your back to the wall Previous Topic   Next Topic Topic: Building On A Flood Plain

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use