View this PageEdit this Page (locked)Attachments to this PageHistory of this PageHomeRecent ChangesSearch the SwikiHelp Guide
Hotspots: Admin Pages | Turn-in Site |
Current Links: Cases Final Project Summer 2007

How to create a Timer Widget using LEDTimerMorph

Uploaded Image: timer1.pngUploaded Image: timer2.pngUploaded Image: timer3.pngUploaded Image: timer4.pngUploaded Image: timer5.png

By David Eakes
Team More Powerful Than Superman, Batman, and Spiderman Put Together

Back during Milestone 1 we were asked to create a Timer Widget that changed colors depending on if you were ahead or behind in your project presentation. I failed that miserably, but I found this thing in the Squeak 3.8 API called LEDTimerMorph. For Milestone 3 we needed to have the Timer Widget working with the project, so it was up to me to make a Timer Widget that worked.

Start off by extending LEDTimerMorph to a new class.
LedTimerMorph subclass: #TimerWidget
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Widgets'


You're only going to need a few methods for this Timer; most of them are overridden methods from LEDTimerMorph. Clearly the hardest part about writing this will be the math you'll need to do to convert minutes to seconds.

LEDTimerMorph functioned based on the system clock and if you paused the visual aspect of the timer, it would continue to count down in the backend. You need to remove that, so you need to override the method "step". Essentially, all you will remove is the boolean about if the LEDTimerMorph is counting or not.

LEDTimerMorph's step method:
step
	flash
		ifTrue: [super step]
		ifFalse: [
			counting ifTrue: [self updateTime]]


TimerWidget's step method:
step
	flash
		ifTrue: [super step]
		ifFalse: [self updateTime].


So we now need to take care of that counting boolean, so we're going to write a method called "updateTime." Here's where all that math I warned you about comes into play. updateTime is overridden from LEDTimerMorph.

LEDTimerMorph's updateTime method:
updateTime

	self value:  Time totalSeconds - startSeconds.
	self changed


Not much happens, the timer just counts up, well we want the timer to count down, and we also want to be able to add minutes to this thing instead of hundreds or thousands of seconds.

TimerWidget's updateTime method:
updateTime
	"Updates the timer so long as there is time left on the clock"
	|temp|
	counting
		ifTrue: [			
			previousTime := Time totalSeconds.
			self value: (startSeconds - previousTime) \\ 60.
			currentTime := currentTime + 1.
			value = 59 ifTrue: [minutesLedMorph value: minutesLedMorph value - 1].
			minutesLedMorph value < 0 ifTrue: [
								counting := false. 
								self value: 0. 
								minutesLedMorph value: 0].
			self changeColor.
			self changed.]
		ifFalse: [
			temp := Time totalSeconds.
			startSeconds := startSeconds - (previousTime - temp).
			previousTime := temp.].

At this point you'll need to add instance variables "previousTime" and "minutesLedMorph." We'll setup minutesLedMorph later. All other instance variables in this method are in LEDTimerMorph. The algorithm in plain English is as follows:
If the timer is counting, update the previousTime variable to be the current second count. Set the value of the seconds to be the start time minus the current system time and mod that by 60 (it will be counting down). Add 1 to the current time. If value was just updated to be 59, then reduce the value of the minutesLedMorph by 1. If value of minutesLedMorph goes negative, stop the timer and set everything to zero (the timer reached 0). Change the color, and notify the morph that it is changed.
If the timer is not counting, you want to update the startSeconds by adding the difference of previousTime and the current time in seconds. (This takes care of the problem where the timer appears to jump when just using an LEDTimerMorph. Update previousTime to be the current time in seconds.

There are 2 things uncreated at this point. The instance variable minutesLedMorph and the method changeColor. First let's write changeColor. Simply all we want to do is check to see what color the timer should be. In this method we want the timer to be red if we are 2 or more slides behind in our presentation, yellow if we are 1 slide behind, green if we are on time, white if we are 1 slide ahead, and blue if we are more than 2 slides ahead. These colors can be anything you wish. We're going to need two more instance variables: "currentPage" and "timePerSlide". We'll make a method to set these later.
changeColor
	"changes the color of the TimerWidget"
	|temp|
	temp := currentTime - (currentPage * timePerSlide).
	 temp > timePerSlide
		ifTrue: [self color: Color red.]
		ifFalse: [temp > 0 
			ifTrue: [self color: Color yellow.]
			ifFalse: [temp > (-1 * timePerSlide)
				ifTrue: [self color: Color green.]
				ifFalse: [temp > (-2 * timePerSlide)
					ifTrue: [self color: Color white.]
					ifFalse: [self color: Color blue.]]]].
	minutesLedMorph color: (self color).


Now let's create that minutesLedMorph as well as anything else we need to display this widget. We'll do this in our initialize method. You'll need one more instance variable: "containerMorph." containerMorph will be a big morph that contains the pieces of our widget.
initialize
	"Initializes the TimerWidget with default position and page number."
	| advancePage |
	super initialize.
	digits := 2.
	currentPage := 1.
	self position: 70@0.
	minutesLedMorph := LedMorph new
		position: 10@0.
	advancePage := SimpleButtonMorph new.
	advancePage target: self.
	advancePage actionSelector: #nextSlide.
	advancePage label: 'Advance Page'.
	advancePage position: 20@45.
	containerMorph := PasteUpMorph new
		extent: 130@70.
	containerMorph addMorph: minutesLedMorph.
	containerMorph addMorph: self.
	containerMorph addMorph: advancePage.
	containerMorph openInWorld.

This will pop up a TimerWidget with default positions and default currentPage. The Advance Page button will tell the timer the page has advanced, so it can update the current page. You'll notice it calls nextSlide, which we will write next. This button can later be edited to advance the page in a slideshow or removed all together if you wish.
nextSlide
	"Advance one slide"
	currentPage := currentPage + 1.

This simply adds one to the currentPage, but suppose you wanted to jump 8 slides ahead. Instead of hitting that button 8 times, you could write another method that changes the currentPage value.
currentPage: aNumber
	"Sets the currentPage"
	((aNumber <= numOfSlides) and: [aNumber >= 1]) ifTrue:
		[currentPage := aNumber.]

numOfSlides is the total number of slides in your presentation.
We need to override "resume."
LEDTimerMorph's resume:
resume
	counting ifFalse: [
		counting := true.
		startSeconds :=  (Time totalSeconds) - self value]

We don't need all that mess, all we need to do is make sure that counting is true.
TimerWidget's resume:
resume
	"resume the timer"
	counting := true.

We'll need to be able to start this thing. To start this thing, we're writing a method called "startSlides:time:".
startSlides: numberOfSlides time: secondsPerSlide
	"Starts the timer taking in as parameters the number of slides and the time per slide"
	numOfSlides := numberOfSlides.
	timePerSlide := secondsPerSlide.
	timeInSeconds := numOfSlides * timePerSlide.
	minutes := timeInSeconds // 60.
	seconds := timeInSeconds \\ 60.
	minutesLedMorph value: minutes.
	startSeconds := Time totalSeconds + seconds.
	currentTime := 0.
	super start.

This sets the number of slides and the time you wish to spend per slide and sets the timer. To make life easier I made instance variables "timeInSeconds", "minutes", "seconds". In addition, you may wish to override the method "start," but I found it unnecessary.

That's it, you should now have yourself a Timer Widget. Open yourself a workspace and do this:
t := TimerWidget new.
t startSlides: 10 time: 10.

...and see it in action.

TimerWidget complete source code

Links to this Page