'Smalltalk Express Calorie Meter'! 'Terrence Ma'! 'Modified from On To Smalltalk'! Object subclass: #Food instanceVariableNames: 'name fCalories cCalories pCalories' classVariableNames: '' poolDictionaries: '' ! !Food methods! name ^ name. ! ! !Food methods! name: aString name := aString. ! ! !Food methods! fCalories ^ fCalories ! ! !Food methods! fCalories: aNumber fCalories := aNumber ! ! !Food methods! cCalories ^ cCalories ! ! !Food methods! cCalories: aNumber cCalories := aNumber ! ! !Food methods! pCalories ^ pCalories ! ! !Food methods! pCalories: aNumber pCalories := aNumber ! ! !Food methods! tCalories ^fCalories + cCalories + pCalories ! ! !Food class methods! collectFrom: aFile |inputStream collection food| inputStream := File pathName: aFile. collection := OrderedCollection new. [(food := inputStream nextWord) notNil] whileTrue: [collection add: ((Food new) name: food; fCalories: inputStream nextWord asInteger; cCalories: inputStream nextWord asInteger; pCalories: inputStream nextWord asInteger)]. inputStream close. ^ collection ! ! GraphPane subclass: #MeterGraphPane instanceVariableNames: 'min max value title' classVariableNames: '' poolDictionaries: 'ColorConstants' ! !MeterGraphPane methods! setMin: aNumber min := aNumber ! ! !MeterGraphPane methods! setMax: aNumber max := aNumber ! ! !MeterGraphPane methods! setValue: aNumber value := aNumber ! ! !MeterGraphPane methods! getValue ^ value ! ! !MeterGraphPane methods! setTitle: aString title := aString ! ! !MeterGraphPane methods! drawMeter | thePen meterWidth meterHeight xLft yBot xPointer yPointer ptrHeight ptrHalfWidth oldBackColor dialArray range step tics tickPlace tickHeight| self erase. thePen := self pen. meterHeight := 30. meterWidth := self width * 3 // 4. ptrHeight := 10. ptrHalfWidth := 5. xLft := self width - meterWidth // 2. yBot := self height * 3 // 8. "Draw frame for meter" thePen drawRectangle: ((xLft - ptrHalfWidth) @ yBot extent: (meterWidth + (ptrHalfWidth * 2)) @ meterHeight). "Draw tick marks. Adaped from code provided by Richard Lyon. Works well for all min and max values" range := max - min. range = 0 ifTrue: [self error: 'Cannot put tics in an interval of width zero']. range < 0 ifTrue: [step := -1. range := range negated] ifFalse: [step := 1]. [range <= 15] whileTrue: [range := range*10. step := step/10]. [range > 150] whileTrue: [range := range/10. step := step*10]. range <= 30 ifTrue: [tics := #(1 5 10)] ifFalse: [(range <= 60) ifTrue: [tics := #(2 10 20)] ifFalse: [tics := #(5 10 50)] ]. step := step * (tics at: 1). (((min // (step negated)) negated) to: (max // step)) do: [:k | (k \\ ((tics at: 3) / (tics at: 1)) = 0) ifTrue: [tickHeight := meterHeight * 3 // 4] ifFalse: [(k \\ ((tics at: 2) / (tics at: 1)) = 0) ifTrue: [tickHeight := meterHeight // 2] ifFalse: [tickHeight := meterHeight * 3 // 8]]. tickPlace := xLft + (k * meterWidth * step // max) - (min * meterWidth // range). thePen lineFrom: (tickPlace @ yBot) to: (tickPlace @ (yBot + tickHeight))]. "Write title" title notNil ifTrue: [thePen centerText: title at: ((self width) // 2) @ (yBot + meterHeight + (2 * thePen font height))]. "Write scale minimum, middle, and maximum" min notNil & max notNil ifTrue: [thePen centerText: min printString at: xLft @ yBot. thePen centerText: (max - min // 2 + min) printString at: (xLft + (meterWidth // 2)) @ yBot. thePen centerText: max printString at: (xLft + meterWidth) @ yBot]. "Draw pointer, if values set" min notNil & max notNil & value notNil ifTrue: [xPointer := (value - min) * meterWidth // (max - min) + xLft. yPointer := yBot + meterHeight - 1. oldBackColor := thePen backColor. thePen backColor: ClrBlack. dialArray := Array with: ((xPointer + ptrHalfWidth) @ yPointer) with: (xPointer @ (yPointer - ptrHeight)) with: ((xPointer - ptrHalfWidth) @ yPointer). thePen polygonFilled: dialArray. thePen backColor: oldBackColor] ! ! ViewManager subclass: #CalorieViewManager instanceVariableNames: 'food foodList' classVariableNames: '' poolDictionaries: '' ! !CalorieViewManager methods! initializeMeter: theMeter theMeter setMin: 0; setMax: 100; setTitle: 'Calorie Meter'; event: #display. ! ! !CalorieViewManager methods! initializeListBox: theListBox foodList := (Food collectFrom: 'D:\DevStu~1\Book\Smtalk~1\vtbls.txt'). theListBox contents: (foodList collect: [:p | p name]). ! ! !CalorieViewManager methods! itemSelected: theListBox food := foodList at: theListBox selection. (self paneNamed: 'CalorieMeter') setValue: food tCalories; event: #display. ! ! !CalorieViewManager methods! callDrawMeter: thePane thePane drawMeter. ! ! !CalorieViewManager methods! resetListBox | theFileDialog file | (theFileDialog := FileDialog new) fileSpec: '*.txt'; open. (file := theFileDialog file) notNil ifTrue: [foodList := (Food collectFrom: file). (self paneNamed: 'ListBox') contents: (foodList collect: [:p | p name]). (self paneNamed: 'CalorieMeter') setValue: nil; event: #display]. ! ! !CalorieViewManager methods! close: aPane Smalltalk isRunTime ifTrue: [(MessageBox confirm: 'Are you sure you want to exit?') ifTrue: [self close. ^Smalltalk exit] ifFalse: [^self]] ifFalse: [^self close] ! ! !CalorieViewManager methods! createViews | theTopPane theListBox theMeterPane theMenuWindow | "Set up all panes" self addView: (theTopPane := TopPane new). theListBox := ListBox new. theMeterPane := MeterGraphPane new. "Set up the top pane" theTopPane owner: self; labelWithoutPrefix: 'Calorie Meter'; noSmalltalkMenuBar; addSubpane: theListBox; addSubpane: theMeterPane; menuWindow: (theMenuWindow := MenuWindow new); when: #close perform: #close:; framingRatio: (1/4 @ (1/3) rightBottom: 3/4 @ (2/3)). "Set up the menu window" theMenuWindow addMenu: (Menu new title: '~Options'; owner: self; appendItem: '~Food type' selector: #resetListBox; appendSubMenu: (Menu new title: '~Mode'; owner: self; appendItem: '~Fat calories' selector: nil; appendItem: '~Total calories' selector: nil)). "Set up the list-box pane" theListBox owner: self; paneName: 'ListBox'; when: #getContents perform: #initializeListBox:; when: #select perform: #itemSelected:; framingRatio: (2/3 @ 0 rightBottom: 1 @ 1). "Set up the meter pane" theMeterPane owner: self; paneName: 'CalorieMeter'; noScrollBars; when: #getContents perform: #initializeMeter:; when: #display perform: #callDrawMeter:; framingRatio: (0 @ 0 rightBottom: 2/3 @ 1). ! !