The Artima Developer Community
Sponsored Link

Agile Buzz Forum
How To Build A GUI WIth Pollock - Main Menu - Part 2

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
How To Build A GUI WIth Pollock - Main Menu - Part 2 Posted: Jul 7, 2004 2:26 PM
Reply to this message Reply

This post originated from an RSS feed registered with Agile Buzz by James Robertson.
Original Post: How To Build A GUI WIth Pollock - Main Menu - Part 2
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 start with a bug fix we'll need for the work we're doing today:

	MenuBarItemAgent>>mouseExitMovedEvent: anEvent
		showActive := false.
		pane enclosingPane ifNotNil: [:value | value hasOpenSubmenu ifFalse: [pane border: nil]]

If you don't change this, then you will get a "Does Not Understand" for nil when we do our Look changing.

Now, on to the main task at hand. We'll start off simply by adding a simple action to our "Exit" menu item:

		submenu addMenuItem: ((MenuItem label: '&Exit') 
			nameKey: #exit;
			image: (ResourceRetriever new className: #{ToolbarIconLibrary}; selector: #abort);
			shortcutKey: #F4;
			shortcutModifiers: InputState altMask;
			action: #close).

Here, we add the action that we want to be executed when the menu item is chosen. UserInterface instances already understand the #close method, so we don't have to do anything else to get this menu item to work. If you have been following along, you'll note that if you pressed Alt-F4, the close already worked. This is because by default, all windows have Alt-F4 set to close. However, the menu option, when selected by the mouse, up until now, didn't do anything. Now it does what we expect.

Next we'll do the &Open On Selected Class menu item:

		submenu addMenuItem: ((MenuItem label: '&Open On Selected Class') 
			nameKey: #openOnClass;
			image: (ResourceRetriever new className: #{ToolbarIconLibrary}; selector: #fileOpen);
			shortcutKey: #F5;
			shortcutModifiers: InputState ctrlMask + InputState altMask;
			action: #openOnClass).

Here, we add an action named #openOnClass. We'll add code to be executed when that is selected:

	openOnClass
		self classTree selectionDo: [:each | self class openWith: each]

If we open our ClassHierarchyBrowser right now, and select a class, and then select that menu item, we'll get a "Does Not Understand" on #openWith:... which is as it should be. We need a couple of things to do this. First, we need to add an #openWith: to the class side of our ClassHierarchyBrowser, and then we need to do some work so that it opens on something other than Object all the time. First, the class openWith: method:

	openWith: aClass
		(self new setInitialClass: aClass) open.

That code implies we need a #setInitialClass: method on the instance side... But first, let's add an instance variable to our definition to hold this new value, and then the new method, as well as an accessor method for the new instance variable:

	Smalltalk defineClass: #ClassHierarchyBrowser
		superclass: #{Pollock.UserInterface}
		indexedType: #none
		private: false
		instanceVariableNames: 'initialClass '
		classInstanceVariableNames: ''
	...

-+-+-+-

	setInitialClass: aClass
		initialClass := aClass

-+-+-+

	initialClass
		^initialClass ifNil: [Object]

Now we just need to change how we hookup the user interface to use "self initialClass" instead of Object by default:

	createInterface
		...
		pane id: #ClassTree.
		self addComponent: pane.
		pane tree: (Pollock.Tree 
			on: self initialClass
			childrenBlock: [:each | each subclasses]
			hasChildrenBlock: [:each | each subclasses notEmpty]).
		pane expandRootFully.

There we go. If we open our ClassHierarchyBrowser, and select a class, it opens a new browser on the selected class. If nothing is selected, then nothing happens. However, to me, that isn't very satisfying. I would prefer if the menu item was disabled when there are no classes selected. We'll do that by adding another UserInterface framework method which in this case is called after the #getMainMenu method is called... which is named #hookupMainMenu:

	hookupMainMenu
		(self menuBar menu atNameKey: #openOnClass) enabled: [self classTree selections notEmpty].

Now the menu item is only enabled when an item is selected in the Class tree pane. You notice that we give it a block for the enabled value. All dynamic menu item values, such as enablement and hidden-ness, can use either a Block, an ObservedValue or a Symbol as the value you give them to act on. If it is a Block or an ObservedValue, it gets the value by sending evaluate to the value. If it is a Symbol, then it asks the UserInterface to perform the method by that name, and then uses the result answered by that execution.

So far, we've done the E-Z menu items. Now for the tougher ones. We'll start with the Look Changing menu items. We'll start with giving the same action to each of these menu items, and that action will be #changeLookTo::

		submenu addMenuItem: ((MenuItem label: '&Microsoft Look') 
			nameKey: #microsoftLook;
			shortcutKey: $M ;
			action: #changeLookTo:).
		submenu addMenuItem: ((MenuItem label: 'MacOS&X Look') 
			nameKey: #macLook;
			shortcutKey: $X ;
			action: #changeLookTo:).
		submenu addMenuItem: ((MenuItem label: 'M&otif Look') 
			nameKey: #motifLook;
			shortcutKey: $O ;
			action: #changeLookTo:).

That's pretty straight forward. Notice, unlike our "Exit" menu item, here we're giving a keyword selector. When a menu item comes across an action that takes one argument, it sends itself along as the argument. That leads us to our #changeLookTo: method:

	changeLookTo: aMenuItem

		aMenuItem nameKey == #microsoftLook
			ifTrue: [^Screen default defaultWidgetPolicy class == Win98WidgetPolicy ifFalse: [Win98LookPolicy installLookPreferences]].
		aMenuItem nameKey == #motifLook
			ifTrue: [^Screen default defaultWidgetPolicy class == MotifWidgetPolicy ifFalse: [MotifLookPolicy installLookPreferences]].
		aMenuItem nameKey ==  #macLook
			ifTrue: [^Screen default defaultWidgetPolicy class == Pollock.MacOSXWidgetPolicy ifFalse: [Pollock.MacOSXLookPolicy installLookPreferences]].

A little behind the scenes look here, as we see how simple it is in Pollock to change a look. Simply take a look and send #installLookPreferences to it. The other part is that the Screen default has a method that answers the current Widget policy. Now, it would have been cooler if we had added our own #changeToKey: method to UILookPolicy, and had it do the work for us. I'll leave that as an exercise for the reader if they wish.

This all works fine, mechanically speaking... but our goal was to have an indicator next to the menu items so that it will show us which look is active. To do this, we give each menu item that acts as a mutual exclusive indicator a shared value, which will be an ObservedValue, that has as its underlying value a Symbol. We'll name this value lookObservedValue, and add it as an instance variable:

	Smalltalk defineClass: #ClassHierarchyBrowser
		superclass: #{Pollock.UserInterface}
		indexedType: #none
		private: false
		instanceVariableNames: 'initialClass lookObservedValue'
	...

Here's how a mutual exclusive (or group indication) menu item knows if it is the "on" value. The value of the observed value that it shares with other items will tell an item that is the "on" value if that value is equal to its nameKey. Let's start off with an accessing method for our key value:

	currentLookKey

		Screen default defaultWidgetPolicy class == Win98WidgetPolicy ifTrue: [^#microsoftLook].
		Screen default defaultWidgetPolicy class == MotifWidgetPolicy ifTrue: [^#motifLook].
		Screen default defaultWidgetPolicy class == Pollock.MacOSXWidgetPolicy ifTrue: [^#macLook].
		^#none

Then we'll add an #initialize method to our ClassHierarchyBrowser to set up our new #lookObservedValue:

	initialize
		super initialize.
		lookObservedValue := self currentLookKey asObservedValue.

Don't be a goof like I was and forget the "super initialize" method the first time I wrote this. Bad things can happen when you forget to call super on initialize, and in Pollock, it will happen if you forget.

Back to the menu. All we need to do now is tell all of our look changing menu items that they share this new value we have set up... we'll extend the #hookupMainMenu method to do this:

	hookupMainMenu
		(self menuBar menu atNameKey: #openOnClass) enabled: [self classTree selections notEmpty].
		(self menuBar menu atNameKey: #microsoftLook) indication: lookObservedValue.
		(self menuBar menu atNameKey: #motifLook) indication: lookObservedValue.
		(self menuBar menu atNameKey: #macLook) indication: lookObservedValue.

Now, depending on which look you're using, when you change looks, then reopen the menu, the appropriate item has an Dot (Windows), Box (Motif) or a Check mark (MacOSX).

Finally, we want to have the "Console Text" menu item to toggle between "Normal" text, and "White On Black" text. Like the grouped items, we also set the indication for the menu item, however, instead of having a symbol, toggle menu items want a boolean value to say if they are on or off. We'll create another instance variable to hold on to that for us, and we'll name it textColorValue:

	Smalltalk defineClass: #ClassHierarchyBrowser
		superclass: #{Pollock.UserInterface}
		indexedType: #none
		private: false
		instanceVariableNames: 'initialClass lookObservedValue  textColorValue '
	...

Then we'll set it up in our initialize method:

	initialize
		super initialize.
		lookObservedValue := self currentLookKey asObservedValue.
		textColorValue := false asObservedValue.

Then we'll hook the value to our menu item in the hookupMainMenu method:

	hookupMainMenu
		...
		(self menuBar menu atNameKey: #macLook) indication: lookObservedValue.
		(self menuBar menu atNameKey: #consoleText) indication: textColorValue.

Finally, we have to give the menu item an action to perform, and the method to match that action... In #getMainMenu we write:

		...
		submenu addMenuItem: ((MenuItem label: '&Console Text')
			nameKey: #consoleText;
			image: (ResourceRetriever new className: #{ToolbarIconLibrary}; selector: #fileNew);
			shortcutKey: $C ;
			action: #toggleTextColors).
		...

And last but not least, we write the #toggleTextColors method:

	toggleTextColors
		textColorValue value
			ifTrue: [(self paneAt: #MethodText) foreground: nil; background: nil]
			ifFalse: [(self paneAt: #MethodText) foreground: ColorValue white; background: ColorValue black].
		textColorValue value: textColorValue value not.

Done! Our ClassHierarchyBrowser now as a nice, if simple, menu bar. We've demonstrated how to set up a menu so that it dynamically changes an item's enablement, as well as group and toggle indications. Lots of behavior, not much code.

The above is published as version 1.20 in the Package named "PollockBlog" on the Cincom public repository.

Next time, we'll add our the openOnClass behavior to the Class tree's popup menu as a way of demonstrating how popup menus work.


And So It Goes
Sames

Read: How To Build A GUI WIth Pollock - Main Menu - Part 2

Topic: How To Build A GUI With Pollock - Main Menu - Part 1 Previous Topic   Next Topic Topic: Security in a rush environment

Sponsored Links



Google
  Web Artima.com   

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