The Artima Developer Community
Sponsored Link

Agile Buzz Forum
How To Create A Custom Widget - MacOSX Blue Haze

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 Create A Custom Widget - MacOSX Blue Haze Posted: May 17, 2005 4:59 PM
Reply to this message Reply

This post originated from an RSS feed registered with Agile Buzz by James Robertson.
Original Post: How To Create A Custom Widget - MacOSX Blue Haze
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 deal with the rather complex task of the blue haze, aka the selection rectangle, for the MacOSX look of the Calendar, and the effect it has on other looks.


The Problem

In the MacOSX look, the blue haze around any pane, even when it isn't visible actually takes up visible area inside the bounds of the pane. So, when with other looks, we ask for a pane that uses a layout rectangle of 10 @ 10 corner: 110 @ 40, the full area we see, whether selected or not is 100 @ 30. But, on the MacOSX look, when not selected, the pane takes up an area of 96 @ 26, and visibly, looks to have an origin of 12 @ 12.

Therefore, when in the MacOSX look, we have to tell it to tell the action and display parts to be smaller, and at a different origin than stipulated.


The Resolution - Action Part

First, we add a couple of constant methods to our artists:

	CalendarArtist>>displayOffset
		^0

+-+-+

	MacOSXCalendarArtist>>displayOffset
		^2

And we use these methods in our #createActionPart as such:

	CalendarArtist>>createActionPart
		actionPart := #{Pollock.Button} value new.
		actionPart frame: (FractionalFrame fractionLeft: 1 top: 0 right: 1 bottom: 1).
		actionPart frame leftOffset: [self buttonWidth negated].
		actionPart frame topOffset: self displayOffset.
		actionPart frame rightOffset: self displayOffset negated.
		actionPart frame bottomOffset: self displayOffset negated.
		actionPart setArtist: (pane widgetPolicy dropDownListButtonArtistClass on: actionPart).
		actionPart interiorDecoration: pane borderPolicyClass dropDownListButtonDecoration.
		actionPart addComponent: self calendarButtonImage.
		actionPart agent showSelected: self showSelected.
		actionPart setEnclosingPane: pane.
		actionPart 
			when: #pressed
			send: #openCalendarWindow
			to: self agent

This forces the offset of the frame to be inset 2 from the top, right and bottom. The left (the edge next to the input field) is already offset. Now if we have the MacOSX look on (using our look changer: PollockWorkspaceWork new openLookSwitcherWindow) and open our tester (CalendarTest new openWindowWithCalendar) we see our action part is a bit smaller than the input part, but otherwise all is well.


The Resolution - Display Part

We have to do a similar change to our #createDisplayPart:

	Calendar>>createDisplayPart
		displayPart := InputField new.
		displayPart frame: (FractionalFrame 
			fractionLeft: 0
			top: 0
			right: 1
			bottom: 1).
		displayPart frame rightOffset: [self artist buttonWidth negated].
		displayPart frame topOffset: self artist displayOffset.
		displayPart frame leftOffset: self artist displayOffset.
		displayPart frame bottomOffset: self artist displayOffset negated.
		displayPart setArtist: (self widgetPolicy dropDownListInputFieldArtistClass on: displayPart).
		displayPart interiorDecoration: self borderPolicyClass calendarInputFieldDecoration.
		displayPart setEnclosingPane: self.
		self isOpen
			ifTrue: [displayPart setupKeyboardFor: self]
			ifFalse: [self configure: #setupKeyboardFor: for: displayPart argument: self].
		displayPart when: #clicked send: #triggerEvent: to: self with: #clicked.
		displayPart when: #rightClicked send: #triggerEvent: to: self with: #rightClicked.
		displayPart when: #doubleClicked send: #triggerEvent: to: self with: #doubleClicked.
		displayPart when: #aboutToLoseFocus send: #triggerEvent: to: self with: #aboutToLoseFocus.
		displayPart when: #losingFocus send: #triggerEvent: to: self with: #losingFocus.
		displayPart agent doNotIncludeInTabList.
		self when: #gettingFocus send: #activate to: displayPart.
		self when: #losingFocus send: #deactivate to: displayPart

Now, closing and opening our tester, all looks pretty good although a bit smaller than before. But that's exactly right.


Into The Haze

Now all that's left is to draw the haze when the pane is selected. Fortunately, the code that is in MacOSXDropDownListArtist will do the trick for us:

	MacOSXCalendarArtist>>displaySelectionRectangleOn: aGraphicsContext in: aRectangle 
		| selectionRectangle oldPaint targetRectangle oldClippingRectangle |
		aRectangle ifNil: [^self].
		oldClippingRectangle := aGraphicsContext clippingBounds.
		selectionRectangle := self selectionRectangleFrom: aRectangle.
		aGraphicsContext clippingRectangle: (aRectangle intersect: oldClippingRectangle).
		oldPaint := aGraphicsContext paint.
		aGraphicsContext paint: SymbolicPaint checkBoxTraversalHilite.
		aGraphicsContext displayRectangularBorder: selectionRectangle.
		aGraphicsContext displayRectangularBorder: (selectionRectangle insetBy: 1).
		targetRectangle := selectionRectangle insetBy: 2.
		aGraphicsContext displayRectangle: (0 @ 0 extent: 1 @ 1) at: targetRectangle origin.
		aGraphicsContext displayRectangle: (0 @ 0 extent: 2 @ 1) at: targetRectangle topRight + (-1 @ 0).
		aGraphicsContext displayRectangle: (0 @ 0 extent: 1 @ 1) at: targetRectangle topRight + (0 @ 1).
		aGraphicsContext displayRectangle: (0 @ 0 extent: 2 @ 1) at: targetRectangle corner + (-1 @ 0).
		aGraphicsContext displayRectangle: (0 @ 0 extent: 1 @ 1) at: targetRectangle corner + (0 @ -1).
		aGraphicsContext displayRectangle: (0 @ 0 extent: 1 @ 1) at: targetRectangle bottomLeft.
		aGraphicsContext paint: oldPaint.
		aGraphicsContext clippingRectangle: oldClippingRectangle.

+-+-+

	MacOSXCalendarArtist>>selectionRectangleFrom: aRectangle 
		"We clip one pixel off the extension because we can't write the selection rectangle ON the edge"

		^aRectangle origin corner: aRectangle corner - 1

We in the main method, we do the usual clipping stuff. And then we call our #selectionRectangleFrom: method. The comment basically says it all... the point is that we can't draw ON the edge of a clipped rectangle. From there on, we use the #checkBoxTraversalHilite color, which has been set up for us to be the blue haze color by the MacOSXLookPolicy.

We draw the outer rectangle, then the inner rectangle, thus giving us a two wide paint. Then we get a new rectangle that is inset 2 from the outermost rectangle. With this rectangle, we paint the left two corners (origin and bottomLeft). This gives the illusion of the edge of the input field being a bit rounded when selected. It's a minor detail but that much better that we take care to match. Between those corer dots, we fill in the "rounded" area of the right top and right bottom (corner) where the button part does not display.

Now the standard CalendarArtist doesn't display a selection rectangle, so we have to write a do nothing method there:

	CalendarArtist>>displaySelectionRectangleOn: aGraphicsContext in: aRectangle
		"By default, I don't do this, subclasses may"

Finally, we add to our standard #displayOn: method, in CalendarArtist:

	CalendarArtist>>displayOn: aGraphicsContext
		| oldClippingRectangle |
		self isVisible ifFalse: [^self].
		actionPart ifNil: [self createActionPart].
		oldClippingRectangle := aGraphicsContext clippingBounds.
		(oldClippingRectangle intersects: self frameVisibleRectangle) ifFalse: [^self].
		aGraphicsContext clippingRectangle: (self frameVisibleRectangle intersect: oldClippingRectangle).
		self hasInteriorDecoration ifTrue: [self interiorDecoration displayOn: aGraphicsContext in: self frameVisibleRectangle].
		(oldClippingRectangle intersects: self frameDisplayableRectangle) ifTrue:
			[aGraphicsContext clippingRectangle: (self frameDisplayableRectangle intersect: oldClippingRectangle).
			actionPart displayOn: aGraphicsContext.
			self displayPart displayOn: aGraphicsContext.
			self agent isSelected ifTrue: [self displaySelectionRectangleOn: aGraphicsContext in: self frameVisibleRectangle]].
		aGraphicsContext clippingRectangle: oldClippingRectangle.

There we go. Now we open our tester, click on the Calendar and....


Mouse Handling

Yup, the haze didn't show! What went wrong?

The hint is in the "isSelected ifTrue:" of our #displayOn:. As we said WAY long ago, and dealt with in our #handlerForMouseEvent: of the CalendarAgent, the Calendar pane itself doesn't do keyboard handling and passes all mouse events to it's underlying display and action panes (the input field and button). What we have to do now is to change our #handlerForMouseEvent: method, so when it sees a mouse pressed event, it tells the Calendar it is selected.

Here's the new #handleMouseEvent: code.

	CalendarAgent>>handlerForMouseEvent: anEvent
		pane actionPart ifNotNil:
			[:button | 
			(button containsPointForEvent: anEvent) ifTrue: 
				[anEvent isButtonPressedEvent ifTrue: [self beSelected: true].
				^button controller]].
		pane displayPart ifNotNil:
			[:inputField |
			(inputField containsPointForEvent: anEvent) ifTrue: 
				[anEvent isButtonPressedEvent ifTrue: [self beSelected: true].
				^inputField controller]].
		^nil

Basically, we check if the event can even go to the part, and if so, and it is a button press, we tell ourselves to be selected. Now, if we open our tester and click on the action part (the button) of our Calendar, the blue haze comes on. Then if we click in the display part (the input field)...


Wrapper Hall Of Fame

The haze didn't show. In fact, it disappeared! What's wrong now?!?!

One of the unfortunate side effect of using Wrapper's Keyboard Processor and Event Dispatcher sub-frameworks, is that when our input field gets focus, one of the thing it does is send deactivate to the Calendar pane itself... actually, it's agent via the controller.

This little bit of horror will be done away with in Pollock Feature Set 2, when we write a new Keyboard Processor that handles independent tab order, and get rid of Controllers all together with a whole new Event Dispatcher sub-framework.

In the mean time, we have to add a bit of Wrapper compatibility to our otherwise pure work. We simply tell our agent to ignore the #deactivate method:

	CalendarAgent>>deactivate
		"Do Nothing because we still use Wrapper's Event Dispatcher and Keyboard Processor sub-frameworks"

This is mostly safe, but will cause us some small problems later. But later is later, so for now, it does what we want.

Now, if we open our tester, and click either on the action part (the button) or the display part (the input field) we get our blue haze.

The above is published as version 1.21 in the Package named "Pollock-Calendar" on the Cincom public repository.

If we change the look, to either Windows or Motif... Well, it seems that our pane stays the small size, and other uglies creep in. Next time, we'll deal with how to make sure everything looks right, with the right sizes of all sub-components, when we change the looks.


And So It Goes
Sames

Read: How To Create A Custom Widget - MacOSX Blue Haze

Topic: Case law update - SCO vs. IBM Previous Topic   Next Topic Topic: Managing Open Source Software as a Licensee

Sponsored Links



Google
  Web Artima.com   

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