_THE MVC PARADIGMN AND SMALLTALK/V_ by Kenneth E. Ayers [LISTING ONE] open | frame | appName := String new. saved := true. editorPen := Pen new. imagePen := Pen new. frame := (Display boundingBox extent // 6) extent:(Display boundingBox extent * 2 // 3). topPane := TopPane new model:self; label:self label; menu:#windowMenu; minimumSize:frame extent; yourself. topPane addSubpane: (listPane := ListPane new model:self; name:#appList; change:#appSelection:; returnIndex:false; menu:#listMenu; framingRatio:(0 @ 0 extent:1/4 @ (2/3)); yourself). topPane addSubpane: (imagePane := GraphPane new model:self; name:#initImage:; menu:#noMenu; framingRatio:(0 @ (2/3) extent:1/4 @ (1/3)); yourself). topPane addSubpane: (editorPane := GraphPane new model:self; name:#initEditor:; menu:#editorMenu; change:#editIcon:; framingRatio:(1/4 @ 0 extent:3/4 @ 1); yourself). topPane reframe:frame. topPane dispatcher openWindow scheduleWindow. [LISTING TWO] open | frame listWid listHgt | saved := true. editorPen := Pen new. imagePen := Pen new. frame := (Display boundingBox extent // 6) extent:(Display boundingBox extent * 2 // 3). listWid := SysFont width * 10. listHgt := SysFont height * 10. topPane := TopPane new label:self label; model:self; menu:#windowMenu; minimumSize:frame extent; yourself. topPane addSubpane: (listPane := ListPane new model:self; name:#appList; change:#appSelection:; returnIndex:false; menu:#listMenu; framingBlock:[:aFrame | aFrame origin extent:listWid @ listHgt]; yourself). topPane addSubpane: (imagePane := GraphPane new model:self; name:#initImage:; menu:#noMenu; framingBlock:[:aFrame| aFrame origin + (0 @ listHgt) extent:(listWid @ (aFrame height - listHgt))]; yourself). topPane addSubpane: (editorPane := GraphPane new model:self; name:#initEditor:; menu:#editorMenu; change:#editIcon:; framingBlock:[:aFrame| aFrame origin + (listWid @ 0) extent:((aFrame width - listWid) @ aFrame height)]; yourself). topPane reframe:frame. topPane dispatcher openWindow scheduleWindow. [COMPLETE SMALLTALK/V SOURCE KEN AYERS'S ARTICLE IN NOVEMBER 1990 ISSUE OF DDJ] [Listing -- Class EmptyMenu] Menu subclass: #EmptyMenu instanceVariableNames: '' classVariableNames: '' poolDictionaries: ''. "***************************************************************" "** EmptyMenu instance methods **" "***************************************************************" popUpAt:aPoint "An empty menu does nothing -- answer nil." ^nil. popUpAt:aPoint for:anObject "An empty menu does nothing -- answer nil." ^nil. [Listing -- Class IconEditor] Object subclass: #IconEditor instanceVariableNames: 'scale cellSize cellOffset saved unZoom topPane listPane editorPane imagePane iconLibrary iconName selectedIcon gridForm editorPen imagePen' classVariableNames: 'IconSize ' poolDictionaries: 'FunctionKeys CharacterConstants'. "***************************************************************" "** IconEditor class methods **" "***************************************************************" initialize "Initialize the class variables." IconSize isNil ifTrue:[IconSize := 32@32]. new "Answer a new IconEditor." self initialize. ^super new. "***************************************************************" "** IconEditor instance methods **" "***************************************************************" "-----------------------------" "-- Window creation methods --" "-----------------------------" openOn:anIconLibrary "Open an IconEditor window on the Dictionary anIconLibrary." iconLibrary := anIconLibrary. self open. open "Open an IconEditor window." | frame | iconLibrary isNil ifTrue:[self initLibrary]. iconName := String new. saved := true. editorPen := Pen new. imagePen := Pen new. frame := (Display boundingBox extent // 6) extent:(Display boundingBox extent * 2 // 3). topPane := TopPane new model:self; label:self label; menu:#windowMenu; minimumSize:frame extent; yourself. topPane addSubpane: (listPane := ListPane new model:self; name:#iconList; change:#iconSelection:; returnIndex:false; menu:#listMenu; framingRatio:(0 @ 0 extent:1/4 @ (2/3)); yourself). topPane addSubpane: (imagePane := GraphPane new model:self; name:#initImage:; menu:#noMenu; framingRatio:(0 @ (2/3) extent:1/4 @ (1/3)); yourself). topPane addSubpane: (editorPane := GraphPane new model:self; name:#initEditor:; menu:#editorMenu; change:#editIcon:; framingRatio:(1/4 @ 0 extent:3/4 @ 1); yourself). topPane reframe:frame. topPane dispatcher openWindow scheduleWindow. "----------------------------" "-- Window support methods --" "----------------------------" windowMenu "Answer the menu for the IconEditor window." ^Menu labels:'collapse\cycle\frame\move\print\close' withCrs lines:#(5) selectors:#(collapse cycle resize printWindow move closeIt). listMenu "Answer the menu for the form list pane." ^Menu labels:'remove icon\create new icon\change size' withCrs lines:#() selectors:#(removeIt createIt resizeIt). editorMenu "Answer the menu for the Editor pane." selectedIcon isNil ifTrue:[^EmptyMenu new]. ^Menu labels:('invert\border\erase\save\print') withCrs lines:#(3) selectors:#(invertIt borderIt eraseIt saveIt printIcon). noMenu "Answer a do-nothing menu." ^EmptyMenu new. initEditor:aRect "Inititalize the editor pane." Display white:aRect. ^Form new extent:aRect extent. initImage:aRect "Inititalize the IconEditor image pane." Display white:aRect. ^Form new extent:aRect extent. iconList "Answer a String Array containing the names of the icons in the icon library." ^iconLibrary keys asArray. iconSelection:anIconName "The user has selected anIconName from the list. Make it the selected icon and re-initialize the editor." | anIcon | self saved ifFalse:[^self]. anIcon := iconLibrary at:anIconName ifAbsent:[nil]. anIcon isNil ifTrue:[^self]. selectedIcon := anIcon deepCopy. iconName := anIconName. selectedIcon extent = IconSize ifTrue:[self displayIcon] ifFalse:[ CursorManager execute change. self resizeIt:selectedIcon extent]. editIcon:aPoint "The select button has been pressed at aPoint. If the cursor is in the editor image, reverse that cell and all others that the cursor passes over until the select button is released." | currX currY refX refY editing newX newY | (editorPen clipRect containsPoint:Cursor offset) ifFalse:[^self]. currX := -1. currY := -1. refX := editorPen clipRect origin x. refY := editorPen clipRect origin y. editing := true. [editing] whileTrue:[ newX := (Cursor offset x - refX) // scale. newY := (Cursor offset y - refY) // scale. ((newX = currX) and:[newY = currY]) ifFalse:[ currX := newX. currY := newY. self setX:currX Y:currY. saved := false]. editing := (editorPen clipRect containsPoint:Cursor offset) and:[Terminal read ~= EndSelectFunction]]. selectedIcon copy:imagePen clipRect from:Display to:0@0 rule:Form over. "----------------------------" "-- Window control methods --" "----------------------------" showWindow "Redisplay the contents of the window's panes." topPane collapsed ifFalse:[self displayIcon]. activatePane "If the cursor is in the Editor pane, change its shape to a crosshair." (editorPane frame containsPoint:Cursor offset) ifTrue:[CursorManager hair change]. deactivatePane "If the cursor is not in the Editor pane, change its shape to the normal pointer." (editorPane frame containsPoint:Cursor offset) ifFalse:[CursorManager normal change]. reframePane:aPane aPane == editorPane ifTrue:[^self reframeEditor:aPane frame]. aPane == imagePane ifTrue:[^self reframeImage:aPane frame]. zoom "Zoom/unzoom the window." | frame | unZoom isNil ifTrue:[ "Zoom to full screen" unZoom := topPane frame. frame := Display boundingBox] ifFalse:[ frame := unZoom. unZoom := nil]. CursorManager execute change. topPane reframe:frame. Scheduler resume. close "Prepare for closing the window." self saved ifFalse:[self saveIcon]. self release. label "Answer the window's label." ^'IconEditor (Ver 2.1 - 07/24/90 - KEA)'. collapsedLabel "Answer the window's label when collapsed." ^'IconEditor'. "---------------------------------------" "-- List-pane menu processing methods --" "---------------------------------------" createIt "Create a new icon to edit." self saved ifTrue:[ self eraseImage; newIcon:self getIcon]. resizeIt "Change the size of the icon." | selection size | self saved ifFalse:[^self]. selection := (Menu labels: '8@8\16@16\32@32\64@64' withCrs lines: #() selectors:#(small medium large xLarge)) popUpAt:Cursor offset. selection isNil ifTrue:[^self]. selection == #small ifTrue:[size := 8@8]. selection == #medium ifTrue:[size := 16@16]. selection == #large ifTrue:[size := 32@32]. selection == #xLarge ifTrue:[size := 64@64]. self resizeIt:size. removeIt "Remove the selectedIcon from the icon library." iconLibrary removeKey:iconName ifAbsent:[]. self eraseEditor; eraseImage; changed:#iconList. selectedIcon := nil. iconName := ''. "-----------------------------------------" "-- Editor-pane menu processing methods --" "-----------------------------------------" eraseIt "Erase icon." (Form width:IconSize x height:IconSize y) displayAt:imagePen clipRect origin rule:Form over. (Form width:IconSize x * scale height:IconSize y * scale) displayAt:editorPen clipRect origin rule:Form over. gridForm displayAt:editorPen clipRect origin rule:Form andRule. self border:editorPen clipRect width:2 marksAt:(4 * scale). self border:imagePen clipRect width:2 marksAt:0. invertIt "Invert the color of the icon." selectedIcon := self getIcon reverse. self displayIcon:selectedIcon. saved := false. borderIt "Draw a border around the icon." | inset | (inset := Prompter prompt:'Inset?' default:'0') isNil ifTrue:[^self]. Display border:(imagePen clipRect insetBy:inset asInteger). self newIcon:self getIcon. saved := false. saveIt "Save the new icon image." self saveIcon. self changed:#iconList. listPane restoreSelected:iconName. self activatePane. printIcon "Print the magnified icon from the editor pane." CursorManager execute change. (Form fromDisplay:(editorPen clipRect expandBy:4)) outputToPrinterUpright. CursorManager normal change. printWindow "Print an image of the entire editor window." CursorManager execute change. (Form fromDisplay:topPane frame) outputToPrinterUpright. CursorManager normal change. "-----------------------------" "-- Display support methods --" "-----------------------------" border:aRect width:aWid marksAt:aStep "Draw a border, of width aWid, around aRect with ruler marks ever aStep cells." | box aForm orig wid hgt | box := aRect expandBy:aWid. 0 to: aWid - 1 do:[:inset| Display border:(box insetBy:inset)]. (aStep = 0) ifTrue: ["** No ruler marks!!!! **" ^nil]. aForm := (Form width:aWid height:aWid) reverse. orig := aRect origin. wid := aRect width. hgt := aRect height. 0 to:wid by:aStep do:[:x| aForm displayAt:(orig + (x @ ((aWid * 2) negated))); displayAt:(orig + (x @ (hgt + aWid)))]. 0 to:hgt by:aStep do:[:y| aForm displayAt:(orig + (((aWid * 2) negated) @ y)); displayAt:(orig + ((wid + aWid) @ y))]. editorBorder "Draw the ruled border around the editor frame." self border:editorPen clipRect width:2 marksAt:(4 * scale). imageBorder "Draw the solid border around the image frame." self border:imagePen clipRect width:2 marksAt:0. displayIcon "Display the selectedIcon for editing." selectedIcon isNil ifFalse:[ self displayIcon:selectedIcon]. displayIcon:anIcon "Display anIcon for editing." CursorManager execute change. self eraseImage; displayImage:anIcon; eraseEditor; editorBorder; displayEditor:anIcon. CursorManager normal change. self activatePane. displayEditor:anIcon "Display anIcon in the editor pane." | mask | (anIcon magnify:(0 @ 0 extent:anIcon extent) by:scale @ scale) displayAt:editorPen clipRect origin rule:Form over. mask := gridForm deepCopy reverse. scale > 4 ifTrue:[ mask displayOn:Display at:(editorPen clipRect origin moveBy:(1@1)) clippingBox:editorPen clipRect rule:Form orThru mask:Form white]. mask displayOn:Display at:(editorPen clipRect origin moveBy:(-1 @ -1)) clippingBox:editorPen clipRect rule:Form orThru mask:Form white. gridForm displayAt:editorPen clipRect origin rule:Form andRule. displayImage:anIcon "Display anIcon in the image pane." anIcon displayAt:imagePen clipRect origin rule:Form over. eraseEditor "Erase the editor pane." Display white:editorPane frame. eraseImage "Erase the image pane." Display white:imagePane frame. getIcon "Answer the currently displayed icon taken from the image pane." ^((Form fromDisplay:imagePen clipRect) offset:0 @ 0; yourself). "-----------------------------" "-- Editing support methods --" "-----------------------------" setX:x Y:y "Reverse a cell in both the Editor and Icon Image panes." editorPen place:(self editorCellX:x y:y); copyBits. imagePen place:(self imageCellX:x y:y); copyBits. editorCellX:x y:y "Answer a Point with the location of the cell at position x,y within the editor image." ^editorPen clipRect origin + ((x * scale @ (y * scale)) + cellOffset). imageCellX:x y:y "Answer a Point with the location of the cell at position x,y within the image form." ^imagePen clipRect origin + (x @ y). "-------------------------------" "-- Reframing support methods --" "-------------------------------" reframeEditor:aFrame "Reframe the editor pane to aFrame." | w h yScale xScale penRect size | w := aFrame width. h := aFrame height. xScale := w // (IconSize x + 2). yScale := h // (IconSize y + 2). scale := xScale min:yScale. scale > 4 ifTrue:[ cellSize := (scale - 3) @ (scale - 3). cellOffset := 2 @ 2] ifFalse:[ cellSize := (scale - 2) @ (scale - 2). cellOffset := 1 @ 1]. size := IconSize * scale. gridForm := Form width:size x height:size y. (Pen new:gridForm) grid:scale. penRect := editorPane frame center - (size // 2) extent:size. editorPen clipRect:penRect; defaultNib:cellSize; combinationRule:Form reverse; mask:nil. reframeImage:aFrame "Reframe the window's icon image pane to aFrame." imagePen clipRect:(aFrame center - (IconSize // 2) extent:IconSize); defaultNib:1 @ 1; combinationRule:Form reverse; mask:nil. "-----------------------------" "-- General support methods --" "-----------------------------" resizeIt:aSize "Change the size of the icon to be aSize." aSize = IconSize ifTrue:[^self]. (selectedIcon notNil and:[selectedIcon extent ~= aSize]) ifTrue:[ selectedIcon := nil. iconName := String new. self eraseEditor; eraseImage. listPane restoreSelected:iconName]. IconSize := aSize. topPane reframe:topPane frame. Scheduler resume. saved "If the image has not changed answer true; otherwise ask user if changes are to be lost." saved ifTrue:[^true]. (Menu message:'Image has changed -- discard changes?') isNil ifTrue:[^false]. saved := true. ^saved. saveIcon "Save a modified icon image." |name| (name := Prompter prompt: 'Enter name of new icon' default: iconName) isNil ifTrue:[^self]. (iconLibrary includesKey:name) ifTrue:[ (Menu message:name, ' exists -- overwrite it?') isNil ifTrue:[^self]. iconLibrary removeKey:name]. selectedIcon := self getIcon. iconName := name. iconLibrary at:name put:selectedIcon. saved := true. "----------------------------" "-- Initialization methods --" "----------------------------" initLibrary "Get the icon library to use." (Smalltalk includesKey:#IconLibrary) ifTrue:[ iconLibrary := Smalltalk at:#IconLibrary] ifFalse:[ iconLibrary := Dictionary new. Smalltalk at:#IconLibrary put:iconLibrary]. newIcon:anIcon "Set up the editor and display a new icon." selectedIcon := anIcon deepCopy. self displayIcon. [Listing -- Modified open Method] openIt "Open an IconEditor window." | frame listWid listHgt | iconLibrary isNil ifTrue:[self initLibrary]. iconName := String new. saved := true. editorPen := Pen new. imagePen := Pen new. frame := (Display boundingBox extent // 6) extent:(Display boundingBox extent * 2 // 3). listWid := SysFont width * 10. listHgt := SysFont height * 10. topPane := TopPane new label:self label; model:self; menu:#windowMenu; minimumSize:frame extent; yourself. topPane addSubpane: (listPane := ListPane new model:self; name:#iconList; change:#iconSelection:; returnIndex:false; menu:#listMenu; framingBlock:[:aFrame| aFrame origin extent:listWid @ listHgt]; yourself). topPane addSubpane: (imagePane := GraphPane new model:self; name:#initImage:; menu:#noMenu; framingBlock:[:aFrame| aFrame origin + (0 @ listHgt) extent:(listWid @ (aFrame height - listHgt))]; yourself). topPane addSubpane: (editorPane := GraphPane new model:self; name:#initEditor:; menu:#editorMenu; change:#editIcon:; framingBlock:[:aFrame| aFrame origin + (listWid @ 0) extent:((aFrame width - listWid) @ aFrame height)]; yourself). topPane reframe:frame. topPane dispatcher openWindow scheduleWindow.