0% found this document useful (0 votes)
3 views593 pages

App Dev Guide

The VisualWorks Application Developer's Guide provides comprehensive information on using VisualWorks software, including its environment, object orientation, syntax, classes, and instances. It covers essential topics such as managing Smalltalk code, application frameworks, and provides guidance on technical support and additional resources. The document is intended for developers and is subject to copyright and licensing restrictions.

Uploaded by

Paul
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views593 pages

App Dev Guide

The VisualWorks Application Developer's Guide provides comprehensive information on using VisualWorks software, including its environment, object orientation, syntax, classes, and instances. It covers essential topics such as managing Smalltalk code, application frameworks, and provides guidance on technical support and additional resources. The document is intended for developers and is subject to copyright and licensing restrictions.

Uploaded by

Paul
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

VisualWorks®

Application Developer's Guide

P46-0101-10
Copyright © 1993–2004 by Cincom Systems, Inc.
All rights reserved.
This product contains copyrighted third-party software.

Part Number: P46-0101-10


Software Release 7.3
This document is subject to change without notice.
RESTRICTED RIGHTS LEGEND:
Use, duplication, or disclosure by the Government is subject to restrictions as
set forth in subparagraph (c)(1)(ii) of the Rights in Technical Data and Computer
Software clause at DFARS 252.227-7013.

Trademark acknowledgments:
CINCOM, CINCOM SYSTEMS, and the Cincom logo are registered trademarks of
Cincom Systems, Inc. ParcPlace and VisualWorks are trademarks of Cincom Systems,
Inc., its subsidiaries, or successors and are registered in the United States and other
countries. ObjectLens, ObjectSupport, ParcPlace Smalltalk, Database Connect, DLL & C
Connect, and COM Connect are trademarks of Cincom Systems, Inc., its subsidiaries, or
successors. ENVY is a registered trademark of Object Technology International, Inc. All
other products or services mentioned herein are trademarks of their respective
companies. Specifications subject to change without notice.

The following copyright notices apply to software that accompanies this


documentation:
VisualWorks is furnished under a license and may not be used, copied, disclosed, and/or
distributed except in accordance with the terms of said license. No class names,
hierarchies, or protocols may be copied for implementation in other systems.
This manual set and online system documentation copyright © 1993–2004 by Cincom
Systems, Inc. All rights reserved. No part of it may be copied, photocopied, reproduced,
translated, or reduced to any electronic medium or machine-readable form without prior
written consent from Cincom.

Cincom Systems, Inc.


55 Merchant Street
Cincinnati, Ohio 45246

Phone: (513) 612-2300


Fax: (513) 612-2000
World Wide Web: [Link]
Contents

About This Book 26


Audience ................................................................................................................... 26
Conventions .............................................................................................................. 26
Typographic Conventions ............................................................................. 26
Special Symbols ........................................................................................... 27
Mouse Buttons and Menus .......................................................................... 27
Getting Help .............................................................................................................. 28
Commercial Licensees ................................................................................. 28
Before Contacting Technical Support .............................................. 28
Contacting Technical Support ......................................................... 29
Non-Commercial Licensees ......................................................................... 29
Additional Sources of Information ............................................................................. 30
Smalltalk Tutorial ....................................................................................................... 30
Online Help .................................................................................................. 30
VisualWorks FAQ ......................................................................................... 30
News Groups ............................................................................................... 31
VisualWorks Wiki ......................................................................................... 31
Commercial Publications .............................................................................. 31
Examples ..................................................................................................... 31

Chapter 1 The VisualWorks Environment 32


Running VisualWorks ................................................................................................ 32
Starting VisualWorks .................................................................................... 33
VisualWorks Command Line Options ........................................................... 33
Image Level Switches ..................................................................... 34
Capturing Command Line Options ............................................................... 34
Running Multiple Versions Under Windows ................................................. 35
Saving the Image ...................................................................................................... 36
Restoring the Original Image ....................................................................... 36
Sources and Changes .................................................................................. 36
Exiting VisualWorks .................................................................................................. 37
Closing on Windows Shutdown .................................................................... 37
Emergency Exit ............................................................................................ 38

Application Developer’s Guide 3


Contents

The VisualWorks Launcher ....................................................................................... 38


Workspace ................................................................................................................ 39
Evaluating Code in a Workspace ................................................................. 40
Workspace Variables ................................................................................... 40
Name Spaces in Workspaces ...................................................................... 41
Saving Workspace Contents ....................................................................... 41
Performing Operations .............................................................................................. 42
Add-in Components .................................................................................................. 43
Parcel Manager ........................................................................................... 44
Viewing the Parcel Path ............................................................................... 45
System Browser ....................................................................................................... 45
Browser Navigator ....................................................................................... 48
Package View ................................................................................. 48
Parcel View ..................................................................................... 48
Hierarchy View ................................................................................ 48
Class / Name Space View .............................................................. 48
Instance, Class, and Variable Views ............................................... 49
Icons in the Navigator ..................................................................... 49
Working with the Browser ............................................................................ 50
Editing Source Code ....................................................................... 50
Missing Source Code ...................................................................... 50
Searching ....................................................................................... 51
Drag and Drop ................................................................................ 51
Controlling Visibility of Methods ...................................................... 51
Using Multiple Views ....................................................................... 51
Source Code Formatting ................................................................. 52
File Browser .............................................................................................................. 53
Inspectors ................................................................................................................. 54
Inspecting an Object .................................................................................... 54
Inspecting Collections .................................................................................. 55
Modifying Objects ........................................................................................ 56
Evaluating Expressions ............................................................................... 56
Browsing and Editing Behavior .................................................................... 57
UI Painter .................................................................................................................. 58
Configuring the VisualWorks Environment ............................................................... 59
VisualWorks Home ...................................................................................... 59
Settings ........................................................................................................ 59

Chapter 2 Object Orientation 61


Procedures vs. Objects ............................................................................................. 61
Objects and Methods ................................................................................................ 62
Composite Objects ................................................................................................... 64

4 VisualWorks
Contents

Variables and Methods .............................................................................................. 65


Method Names .......................................................................................................... 66
Method Categories .................................................................................................... 66
Classes and Instances .............................................................................................. 67
Class Variables ............................................................................................ 67
Class Methods vs. Instance Methods ........................................................... 67
Class Inheritance ...................................................................................................... 69
Looking up a Method .................................................................................... 69
Overriding an Inherited Method .................................................................... 70
Abstract Classes .......................................................................................... 71
Choosing a Superclass ................................................................................ 72

Chapter 3 Syntax 74
Literals ....................................................................................................................... 74
Numbers ....................................................................................................... 74
Integers ........................................................................................... 74
Floating Point Numbers ................................................................... 75
Fixed-Point Numbers ....................................................................... 75
Nondecimal Numbers ...................................................................... 75
Numbers in Scientific Notation ........................................................ 75
Characters .................................................................................................... 76
Strings .......................................................................................................... 76
Symbols ....................................................................................................... 76
Byte Arrays ................................................................................................... 76
Arrays ........................................................................................................... 76
Booleans ...................................................................................................... 77
nil .................................................................................................................. 77
Variables ................................................................................................................... 78
Variable Types .............................................................................................. 78
Variable Names and Conventions ................................................................ 78
Private Variables .......................................................................................... 79
Temporary Variables ....................................................................... 79
Argument Variables ......................................................................... 80
Instance Variables ........................................................................... 81
Class Instance Variables ................................................................. 83
Shared Variables .......................................................................................... 83
Class Variables ................................................................................ 84
Pool Variables ................................................................................. 85
As Global Variables ......................................................................... 87
Class and Name Spaces Names .................................................... 88
Constant and Variable Bindings ...................................................... 88
Public and Private Shared Variables ............................................... 89

Application Developer’s Guide 5


Contents

Defining a Binding as Private or Public ........................................... 89


Initializing Shared Variables ............................................................ 90
Assigning a Value to a Variable ................................................................... 90
Special Variables ......................................................................................... 91
Undeclared Variables ................................................................................... 92
Message Expressions .............................................................................................. 93
Unary Messages .......................................................................................... 93
Binary Messages ......................................................................................... 93
Keyword Messages ...................................................................................... 95
Pragmas ...................................................................................................... 95
Messages in Sequence ............................................................................... 96
Cascading Messages .................................................................................. 96
Parsing Order for Messages ........................................................................ 96
Block Expressions .................................................................................................... 98
Formatting Conventions ............................................................................................ 99

Chapter 4 Classes and Instances 100


Defining a Class ..................................................................................................... 100
Creating a Class using the New Class Dialog ........................................... 100
Creating a Class from a Template ............................................................. 102
Editing a Class Definition ........................................................................... 104
Class Types ............................................................................................... 104
Locating a Class by Name ...................................................................................... 105
Working with Instances ........................................................................................... 106
Creating an Instance ................................................................................. 106
Destroying an Instance .............................................................................. 106
Garbage Collecting ....................................................................... 106
Finalization .................................................................................... 107
Lingering Instances ...................................................................... 107
Immutable objects ...................................................................................... 107
Methods .................................................................................................................. 109
Creating a Method ..................................................................................... 109
Fixing Common Errors at Compile Time .................................................... 110
Undeclared temporary variables ................................................... 110
Undeclared class and instance variables ...................................... 110
Missing period .............................................................................. 110
Missing delimiters ......................................................................... 110
Returning from a Method ........................................................................... 110
Returning From an Enclosed Block .............................................. 111
Returning the Result of a Message .............................................. 111
Returning a Conditional Value ...................................................... 111

6 VisualWorks
Contents

Chapter 5 Control Structures 112


Branching ................................................................................................................ 112
Boolean Values .......................................................................................... 112
Conditional Tests ........................................................................................ 113
Compound Conditions ................................................................................ 114
Looping ................................................................................................................... 115
Conditional Looping ................................................................................... 115
whileTrue: and whileFalse: ............................................................ 115
repeat ............................................................................................ 116
Number Iteration ........................................................................................ 116
timesRepeat: ................................................................................. 116
to:by:do: ........................................................................................ 116
to:do: ............................................................................................. 117
Collection Iteration ..................................................................................... 117
do: ................................................................................................. 117
select: ............................................................................................ 117
reject: ............................................................................................ 117
detect: ........................................................................................... 118
collect: ........................................................................................... 118
inject:into: ...................................................................................... 118

Chapter 6 Name Spaces 120


Multiple Name Spaces ............................................................................................ 121
Getting Started ........................................................................................................ 121
Name Spaces and Their Contents .......................................................................... 122
Name Space Contents ............................................................................... 122
The Name Space Hierarchy ....................................................................... 123
[Link] ............................................................... 124
Working with Name Spaces .................................................................................... 125
Browsing Name Spaces ............................................................................. 125
Creating Name Spaces .............................................................................. 126
Naming a Name Space .............................................................................. 127
When to Create a New Name Space ......................................................... 128
Rearranging Name Spaces ........................................................................ 128
Classes as Name Spaces .......................................................................... 129
Referencing Objects in Name Spaces .................................................................... 129
Dotted Names and Name Space Paths ..................................................... 129
Binding References .................................................................................... 131
Binding Reference Resolution ....................................................... 133
When to Use BindingReference or LiteralBindingReference ......... 133
Importing Bindings ..................................................................................... 133
Importing Classes and Name Spaces ........................................... 135

Application Developer’s Guide 7


Contents

Importing Class Variables ............................................................. 136


Importing Pool Variables ............................................................... 136
“Circular” System Imports ............................................................. 136
Binding Rules and Errors ........................................................................... 138

Chapter 7 Managing Smalltalk Code 139


Source Code Files .................................................................................................. 140
File-Out Format Files .............................................................................................. 141
Filing Out Code .......................................................................................... 141
Filing In Code ............................................................................................ 142
Packages and Bundles ........................................................................................... 142
Creating packages ..................................................................................... 143
Assigning Definitions to Packages ............................................................. 143
Specifying prerequisites ............................................................................ 144
Suppress warnings .................................................................................... 145
Specify prerequisite version ....................................................................... 146
Managing bundle content .......................................................................... 146
Creating and arranging bundles ................................................... 146
Editing a bundle specification ....................................................... 147
Specify prerequisites .................................................................... 148
Suppress warnings ....................................................................... 148
Specify prerequisite version .......................................................... 149
Bundle load and unload actions .................................................... 149
Parcels .................................................................................................................... 149
Loading and Unloading Parcels ................................................................. 150
Using the Parcel Manager ............................................................ 150
Using the System Browser ........................................................... 151
Loading Parcels Programmatically ............................................... 152
Parcel Search Path .................................................................................... 153
Managing Parcels ...................................................................................... 154
Parcel Condition Indicators ........................................................... 154
Creating a New Parcel .................................................................. 155
Adding and Removing Definitions ................................................. 155
Saving a Parcel ............................................................................. 156
Managing Parcel Contents ........................................................................ 158
Deciding What to Parcel ............................................................... 158
Dangling References .................................................................... 158
Finding Unparcelled Methods ....................................................... 159
Restrictions on Parcel Contents ................................................... 159
Defining Parcel Prerequisites .................................................................... 160
Defining Deployment Prerequisites .............................................. 160
Defining Development Prerequisites ............................................. 161

8 VisualWorks
Contents

Partial Loading ........................................................................................... 161


Limitations ..................................................................................... 162
Controlling Parcel Load and Unload Behavior ............................................ 162
Saving ........................................................................................... 162
Loading ......................................................................................... 163
Unloading ...................................................................................... 164
Limitations ..................................................................................... 164
Guidelines for Clean Loading and Unloading ................................ 164
Definition Overrides .................................................................................... 166
Shape Change Tolerance ........................................................................... 167
Limitations ..................................................................................... 167
Filing Parcel Contents In and Out .............................................................. 167
Working with Change Sets ...................................................................................... 168
Selecting a Current Change Set ................................................................ 168
Creating a New Change Set ....................................................................... 169
Exploring Changes ..................................................................................... 169
Browse .......................................................................................... 170
Edit ................................................................................................ 170
Inspect ........................................................................................... 171
Updating the Changes Display ...................................................... 171
Saving Changes ......................................................................................... 171
Creating Install and Remove Scripts .......................................................... 172
Change Initialization Ordering .................................................................... 172
Clearing a Change Set ............................................................................... 172
Using the Change List ............................................................................................. 172
The Change List Tool ................................................................................. 173
Browsing a Change List ............................................................................. 174
Reordering Items in the Change List .......................................................... 174
Removing Items from the Change List ....................................................... 175
Resolving Conflicts with the System .......................................................... 175
Using the Conflicts Filter ............................................................... 175
Managing Conflicts ........................................................................ 176
Change/Change Back Changes ................................................................. 177
Reverting to a Prior Version ....................................................................... 178
Recovering from a Crash ........................................................................... 178
Recovering Changes to a Clean Image ..................................................... 179
Condensing the Change List File ............................................................... 180
Changing the Change List File Name ........................................................ 180
Filing Out a Set of Changes ....................................................................... 180
Managing Overrides ................................................................................................ 181
Opening the Override Tool ......................................................................... 181
Selecting Overrides .................................................................................... 183
Restoring an Overridden Definition ............................................................ 183

Application Developer’s Guide 9


Contents

Removing an Overridden Definition ........................................................... 183


Publishing Parcels and Packages with Overrides ...................................... 184

Chapter 8 Application Framework 185


Separating the Domain and the User Interface ...................................................... 185
Application Model Acts as Mediator ........................................................... 186
Value Model Links Widget to Attribute ....................................................... 187
Builder Assembles User Interface .............................................................. 188
Dependencies Between Objects ............................................................................ 189
The Update/Change System ..................................................................... 189
Notifications From Value Model to Application Model ................................ 190
Notifications From Any Object to Any Object ............................................. 191
DependencyTransformer .............................................................. 191
Direct Dependency ....................................................................... 192
Removing Dependents ................................................................. 193
Circular Dependencies ................................................................. 193
Application Startup and Shutdown ......................................................................... 194
Selecting an Interface ................................................................................ 194
Prebuild Intervention .................................................................................. 195
Postbuild Intervention ................................................................................ 195
Postopen Intervention ................................................................................ 195
Application Cleanup ................................................................................... 195
User Settings Framework ....................................................................................... 196
Settings ...................................................................................................... 197
Browsing the Definition for a Setting .......................................................... 198
Defining a Setting ...................................................................................... 198
Additional Setting Parameters ................................................................... 199
Controlling the Vertical Position of a Setting .............................................. 199
Settings Pages ........................................................................................... 200
Defining a Page of Settings ....................................................................... 201
Setting Types ............................................................................................. 202
Creating a Setting Model ........................................................................... 204
Backward Compatibility with VisualWorks UISettings ................................ 204
Using Drop-Down List and Radio Button Settings ..................................... 205
Defining a Settings Domain ....................................................................... 206
Saving and Loading Settings ..................................................................... 207
Responding to System Events ................................................................................ 208
Defining System Event Actions .................................................................. 208
Command Line Processing in a Subsystem .............................................. 210
Activating a Subsystem ............................................................................. 212
Dependency Ordering of Subsystems ....................................................... 212

10 VisualWorks
Contents

Chapter 9 Trigger-Event System 214


Triggering Events .................................................................................................... 215
Event Triggering Messages ........................................................................ 215
Registering an Event Handler ................................................................................. 216
Handling an Event with Arguments ............................................................ 217
Handler Registration Messages ................................................................. 217
Removing Event Handlers ....................................................................................... 219
RemoveAction messages ........................................................................... 220
Defining Event Sets ................................................................................................. 220
Specifying event strictness ......................................................................... 220
Specifying events to trigger ........................................................................ 221
Event classes ............................................................................................. 221
How Handlers are Registered ................................................................................. 221
Trigger Event System Support Methods .................................................................. 222
Trigger Event Support Methods Available to All Objects ............................ 222
Trigger Event Support Methods In ApplicationModel ................................. 223

Chapter 10 Debugging Techniques 225


Software Probes ...................................................................................................... 226
Breakpoint .................................................................................................. 226
Watchpoint ................................................................................................. 227
Setting Probes ......................................................................................................... 228
Setting a breakpoint ................................................................................... 228
Setting a variable watchpoint ..................................................................... 228
Setting an expression watchpoint ............................................................... 229
Removing probes ....................................................................................... 231
Making a probe conditional ........................................................................ 231
Select a watch window ............................................................................... 234
Modifying a probe ....................................................................................... 234
Probe location ............................................................................................ 235
Recompiling a Probed Method ................................................................... 235
Limitations .................................................................................................. 236
Probe highlights ............................................................................ 236
Inserting probes at returns ............................................................ 236
Class Probes ........................................................................................................... 237
Adding class probes ................................................................................... 237
On Instance Variable Access... ..................................................... 237
On Message Receipt... .................................................................. 239
Remove class probes ................................................................................. 241
Browse probed methods ............................................................................ 241
Debugger ................................................................................................................ 242
Walkback Notifier ....................................................................................... 242

Application Developer’s Guide 11


Contents

Debugger Window ..................................................................................... 242


Reading the Execution Stack ..................................................................... 244
Editing a Method Definition ........................................................................ 246
Inspecting and Changing Variables ........................................................... 246
Inspecting the Stack .................................................................................. 247
Tracing the Flow of Messages ................................................................... 247
Stack menu ................................................................................... 248
Method menu ................................................................................ 248
Execute menu ............................................................................... 249
Correct menu ................................................................................ 250
Inserting Probes in the Debugger ........................................................................... 250
Temporary Probes ..................................................................................... 250
Probe context management ....................................................................... 251
Debugging Tips ....................................................................................................... 252
Inserting probes into blocks ....................................................................... 252
Iteration debugging .................................................................................... 252
Interrupting a Program .............................................................................. 253
Global Probe Management ..................................................................................... 254
Probe library .............................................................................................. 254
Expression libraries ................................................................................... 255
Storing CompiledMethods Externally ........................................................ 255
Debugging Within the Virtual Machine ................................................................... 256

Chapter 11 Exception and Error Handling 257


ANSI Exception Handling ....................................................................................... 257
Adapting Signal-based Code ..................................................................... 258
Reinitializing Signal Creators and Initializers ................................ 258
Name Signals ............................................................................... 258
Do Not Depend on Signal noHandlerSignal ................................. 258
Exception Classes .................................................................................................. 259
Handling Exceptions ............................................................................................... 260
Exception Sets ........................................................................................... 262
Signaling Exceptions .............................................................................................. 262
Exception Environment ........................................................................................... 263
Exiting Handlers Explicitly ...................................................................................... 265
Resumable and Nonresumable Exceptions ............................................................ 267
Translating Exceptions ............................................................................................ 269
Unwind Protection .................................................................................................. 269
Using a Signal to Handle an Error .......................................................................... 270
Choosing or Creating a Signal ................................................................... 270
Proceedabiltiy ............................................................................................ 271
Creating an Exception ............................................................................... 271

12 VisualWorks
Contents

Setting Parameters ..................................................................................... 272


Passing Control From the Handler Block ................................................... 272
Using Nested Signals ................................................................................. 273

Chapter 12 Process Control 275


UI Processes ........................................................................................................... 275
Creating a Process .................................................................................................. 275
Scheduling a Process ............................................................................................. 276
Setting the Priority Level ......................................................................................... 277
Synchronizing Processes ........................................................................................ 278
Semaphore ................................................................................................. 278
Sharing Data Between Processes .......................................................................... 280
Using a Delay .......................................................................................................... 280

Chapter 13 Weak Reference and Finalization 281


Weak Arrays ............................................................................................................ 282
Finalization .............................................................................................................. 282
WeakDictionary ....................................................................................................... 285
HandleRegistry .......................................................................................... 285
Finalization Example ............................................................................................... 285
Ephemerons ............................................................................................................ 287

Chapter 14 Application Delivery 288


Choosing a Delivery Strategy .................................................................................. 289
Single Image File ....................................................................................... 289
Parcels ....................................................................................................... 289
Combined Deployment ............................................................................... 289
Packaging for Distribution ........................................................................................ 290
Deploying as a Single File .......................................................................... 290
VisualWorks Installer .................................................................................. 290
Running a Deployed Image ..................................................................................... 290
Loading Parcels At Start Up ....................................................................... 291
Opening a Runtime Application .................................................................. 291
Exiting a Deployed Image .......................................................................... 292
Installing as a Service on Windows ............................................................ 292
Preparing an Image for Deployment ....................................................................... 293
Loading Application Code .......................................................................... 293
Code Developed in the Image ....................................................... 293
Code Saved in File-outs ................................................................ 293
Code Saved in Parcels .................................................................. 294
Code in a Store Database ............................................................. 294

Application Developer’s Guide 13


Contents

Removing Source Files .............................................................................. 294


The Transcript ............................................................................................ 295
Handling Errors .......................................................................................... 295
Registering an Interest in System Events .................................................. 295
Pragma-based Event Dependency ............................................... 296
Message-based Event Dependency ............................................. 296
Shutdown When the Last Window Closes ................................................. 297
Handling Command Line Options .............................................................. 298
Pragma-based Option Processing ................................................ 299
Message-based Option Processing .............................................. 300
Unload Tools Parcels ................................................................................. 301
Removing Undeclared Variables ............................................................... 301
Garbage Collecting Lingering Instances .................................................... 302
Replacing Startup Splashscreen and Sound ............................................. 302
Creating the Deployment Image ............................................................................. 303
Running Runtime Packager ....................................................................... 303
A Short-cut Procedure ............................................................................... 305
Examples ................................................................................................... 305
Building a Stand-alone Image ....................................................... 305
Building an Image Using Parcels .................................................. 306
Runtime Packager Process Details ........................................................................ 308
Saving Runtime Packager Parameters ...................................................... 308
Clean Up Image ......................................................................................... 308
Set Common Options ................................................................................ 309
Details Page ................................................................................. 309
Platforms Page ............................................................................. 311
Exceptions Page ........................................................................... 311
Parcels Page ................................................................................. 312
Parcel operations .......................................................................... 313
Stripping Page .............................................................................. 313
Specify Items to Keep and Delete .............................................................. 315
Pop-up Menus ............................................................................... 317
Scan for Unreferenced Items ..................................................................... 319
Review Kept Items ..................................................................................... 320
Pop-up Menus ............................................................................... 321
Save Loadable Parcels .............................................................................. 321
Test the Application .................................................................................... 322
Set Runtime Memory Parameters ............................................................. 324
Space sizes .................................................................................. 324
Policy Values ................................................................................. 324
Notes ............................................................................................ 325
Strip and Save Image ................................................................................ 325

14 VisualWorks
Contents

Debugging a Deployed Image ................................................................................. 327


Customizing the Emergency Notifier .......................................................... 328
Customizing Detected References .......................................................................... 329
Customizing Image Stripping .................................................................................. 330
Trouble Shooting ..................................................................................................... 330
Workspace or Browser is Opened with the Application .............................. 330
Parcel File not Readable ............................................................................ 330
Application Cannot Find a Parcel Source File ............................................ 331
Application Exits Immediately .................................................................... 331
An Identifier has no Binding ....................................................................... 331

Chapter 15 Creating an Application without a GUI 332


Key Concepts .......................................................................................................... 332
Setting Up a Headless Image ................................................................................. 333
Running an Application in Headless Mode ............................................................. 334
Starting on Unix/Linux ................................................................................ 334
Starting on Windows .................................................................................. 334
When an Image Starts ............................................................................... 335
If an Application Attempts to Access a Display .......................................... 335
Debugging a Suspended Process ........................................................................... 336
Creating a Headful Copy of a Headless Image ....................................................... 336
Tips for Programming a Headless Application ........................................................ 336
Techniques for Starting a Headless Application ......................................... 337
Techniques for Communicating with a Headless Application ..................... 337
Terminating a Headless Application ........................................................... 337
Sending Output to the System Console ..................................................... 337
Preventing Access to the Display ............................................................... 338
Delivering a Headless Application ........................................................................... 339

Chapter 16 Numbers, Dates, and Times 340


Numbers .................................................................................................................. 340
Creating a Number ..................................................................................... 341
Arithmetic Operations ................................................................................. 342
Comparing Numbers .................................................................................. 342
Testing Numbers for Properties .................................................................. 343
Converting Object Type .............................................................................. 344
Mathematical Functions ............................................................................. 344
Factoring ....................................................................................... 344
Trigonometric Functions ................................................................ 344
Logarithmic Functions ................................................................... 345
Numeric Constants ..................................................................................... 345
Zero ............................................................................................... 345

Application Developer’s Guide 15


Contents

Unity ............................................................................................. 346


Pi .................................................................................................. 346
Random Numbers .................................................................................................. 347
Dates ...................................................................................................................... 347
Creating a Date ......................................................................................... 347
Getting Information about a Day ................................................................ 349
Adding and Subtracting with Dates ............................................................ 349
Comparing Dates ....................................................................................... 349
Formatting a Date ...................................................................................... 350
Times ...................................................................................................................... 351
Creating a Time ......................................................................................... 351
Getting the Seconds, Minutes, and Hours ................................................. 352
Adding and Subtracting Times ................................................................... 352
Creating a Time Stamp .............................................................................. 352
Setting the Time Zone ............................................................................... 353

Chapter 17 Collections 355


Choosing the Appropriate Class ............................................................................. 356
Set ............................................................................................................. 356
Bag ............................................................................................................ 357
Array .......................................................................................................... 357
Interval ....................................................................................................... 357
OrderedCollection ...................................................................................... 357
SortedCollection ........................................................................................ 358
LinkedList ................................................................................................... 358
Dictionary ................................................................................................... 358
Creating a Collection .............................................................................................. 359
Adding Elements .................................................................................................... 360
Adding an Element to a Collection ............................................................. 360
Inserting an Element at a Specific Location .............................................. 361
Adding a Collection of Elements ................................................................ 362
Expanding an Array ................................................................................... 362
Removing Elements ............................................................................................... 363
Removing a Subcollection ......................................................................... 363
Removing an Element or Range of Elements by Index ............................. 364
Removing All Elements That Pass a Test .................................................. 364
Removing an Association from a Dictionary .............................................. 365
Removing an Element from an Array ......................................................... 365
Replacing Elements ................................................................................................ 366
Replacing Individual Elements ................................................................... 366
Replacing All Elements .............................................................................. 366
Replacing Specified Elements ................................................................... 367

16 VisualWorks
Contents

Replacing All Occurrences of an Object .................................................... 367


Replacing a Subcollection .......................................................................... 367
Copying Elements ................................................................................................... 368
Copying a Subcollection ............................................................................. 368
Concatenating Two Collections .................................................................. 369
Subtracting One Set from Another ............................................................. 369
Testing Collections .................................................................................................. 369
Equality and Identity ................................................................................... 369
Getting the Number of Elements ................................................................ 370
Getting the Capacity ................................................................................... 370
Testing for Emptiness ................................................................................. 370
Testing for the Presence of an Object ........................................................ 371
Retrieving Elements ................................................................................................ 371
Getting the Element at an Index ................................................................. 371
Finding the Index of an Object ................................................................... 371
Finding a Subcollection by Index ................................................................ 372
Getting the Value at a Key .......................................................................... 372
Retrieving an Object by Relative Position .................................................. 373
Finding Elements That Pass or Fail a Test ................................................. 373
Sorting a Collection ................................................................................................. 374
Converting Collection Types .................................................................................... 375
Looping through the Elements (Iterating) ................................................................ 376
Looping by Index or Key ............................................................................. 376
Collecting the Results of the Processing .................................................... 377
Looping through Two Parallel Collections ................................................... 378

Chapter 18 Working With Graphics and Colors 379


Examples Browser .................................................................................................. 379
The VisualWorks Graphics Environment ................................................................. 379
Coordinate System ..................................................................................... 379
Points ......................................................................................................... 380
Graphical Objects ....................................................................................... 381
Text Objects ................................................................................... 381
Lines, Polygons, and Curves ......................................................... 381
Bitmap Images .............................................................................. 382
Display Surfaces ........................................................................................ 382
Graphics Context ........................................................................... 382
Windows ........................................................................................ 383
Pixmaps ........................................................................................ 383
Masks ............................................................................................ 383
Cursors ....................................................................................................... 384
Colors and Patterns ................................................................................... 384

Application Developer’s Guide 17


Contents

Displaying a Geometric Object ............................................................................... 384


Drawing a Transient Shape ........................................................................ 384
Drawing a Geometric Object ...................................................................... 385
Shifting Graphics ....................................................................................... 386
Integrating Graphics into an Application .................................................... 387
Storing Graphic Attributes .......................................................................... 389
Drawing Objects ..................................................................................................... 390
Lines .......................................................................................................... 391
Polylines and Polygons .............................................................................. 392
Rectangles ................................................................................................. 393
Creating a Rectangle .................................................................... 393
Getting a Rectangle’s Dimensions ................................................ 394
Scaling and Transforming Rectangles .......................................... 395
Arcs, Ellipses, and Circles ........................................................................ 395
Drawing Circles ............................................................................. 396
Drawing Ellipses ........................................................................... 397
Drawing Arcs ................................................................................ 398
Curved Lines ............................................................................................. 399
Line Properties .......................................................................................... 401
Changing the Line Thickness ....................................................... 401
Changing the Line Cap Style ....................................................... 402
Changing the Line Join Style ........................................................ 403
Graphical Images ................................................................................................... 403
Pixels and Bitmaps .................................................................................... 404
Color Depth and Images ............................................................................ 404
Creating a Graphic Image .......................................................................... 405
Using the Image Editor ................................................................. 405
Reading an Image from a File ...................................................... 406
Capturing an Image from the Screen ............................................ 406
Creating a Bitmap Manually .......................................................... 407
Displaying an Image .................................................................................. 407
Creating a Display Surface Bearing an Image .............................. 408
Caching an Image ..................................................................................... 408
Coloring Pixels in an Image ....................................................................... 408
Changing Color by Color Value ..................................................... 409
Changing Color by Numeric Value ................................................ 409
Masking an Image ..................................................................................... 410
Creating a Mask ............................................................................ 410
Masking a Rectangular Area ........................................................ 411
Masking a Nonrectangular Area ................................................... 411
Modifying an Image ................................................................................... 412
Expanding or Shrinking an Image ................................................. 412
Flopping an Image ........................................................................ 413

18 VisualWorks
Contents

Rotating an Image ......................................................................... 414


Overlaying Images ........................................................................ 415
Colors and Patterns ................................................................................................ 416
Pixel Coverage ........................................................................................... 417
Creating a Color ......................................................................................... 417
Create by Color Name ................................................................... 417
Create by Red, Green, and Blue Values ....................................... 418
Coloring a Graphical Object .......................................................... 420
Creating a Pattern ...................................................................................... 421
Applying a Pattern ......................................................................... 421
Adjusting a Pattern’s Tile Phase .................................................... 422
Image Color Palettes ............................................................................................... 423
Coverage Palettes ...................................................................................... 423
Color Palettes ............................................................................................. 423
Creating a Color Palette ................................................................ 423
Eight-bit Color Palettes .................................................................. 424
Image Display Performance .......................................................... 424
Device Color Map ....................................................................................... 424
Applying a Palette to an Image .................................................................. 425
Converting an Image to Use the Default Palette ........................................ 426
Color Rendering Policies ......................................................................................... 426
NearestPaint ............................................................................................... 427
OrderedDither ............................................................................................ 427
ErrorDiffusion ............................................................................................. 427
Applying a Renderer to an Image .............................................................. 428
Converting an Image to a Specific Palette .................................... 429
Setting the Rendering Policy for Nonimage Graphics ................... 429

Chapter 19 Working with Text 431


Characters ............................................................................................................... 431
Creating Characters ................................................................................... 431
Testing Character Types ............................................................................. 432
Comparing Characters ............................................................................... 433
Strings ..................................................................................................................... 433
Creating a String ........................................................................................ 433
Changing the Case in a String ................................................................... 434
Getting a String’s Length and Width ........................................................... 435
Combining Strings ...................................................................................... 436
Comparing Strings ..................................................................................... 437
Testing for Equality and Identity .................................................... 437
Comparing by Sorting Order ......................................................... 438
Rating the Similarity of Two Strings ............................................... 438

Application Developer’s Guide 19


Contents

Searching .................................................................................................. 438


Get the Index of a Character in a String ....................................... 439
Ignoring Case in a Search ............................................................ 439
Substring Operations ................................................................................. 440
Copying a Substring ..................................................................... 440
Copying a Prefix ........................................................................... 441
Removing or Replacing a Substring ............................................. 441
Replacing a Substring .................................................................. 441
Replacing All Occurrences of a Substring .................................... 442
String Substitution Parameters .................................................................. 442
Abbreviating a String ................................................................................. 444
Contracting a String ...................................................................... 444
Removing Vowels ......................................................................... 444
Inserting Line-End Characters ................................................................... 444
Formatted Text and Fonts ....................................................................................... 445
Creating a Formattable Text Object ........................................................... 445
Displaying a Text Object ............................................................................ 446
Controlling Line Length .............................................................................. 446
Setting Line Length ....................................................................... 447
Controlling Word Wrap ................................................................. 447
Controlling Line Format ............................................................................. 448
Setting Alignment .......................................................................... 448
Setting Indents .............................................................................. 448
Setting Tab Stops .......................................................................... 449
Printing a Text Object ................................................................................. 450
Text String Operations ............................................................................... 450
Counting Characters ..................................................................... 451
Search for Text .............................................................................. 451
Replacing Text .............................................................................. 451
Comparing Text Objects ............................................................... 452
Copying a Range of Text ............................................................... 452
Character Formatting .............................................................................................. 453
Applying Character Variations ................................................................... 454
Applying Boldfacing and Other Emphases ................................... 454
Applying Color to Text ................................................................... 454
Changing Font Size ...................................................................... 455
Defining Text and Character Styles ............................................................ 455
Using the Platform Default Font .................................................... 456
Defining a Custom Text Style ........................................................ 457
Set text typeface family ................................................................. 458
Setting Font Family or Name ........................................................ 459
Defining Custom Sizes ................................................................. 461
Setting Font Pixel Size .................................................................. 461

20 VisualWorks
Contents

Creating a Scaled Text Style ......................................................... 462


Defining an Emphasis for a Custom Size ...................................... 463
Adjusting the Line Spacing and Baseline ...................................... 464
Adding a Custom Font to the Fonts Menu .................................................. 465
Changing the Default Font ......................................................................... 465
Setting the Preferred Font Family ............................................................... 466
Setting the Preferred Font Pixel Size ......................................................... 466

Chapter 20 Files 468


File Names .............................................................................................................. 468
Creating a Filename ................................................................................... 469
Constructing a Portable Filename .............................................................. 469
Creating a File or Directory ..................................................................................... 470
Creating an Empty File ............................................................................... 470
Creating a New Disk Directory ................................................................... 471
Getting File Information ........................................................................................... 471
Testing for Existence .................................................................................. 471
Getting the Size of a File ............................................................................ 471
Getting and Setting the Working Directory ................................................. 472
Getting the Parent Directory ....................................................................... 472
Getting the Parts of a Pathname ................................................................ 473
Distinguishing a File from a Directory ........................................................ 473
Getting the Access and Modification Times ............................................... 473
Getting File or Directory Contents ........................................................................... 474
Getting the Contents of a File .................................................................... 474
Getting the Contents of a Directory ............................................................ 474
System Variables .................................................................................................... 475
Storing Text in a File ................................................................................................ 475
Writing a Stream to a File ........................................................................... 476
Appending Text to a File ............................................................................. 476
Deleting a File or Directory ...................................................................................... 476
Copying or Moving a File ......................................................................................... 477
Copying a File ............................................................................................ 477
Moving a File .............................................................................................. 477
Renaming a File ......................................................................................... 478
Comparing Two Files or Directories ........................................................................ 478
Comparing Two Filenames or Two Files ..................................................... 479
Comparing Two Filenames or Two Directories ........................................... 479
Printing a File .......................................................................................................... 480
Print a Text File ........................................................................................... 480
Printing a File Directly ................................................................................ 480

Application Developer’s Guide 21


Contents

Writing Fields to a Data File ....................................................................... 480


Reading Fields from a Data File ................................................................ 481
Setting File Permissions ......................................................................................... 482

Chapter 21 Binary Object Files (BOSS) 484


Storing Objects in a BOSS File .............................................................................. 484
Storing a Collection of Objects .................................................................. 485
Appending an Object to a File ................................................................... 485
Getting Objects from a BOSS File .......................................................................... 486
Retrieving All Objects ................................................................................ 486
Searching Sequentially for an Object ........................................................ 487
Getting an Object at a Specific Position ..................................................... 488
Storing and Getting a Class .................................................................................... 489
Storing a Collection of Classes .................................................................. 490
Loading a Collection of Classes ................................................................ 490
Converting Data After Changing a Class ................................................................ 491
Customizing the Storage Representation ............................................................... 492

Chapter 22 Refactoring 494


Overview ................................................................................................................. 495
Refactoring for Abstraction ..................................................................................... 497
Creating an Abstract Class ........................................................................ 497
Moving Instance Variables to a Superclass .................................. 498
Consolidating Common Code ....................................................... 499
Inlining Methods ........................................................................................ 500
Individual Refactorings ........................................................................................... 502
Refactoring Classes ................................................................................... 502
Creating a Subclass ...................................................................... 502
Renaming a Class and Its References ......................................... 502
Safely Removing a Class .............................................................. 502
Changing a Class to a Sibling ....................................................... 502
Adding a Variable .......................................................................... 503
Renaming a Variable and its References ...................................... 503
Removing a Variable ..................................................................... 503
Moving a Variable from or to a Subclass ...................................... 503
Creating Variable Accessors ......................................................... 504
Abstracting a Variable ................................................................... 504
Making a Variable Concrete .......................................................... 504
Refactoring Methods .................................................................................. 504
Moving a Definition to Another Component .................................. 504
Renaming a Method and its References ....................................... 504
Safely Removing a Method ........................................................... 505

22 VisualWorks
Contents

Adding a Parameter to a Method ................................................... 505


Inlining all Sends to Self ................................................................ 505
Moving a Method to or from a Superclass ..................................... 505
Refactoring Portions of a Method ............................................................... 505
Extracting a Method ...................................................................... 505
Inlining a Temporary Variable ........................................................ 506
Converting a Temporary into an Instance Variable ........................ 506
Removing a Parameter .................................................................. 506
Inlining a Parameter ...................................................................... 506
Renaming a Temporary ................................................................. 506
Moving a Temporary to an Inner Scope ........................................ 506
Extracting to a Temporary ............................................................. 506
Inlining a Message ........................................................................ 507

Chapter 23 Coding Tools 508


Code Critic .............................................................................................................. 508
Using the Code Critic ................................................................................. 509
Filtering Results ......................................................................................... 509
Working with Rule Filters ............................................................... 510
Applying Transformation Rules ................................................................... 510
Limitations of the Code Critic ..................................................................... 510
Code Rewriting Editor ............................................................................................. 511
Transformation Rules ................................................................................. 512
Using Meta-variables and Modifiers .............................................. 512
Rewriting Methods ..................................................................................... 514
Replacing Whole Methods ......................................................................... 514
Unit Testing ............................................................................................................. 515
Unit Testing in VisualWorks ........................................................................ 516
Running Test Cases ...................................................................... 516
Creating a Test Case ..................................................................... 517
Writing Test Methods ..................................................................... 517
Using should: and shouldnt: ....................................................... 518
Defining Test Resources ............................................................... 518
Guidelines for Working with Test Cases ..................................................... 519
Criteria for Testing ......................................................................... 520

Chapter 24 XML Framework 521


Working with XML documents ................................................................................. 521
Parsing an XML document ......................................................................... 522
Validating Against a Schema ..................................................................... 523
Selecting a XMLParser driver ..................................................................... 523
Accessing XML Document elements ...................................................................... 524

Application Developer’s Guide 23


Contents

Get Document root element ....................................................................... 525


Selecting elements .................................................................................... 526
Selecting attributes .................................................................................... 527
Building a document ............................................................................................... 528
Create a basic document ........................................................................... 528
Node ordering ............................................................................................ 528
Add element nodes .................................................................................... 529
Add a root element ....................................................................... 529
Add nested elements .................................................................... 529
Adding element attributes ............................................................. 530
Adding text .................................................................................... 531
Add processing instructions ....................................................................... 531
Writing the XML Document ........................................................................ 532
Using XML Namespaces ........................................................................................ 533
Declare namespaces ................................................................................. 533
Applying a namespace to an element ........................................................ 534
Assigning a namespace to an attribute ...................................................... 536
Building a SAX driver .............................................................................................. 537
Handling SAX events ................................................................................. 537
Configuring SAX features and properties .................................................. 539
Document Fragments ................................................................................ 541
Building a fragment .................................................................................... 542
Parsing a fragment .................................................................................... 542
XSL stylesheet processing ..................................................................................... 543
Loading XSL support ................................................................................. 543
Applying a stylesheet to a document ......................................................... 543
Using XPath ............................................................................................................ 545
Creating a path expression ........................................................................ 545
Applying an XPath expression ................................................................... 547
Selecting nodes with an XPath .................................................................. 547
XML Error Handling ................................................................................................ 548

Appendix A VisualWorks Smalltalk Syntax Description 549


Lexical Primitives .................................................................................................... 549
Character Classes ..................................................................................... 550
Numbers .................................................................................................... 550
Other Lexical Constructs ........................................................................... 550
Atomic Terms .......................................................................................................... 551
Expressions and Statements .................................................................................. 552
Methods .................................................................................................................. 553

24 VisualWorks
Contents

Appendix B Special Characters 554


Composed Characters ............................................................................................ 555
Diacritical Marks ...................................................................................................... 558

Appendix C Virtual Machines 560


VisualWorks Virtual Machines ................................................................................. 560
Production Engines .................................................................................... 560
Debug Engines ........................................................................................... 561
Assert Engines ........................................................................................... 561
Headless and Headful Engines .................................................................. 561
Linkable Object Engines ............................................................................ 561
Console Object Engines ............................................................................. 562
Virtual Machine Command Line Options ................................................................ 562
All platforms ............................................................................................... 562
Windows platforms ..................................................................................... 563
Unix/Linux platforms ................................................................................... 563
System Colors on X11 ............................................................................................ 564

Appendix D Code Critic Rules 565


Bugs ........................................................................................................................ 566
Possible Bugs .......................................................................................................... 567
Unnecessary Code ................................................................................................. 569
Intention Revealing .................................................................................................. 571
Miscellaneous ......................................................................................................... 574
Code Transformations ............................................................................................. 577

Index 579

Method Index 589

Application Developer’s Guide 25


About This Book

This document is designed to help both new and experienced developers


create application programs effectively using the VisualWorks®
application frameworks, tools, and facilities.

Audience
This guide assumes that you have at least a beginning familiarity with
object-oriented programming. The description of VisualWorks begins at
an elementary level, with an overview of the system tools and facilities,
and a description of Smalltalk syntax, but does not attempt to be a
tutorial.
For additional help, a large number of books and tutorials are available
from commercial book sellers and on the world-wide web. In addition,
Cincom and some of its partners provide VisualWorks training classes.

Conventions
We have followed a variety of conventions, which are standard in the
VisualWorks documentation.

Typographic Conventions
The following fonts are used to indicate special terms:

Example Description
template Indicates new terms where they are defined,
emphasized words, book titles, and words as words.
[Link] Indicates filenames, pathnames, commands, and
other constructs to be entered outside VisualWorks
(for example, at a command line).
[Link] Indicates a variable element for which you must
substitute a value.

26 VisualWorks
Conventions

Example Description
windowSpec Indicates Smalltalk constructs; it also indicates any
other information that you enter through the
VisualWorks graphical user interface.
Edit menu Indicates VisualWorks user-interface labels for menu
names, dialog-box fields, and buttons; it also
indicates emphasis in Smalltalk code samples.

Special Symbols
This book uses the following symbols to designate certain items or
relationships:

Examples Description
File ­ New Indicates the name of an item (New) on a menu
(File).
<Return> key Indicates the name of a keyboard key or mouse
<Select> button button; it also indicates the pop-up menu that is
displayed by pressing the mouse button of the
<Operate> menu same name.
<Control>-<g> Indicates two keys that must be pressed
simultaneously.
<Escape> <c> Indicates two keys that must be pressed
sequentially.
Integer>>asCharacter Indicates an instance method defined in a class.
Float class>>pi Indicates a class method defined in a class.

Mouse Buttons and Menus


VisualWorks supports a one-, two-, or three-button mouse common on
various platforms. Smalltalk traditionally expects a three-button mouse,
where the buttons are denoted by the logical names <Select>,
<Operate>, and <Window>:

<Select> button Select (or choose) a window location or a menu


item, position the text cursor, or highlight text.
<Operate> button Bring up a menu of operations that are
appropriate for the current view or selection. The
menu that is displayed is referred to as the
<Operate> menu.
<Window> button Bring up the menu of actions that can be
performed on any VisualWorks window (except
dialogs), such as move and close. The menu that is
displayed is referred to as the <Window> menu.

Application Developer’s Guide 27


About This Book

These buttons correspond to the following mouse buttons or


combinations:

3-Button 2-Button 1-Button


<Select> Left button Left button Button
<Operate> Right button Right button <Option>+<Select>
<Window> Middle button <Ctrl> + <Select> <Command>+<Select>

Note: This is a different arrangement from how VisualWorks used


the middle and right buttons prior to 5i.2.
If you want the old arrangement, toggle the Swap Middle and Right Button
checkbox on the UI Feel page of the Settings Tool.

Getting Help
There are many sources of technical help available to users of
VisualWorks. Cincom technical support options are available to users
who have purchased a commercial license. Public support options are
available to both commercial and non-commercial license holders.

Commercial Licensees
If, after reading the documentation, you find that you need additional help,
you can contact Cincom Technical Support. Cincom provides all
customers with help on product installation. For other problems there are
several service plans available. For more information, send email to
supportweb@[Link].
Before Contacting Technical Support
When you need to contact a technical support representative, please be
prepared to provide the following information:
• The version id, which indicates the version of the product you are
using. Choose Help ­ About VisualWorks in the VisualWorks main
window. The version number can be found in the resulting dialog
under Version Id:.
• Any modifications (patch files) distributed by Cincom that you have
imported into the standard image. Choose Help ­ About VisualWorks in
the VisualWorks main window. All installed patches can be found in
the resulting dialog under Patches:.

28 VisualWorks
Getting Help

• The complete error message and stack trace, if an error notifier is the
symptom of the problem. To do so, select copy stack in the error notifier
window (or in the stack view of the spawned Debugger). Then paste
the text into a file that you can send to technical support.
Contacting Technical Support
Cincom Technical Support provides assistance by:
Electronic Mail
To get technical assistance on VisualWorks products, send email to
supportweb@[Link].
Web
In addition to product and company information, technical support
information is available on the Cincom website:
[Link]
Telephone
Within North America, you can call Cincom Technical Support at
(800) 727-3525. Operating hours are Monday through Friday from
8:30 a.m. to 5:00 p.m., Eastern time.
Outside North America, you must contact the local authorized
reseller of Cincom products to find out the telephone numbers and
hours for technical support.

Non-Commercial Licensees
VisualWorks Non-Commercial is provided “as is,” without any technical
support from Cincom. There are, however, on-line sources of help
available on VisualWorks and its add-on components. Be assured, you
are not alone. Many of these resources are valuable to commercial
licensees as well.
The University of Illinois at Urbana-Champaign very kindly provides
several resources on VisualWorks and Smalltalk:
• A mailing list for users of VisualWorks Non-Commercial, which
serves a growing community of VisualWorks Non-Commercial users.
To subscribe or unsubscribe, send a message to:
vwnc-request@[Link]
with the SUBJECT of "subscribe" or "unsubscribe".
• An excellent Smalltalk archive is maintained by faculty and students
at UIUC, who are long-time Smalltalk users and leading lights in the
Smalltalk community, at:

Application Developer’s Guide 29


About This Book

[Link]
• A Wiki (a user-editable web site) for discussing any and all things
VisualWorks related at:
[Link]
• A variety of tutorials and other materials specifically on VisualWorks
at:
[Link]
The Usenet Smalltalk news group, [Link], carries on active
discussions about Smalltalk and VisualWorks, and is a good source for
advice.

Additional Sources of Information


This is but one manual in the VisualWorks library. The Cincom Smalltalk
publications website:
[Link]
is a resource for the most up to date versions of VisualWorks manuals
and additional information pertaining to Cincom Smalltalk.

Smalltalk Tutorial
A new VisualWorks Smalltalk tutorial is available online at:
[Link]
The tutorial information is growing, so revisit this site.

Online Help
VisualWorks includes an online help system. To display the online
documentation browser, open the Help pull-down menu from the
VisualWorks main menu bar and select one of the help options.

VisualWorks FAQ
An accumulating set of answers to frequently asked questions about
VisualWorks is being compiled in the VisualWorks FAQ, which
accompanies this release and is available from the Cincom Smalltalk
documentation site.

30 VisualWorks
Smalltalk Tutorial

News Groups
The Smalltalk community is actively present on the internet, and willing to
offer helpful advice. A common meeting place is the [Link]
news group. Discussion of VisualWorks and solutions to programming
issues are common.

VisualWorks Wiki
A wiki server for VisualWorks is running and can be accessed at:
[Link]
This is becoming an active place for exchanges of information about
VisualWorks. You can ask questions and, in most cases, get a reply in a
couple of days.

Commercial Publications
Smalltalk in general, and VisualWorks in particular, is supported by a
large library of documents published by major publishing houses. Check
your favorite technical bookstore or online book seller.

Examples
There are a number of examples in file-in format in the examples
subdirectory, under the VisualWorks install directory. In addition, several
example listings in the document, especially those in Chapter 18,
“Working With Graphics and Colors”, invoke an Examples Browser. This
browser is provided in a parcel, also in the examples directory.

Application Developer’s Guide 31


1
The VisualWorks Environment

VisualWorks is a complete Smalltalk development environment, including


an implementation of the Smalltalk language, an virtual machine (also
called the object engine) for executing Smalltalk code, and a wide
assortment of tools for creating applications in Smalltalk.
In this chapter we give an introduction to the primary features of the
VisualWorks environment. We will not describe everything or in detail.
Take these descriptions as pointers to what is available in VisualWorks,
and explore to become familiar with the environment.

Running VisualWorks
VisualWorks runs as an virtual machine processing the data in a
Smalltalk image. The virtual machine is an executable file that interprets
and executes the Smalltalk byte-code stored in the image. There is a
separate virtual machine for each operating system supported by
VisualWorks.
The image is in a platform-independent file format, allowing a single
image file to run under the VisualWorks virtual machine running on any
supported operating system.
As you work with VisualWorks, the primary way of saving your work is by
saving the image, either periodically while working or when exiting
VisualWorks. You will probably accumulate several images over time. To
start a specific image, simply specify that image on the startup command
line.

32 VisualWorks
Running VisualWorks

Starting VisualWorks
To start VisualWorks, you run the virtual machine with the image file
passed as the argument:
virtual_machine image_file
For development work, we recommend using the engines named
vw<plat> (for example, [Link] for Microsoft Windows systems or
vwlinux86 for Linux systems). These engines include debug symbols
which can be helpful if you suffer engine crashes.
The virtual machines named [Link] on Windows systems and
visual on Unix and MacOS systems, are stripped versions of these
engines, and are suitable for deployed applications. See the Appendix
“Virtual Machines” on page 560 for more information about development
and deployment options.
By default, the virtual machine is installed in the bin/<platform>/
subdirectory of the root VisualWorks installation directory.
If no image file is specified, the virtual machine looks for an image with
the same name as the engine. For example, if you execute visual (or
[Link]) without an image name, it will look for [Link],.
Typically, you will start by changing to the image subdirectory, and
execute the object engine with the image as argument. For example:
> cd c:\visual\image
> ..\bin\win\[Link] [Link]
If you use a file manager to start VisualWorks, you may need to specify
full paths for both the object engine and the image.
If both the virtual machine and the image file are in the same directory, no
path information is required at all.

VisualWorks Command Line Options


There are three types of command line options that you can use when
starting VisualWorks: object engine switches, image level switches, and
user-defined switches.
The generic command line syntax is:
<oe name> [oe switches] <image-name>
[image switches] [user-switches]
For a complete description of the various object engine switches, see
“Virtual Machine Command Line Options” on page 562.

Application Developer’s Guide 33


Chapter 1 - The VisualWorks Environment

For additional information on providing your own splash screen and


startup sound, refer to “Replacing Startup Splashscreen and Sound” in
Chapter 14, “Application Delivery”.
Image Level Switches
Application-specific switches may be defined in the image. For a
description of the mechanisms used to define command line options,
refer to “Handling Command Line Options” in Chapter 14, “Application
Delivery.”
The following image level switches are available for use on deployment
images that are created RuntimePackager. Refer to “Running a Deployed
Image” in Chapter 14, “Application Delivery” for details.
-pcl parcelFile
Load the parcelFile into the image on startup. Parcels are external file
representations of packages (refer to Chapter 7, “Managing Smalltalk
Code”).
-cnf configurationFile
Load all of the parcel files named in the <Configuration File> on
image startup.
-psp dir1 dir2 ...
Sets the parcel search path to include the specified directories.
-err errorFile
Set the path and file name for the error log file.
-notifier NotifierClass
Set class for unhandled exceptions to NotifierClass.

Capturing Command Line Options


The Smalltalk expression:
CEnvironment commandLine
returns an Array of Strings which are the command line switches and
arguments, in the order they were specified.
Instead of sending commandLine directly, an application can register
dependencies that will trigger an action based on command line options.
There are two mechanisms for registering these dependencies. Refer to
“Handling Command Line Options” in Chapter 14, “Application Delivery”
for more information.

34 VisualWorks
Running VisualWorks

Running Multiple Versions Under Windows


On Microsoft Windows systems, you can launch VisualWorks by double-
clicking on an image file, as long as the .im extension is associated with
the virtual machine. However, if you have multiple versions of
VisualWorks installed, Windows only associates one engine with the
extension. In this case, associate the small executable,
[Link] with the .im extension, and edit [Link]
to identify the location of the engine for each applicable version.
Both [Link] and [Link] are installed, by
default, in the bin\win\ directory. Copy these to a different directory
that you will maintain independently of any specific installation of
VisualWorks, such as c:\visualworks.
Associate the .im extension in Windows with this executable by creating
an “open” action for the extension, and specify in the “Application used to
perform action” field:
C:\visualworks\[Link] "%1"
When you double-click on an image file, [Link] is launched
with the image clicked as argument.
Each release of VisualWorks includes a new [Link] that
contains a line with a default listing for the current release. Copy the line
from this file into your own copy of the file, and edit the vm path name for
your installation. After accumulating for several releases, you may have a
file that looks like:
72 00 c:\vw7.2\bin\win\[Link]
71 00 c:\vw7.1\bin\win\[Link]
70 00 c:\vw7\bin\win\[Link]
54 00 c:\vw5i.4\bin\win\[Link]
53 00 c:\vw5i.3\bin\win\[Link]
53 78 c:\vw5i.3\bin\win\[Link]
52 00 c:\vw5i.2\bin\win\[Link]
52 78 c:\vw5i.2nc\bin\win\[Link]
The first two digits indicate the VisualWorks release number, and the
second two are either 00 or 78, indicating commercial and
noncommercial releases, respectively. [Link] matches
these numbers with a version identifier in the image file to invoke the
appropriate virtual machine. These numbers are the fifth and sixth bytes
of the array returned by:
ObjectMemory versionId
which is also shown by selecting Help ­ About VisualWorks... in the
Launcher.

Application Developer’s Guide 35


Chapter 1 - The VisualWorks Environment

Saving the Image


The image file is a “snap-shot” of the VisualWorks environment, including
all the code that makes up the development environment, class libraries,
tools, and your application. Saving the image is the traditional Smalltalk
way of saving changes to the system as you develop the application.
The original image file distributed with VisualWorks is called [Link]
([Link] in non-commercial distributions). This is the minimal
development image, and should be kept safe.
To save the current image, select File ­ Save Image in the VisualWorks
Launcher. The current image file is then updated with any changes.
To save the image to a new name, select File ­ Save Image As... A dialog
prompts you for the name of the image, with the current image name as
the default. To save the image to a different file, keeping the previously
saved image safe, enter a new name, without the .im extension.
It is recommended that you keep a known good backup image, either a
copy of the image as originally supplied with VisualWorks, or a copy of
the basic image with optional tools and add-ins installed. This provides a
clean start whenever needed.

Restoring the Original Image


A clean copy of the original [Link] (or [Link]) image file is
included in the image/ directory, in the file [Link] (or
[Link]). If you accidentally overwrite the original image file or
otherwise need to restore it, unzip this file into the image/ directory.

Caution: Be aware that unzipping this file will overwrite the


[Link] in that directory, destroying any changes it might
contain.

Sources and Changes


When you make a backup copy of a VisualWorks image, three files must
be archived together: the image file, the sources file, and the changes
file. These three files are synchronized, and so must be backed up
together in order to have a complete record of the system.
The image file has already been described at the beginning of this
chapter.

36 VisualWorks
Exiting VisualWorks

The sources file holds source code for the original VisualWorks system
image before you made changes. By default it is named [Link]
which is the original image name with a .sou extension.
The changes file, which typically has the same name as the image file but
with .cha as its extension, contains source code for changes you have
made to the system, specifically for any application code you have
created. Changes are recorded to this file every time you accept an edit,
whether or not you save the image, so you always have a history of work.
The changes file can become very large, and so should occasionally be
condensed using Changes ­ Condense Changes from the Launcher’s System
menu. This removes all but the latest version of each system change.
You can change the name of the sources file and of the changes file on
the Source Files page of the Settings Tool (to open this tool, select System
­ Settings in the Launcher window).

Exiting VisualWorks
To end a VisualWorks development session, select File ­ Exit VisualWorks
in the Launcher. A dialog prompts you to save the image before exiting. If
you choose to save the image, you may provide a new filename.
Selecting Cancel continues your session in the VisualWorks development
environment.
Note that closing the Launcher window, for example by clicking the
window’s close icon, allows you to either exit VisualWorks, or simply
close the Launcher window itself.

Closing on Windows Shutdown


When you shut down a Microsoft Windows system with VisualWorks
running, the image closes ending the VisualWorks session. The exit may
ungracefully close resources, possibly resulting in data loss. This is
important, for example, in database applications that might have an open
session.
Windows shutdown events are delivered to the VisualWorks image as a
QuitSystem event. There are a couple of ways to handle this.
• You can put a dependent on ObjectMemory and have an update
method that watches for #aboutToQuit (which would also be triggered
every time the image is quit).
• You can modify InputState>>#send:eventQuitSystem: to provide some
special hook only invoked on an exit event.

Application Developer’s Guide 37


Chapter 1 - The VisualWorks Environment

• Instead of modifying #send:eventQuitSystem:, you can add your own


quit method to InputState, such as #send:myEventQuitSystem:, and then
at system startup, you can go to [Link] and put
your own message at position 19.
Alternatively, you can block VisualWorks from exiting, though this is not
the best solution. By default, the InputState class method
setDispatchTableForPlatform registers true with the acceptQuitEvents object.
To prevent VisualWorks from being prematurely shut down, set this to
false instead.

Emergency Exit
If VisualWorks stops responding to inputs such as mouse movements,
there are a few options.
You can press <Control>-\ to open the Process Monitor, which lists all
running VisualWorks processes. All UI processes are paused, as can be
seen by examining the listings. You can select a process and debug it to
find the problem.
Pressing <Control>-Y opens a debugger directly on the current process, by-
passing the Process Monitor.
If that doesn’t work, you can use the Emergency Evaluator. To open an
Emergency Evaluator, type <Shift>-<Control>-Y. An Emergency Evaluator
window will appear, with instructions to type a Smalltalk expression
terminated by <Escape>. Enter:
ObjectMemory quit
in the window, then press <Escape>. The system will shut down, after which
you can restart it.
To save the image before quitting, send:
ObjectMemory saveAs: 'filename' thenQuit: true
Then press <Escape>.

The VisualWorks Launcher


The VisualWorks Launcher is the main window from which you perform
operations in the VisualWorks development environment. The Launcher
is displayed when you start VisualWorks, and a copy of it remains open
until you exit the VisualWorks development environment. (VisualWorks
runtime applications don’t display a Launcher.)

38 VisualWorks
Workspace

The Launcher primarily provides a way to launch VisualWorks tools,


either by selecting menu items or by clicking one of the buttons on the
button bar. (To find out what the buttons do, try them.) Individual items will
be described in sections throughout this document.
Attached to the Launcher is the system Transcript. It shows a running list
of informational messages generated by VisualWorks or your code.
For details on individual menu functions, you may use the VisualWorks
Help System (available from the Launcher’s Help menu) to browse topics
concerning VisualWorks Tools.

Workspace
A Workspace is a window in which you can test Smalltalk code. To open a
Workspace, choose Tools ­ Workspace or click on the Workspace icon in
the VisualWorks Launcher.

Application Developer’s Guide 39


Chapter 1 - The VisualWorks Environment

The Workspace is a multipage tool, each page containing independent


contents. One page, labeled Variables, displays the current workspace
variables and their values. Each workspace page can be saved into its
own file. The multipage structure allows you to have several workspaces
open at a time, sharing workspace variables. You can also “tear off” a
workspace page to have it in its own single-page workspace.
Workspaces are useful, for example, to hold expressions entered and
ready to execute for testing different features of the application you are
developing.

Evaluating Code in a Workspace


The workspace is a test bed for Smalltalk code. When you enter a
Smalltalk expression into a workspace, or any other code pane for that
matter, there are four evaluation methods to use. To invoke any of these,
either select the expression to evaluate or, to evaluate a whole line simply
position the cursor somewhere on the line. Then, using either the
<Operate> menu, the Smalltalk menu, or the button, select the operation:
Do it
Silently evaluates the selected expression. Any output is the
responsibility of the expression being evaluated.
Print it
Evaluates the selected expression and prints the return value in the
workspace.
Inspect it
Evaluates the selected expression and opens an inspector on the
return value.
Debug it
Evaluates the expression and opens a debugger on the first
message-send. This is similar to placing a self halt in the code and
evaluating. You can then use Step into and Step commands to explore
the code’s operation. See Chapter 10, “Debugging Techniques” for
suggestions.

Workspace Variables
Temporary variables used in a workspace have the workspace as their
scope, and exist as long as the workspace does, or until they are
explicitly cleared. These variables, called workspace variables, are
created when first assigned a value. That assignment then persists and
can be referenced by subsequently evaluated expressions in that
workspace. The variable and its assignment are saved with the image,
and so are available when reloading the saved image.

40 VisualWorks
Workspace

For example, you can define a variable to hold an array simply by


evaluating an assignment operation:
fred := Array with: 5
The variable fred persists now, and so is available for further operations,
such as:
fred at: 1 put: ‘this is a test’
To inspect, remove, or otherwise edit workspace variables, click on the
the Variables tab. This toggles the display of an inspector on the current
variables for this workspace. Select a variable and use the commands on
its <Operate> menu to perform operations on the variable.

Name Spaces in Workspaces


Workspaces can import name spaces, enabling them to better simulate
the name scopes of running code. Without imports, the default name
space is Smalltalk, so you would have to reference shared variables, such
as your application class names, using a long dotted name.
By default, and for convenience, a workspace imports all name spaces in
the system. This allows you to refer to all shared variables by their
unqualified names. For better simulations of naming scopes, you can
specify just which name spaces to import.
To set the name space selection, select the Smalltalk ­ Namespaces...
menu. In the selection dialog, select either All or Some. If you select Some,
also select which name spaces to import. Imported name spaces are
then indicated by a check mark.

Saving Workspace Contents


You can save the contents of your workspace as a text file for later
opening. This is useful, for example, for saving collections of test
expressions. Note that workspace variables are not saved with the file,
and so must be recreated when the file is opened back into a workspace.
To save the contents as a file, select Page ­ Save or Page ­ Save As... in
the workspace menu. If the workspace has not already been saved, you
are prompted for a name, with the .ws extension supplied.
To load a saved workspace, open a new workspace, then select
Page ­ Open, and enter the workspace name.

Application Developer’s Guide 41


Chapter 1 - The VisualWorks Environment

Performing Operations
While working in VisualWorks, you will need to perform a variety of
operations, such as opening tools and evaluating (executing) Smalltalk
expressions. The Launcher’s menus and buttons provide some number of
these operations, and many more are available in pop-up menus
throughout the system.
VisualWorks, like all Smalltalk systems, requires a mouse as a pointing
device. There are three primary operations performed using the mouse,
corresponding to three “buttons”:
• The <Select> button selects objects and text.
• The <Operate> button opens the <Operate> menu, which contains
commands appropriate to the current view. This context-sensitive
menu changes based on the current window and selection.
• The <Window> button opens the <Window> menu, which contains
commands that operate on the current window.
Since VisualWorks supports systems with one, two, and three-button
mouses, there are different methods for invoking operations on the
different systems.

3-Button 2-Button 1-Button


<Select> Left button Left button Button
<Operate> Right button Right button <Option>+<Select>
<Window> Middle button <Ctrl> + <Select> <Command>+<Select>

Note: This is a different arrangement from that used in previous


releases of VisualWorks. If you want the old arrangement, toggle the
Swap Middle and Right Button checkbox on the Look and Feel page of the
Settings Tool.

The <Operate> menu is probably the most important in VisualWorks.


Many of the operations and procedures described throughout this
document involve picking a command from the <Operate> menu.
As an example, display some text in the Transcript. Open a workspace,
and type these expressions in it:
Transcript show: 'Test Message' printString.
Transcript cr.

42 VisualWorks
Add-in Components

Now select the entire text (using the <Select> button), then click and hold
the <Operate> button with the cursor in the workspace. Move the cursor
to select Do it, and release the mouse button. The text will display in the
Transcript.

Selecting Do it in this way is the usual way of evaluating a Smalltalk


expression for testing. If the expression doesn’t do anything resulting in
an action in a window, you can use Print it on the <Operate> menu. Try
evaluating the numeric expression 3 + 4 in the workspace using Print it.
Other commands on the <Operate> menu are explained as needed in
this manual. As usual, the most effective way of learning about the many
options is to experiment with them.

Add-in Components
With the increased use of components in VisualWorks, many features of
the development environment are now distributed as add-ins to the base
image. These add-in components are distributed as parcel files. When
loaded into the image, the components are represented as packages and
bundles. When Store is installed, packages and bundles can be loaded
directly from a repository (refer to Chapter 7, “Managing Smalltalk Code”
below and the Source Code Management Guide).

Application Developer’s Guide 43


Chapter 1 - The VisualWorks Environment

For example, the UI Painter is a standard tool for developing the GUI in a
VisualWorks application, and used to be part of the standard base image.
It is now an add-in component that must be loaded for GUI development.
However, it does not need to be loaded to run an application with a GUI
built using the UIPainter. The runtime support is in the base image.
Partitioning and the environment in this way keeps the image smaller, and
simplifies application delivery, by allowing you to load only those
components required for your application and development environment.

Parcel Manager
Parcels and their descriptions may be viewed, loaded, and unloaded
using the Parcel Manager. To open a Parcel Manager, choose
System ­ Parcel Manager or click on the corresponding icon in the Launcher
window.

The Parcel Manager enables you to view all parcels on the parcel path
that is associated with the working image. Use the tab control over the
left-hand list to control the presentation of the parcel path.
Select the Suggestions tab to see a pre-defined set of recommended
parcels. Each category under Suggestions contains parcels that have been
identified as key add-in features for VisualWorks. By selecting a particular

44 VisualWorks
System Browser

category, you may view a list of recommended parcels (shown in the


upper-right view). For example, the UI Painter, located under Essentials, is
the main VisualWorks tool for GUI development.
To load a parcel, select it from the parcel list (upper-right corner of the
tool), and choose Load from the <Operate> menu. You may also use the
toolbar button, or simply double-click on the name of the parcel.
To browse those parcels that have already been loaded in the image,
select Browse from the <Operate> menu.
The Parcel Manager uses several special icons to distinguish product
parcels from others (the “shopping sack” icon):

Icon Description

Supported VisualWorks product parcels

Goodies or add-in components from other vendors

Use the tab controls on the parcel list view to view parcels sorted in
alphabetical order, or a hierarchical presentation ordered by parcel
prerequisties. The parcel details view (lower-right corner) shows
comments and properties associated with the selected parcel.

Viewing the Parcel Path


By choosing the Directories tab, you may view the parcel paths as a
directory tree. Selecting a particular directory displays all the parcels
contained within it. Select Loaded to view all parcels that are currently
loaded into your working image.
To change, add or remove items from the parcel path, use the Parcel Path
page in the Settings Tool (select System ­ Settings in the Launcher
window). For more information about parcels, see “Parcels” on page 149.

System Browser
The principal programming tool in VisualWorks is the System Browser.
You use it for “browsing” the code library, editing, compiling, and printing
any selected portion of source code. The browser also provides special-
purpose tools for refactoring, rewriting, checking and testing code.
To open a browser, choose Browse ­ System or click on the Browser icon
in the VisualWorks Launcher window.

Application Developer’s Guide 45


Chapter 1 - The VisualWorks Environment

The System Browser allows you to select either a Package (the default)
or Parcel view of the system components. From either of these views, you
can also select a Hierarchy view, either of the selected component or of
the entire system. To change the primary view for a browser, select the
view from the Browser menu. To change default view for a new browser,
use the Browser page of the Settings Tool (System ­ Settings in the
Launcher window, and set the default to either Package or Parcel.
Depending on the current view, the lists show different items. It will take
some experimentation and experience to get comfortable with the
browser, but the following comments will guide your learning.
Package / Bundle Class / Namespace Protocol Method / Variable
List List List List

Navigator

Code Tool

The browser window is composed of a navigator and a set of code tools.


You select a view in the navigator by clicking on upper row of tab control
buttons. Code tools are selected using the lower row of tab controls. The
tab label indicates which view or tool it selects, and its current focus.

46 VisualWorks
System Browser

Navigator

Code Tool

The VisualWorks system is organized as a class library. Classes are


defined in an inheritance hierarchy, which you can browse by selecting
the navigator’s Hierarchy tab.
For organizational purposes, classes are grouped into packages, and
packages can be grouped into bundles. Packages and bundles can be
saved, or “published,” as parcels, which are essentially a external file
based representation of a package or bundle. This organization is
described more fully in Chapter 7, “Managing Smalltalk Code” below.
You use the navigator to traverse the VisualWorks class library, viewing
definitions for classes, namespaces, methods, and variables.
The Package, Parcel, and Hierarchy views each has its own <Operate>
menu, offering commands that are appropriate to its contents. Many of
the commands are obvious. Specific commands are explained throughout
this document as the operation is discussed. For details on individual
menu functions, view the online help available from the browser’s Help
menu.

Application Developer’s Guide 47


Chapter 1 - The VisualWorks Environment

Browser Navigator
The different parts of the browser’s navigator provide different views of
the system. Here is a brief summary of their function and use:
Package View
The VisualWorks library is organized into packages and bundles. Each
code definition is contained in a package, and can be viewed by selecting
the package. Packages can also be grouped into bundles and the
contained definitions browsed. The browser displays packages when
Package tab is selected in the Browser.
When Store is loaded, packages and bundles support code revisioning
and related mechanisms to assist in source code management. For
information about working with packages, refer to the VisualWorks
Source Code Management Guide.
Note that the use of packages has now replaced the use of class
categories and parcels as units of organization, as they were used in
previous versions.
Parcel View
Parcels are intended as a deployment artifact, providing an external file
container for the code defined in packages (and bundles). At this time, it
is still useful to be able to browse code in terms of the parcel that
contains it, which you do using the Parcel view (select Browser ­ Parcel in
the system browser).
Hierarchy View
Occasionally it is useful to explore a class in terms of the other classes
from which it inherits behavior, or that inherit behavior from it. The
navigator allows you to do this by displaying the hierarchy of the selected
class.
To vew the entire class hierarchy, start by selecting class Object. You can
then find and browse a class by navigating through the hierarchy to it.
Although this is seldom very useful, it can be instructive.
Class / Name Space View
Classes and name spaces are defined in packages, so the contents of
the Class / Name space view depend upon the selected Package.
In addition to having a superclass, each class is defined in a name space,
which identifies the s. A name space is a name resolution scope for name
space, class, and shared variable names. Typically, you create your own

48 VisualWorks
System Browser

name space and then create your applications within that name space.
(Refer to “Working with Name Spaces” in Chapter 6 for more
information.)
When the class hierarchy view is selected, this view shows the containing
package for the selected item.
Instance, Class, and Variable Views
The Instance, Class, Shared Variable and Instance Variable tabs toggle the
contents of the method category and method/variable views, selecting
whether the categories and defintions of instance methods, class
methods, shared or instance variables are shown. In some situations,
such as when a namespace is selected that has only shared variables
defined in it, only one of the buttons, in this case Shared Variables, is shown.
Usually, any of the buttons can be selected, even though there may be no
entries for that view.
Icons in the Navigator
The browser’s navigator uses a number of special icons to distinguish
code components, special system classes, as well as the condition of
individual methods. The following table offers a brief summary:

Icon Description

Package

Bundle

Name space

Subclass of Model

Subclass of ApplicationModel

Subclass of Collection

Subclass of Exception

Method redefined by at least one superclass.

Method redefined by at least one subclass.

Application Developer’s Guide 49


Chapter 1 - The VisualWorks Environment

Working with the Browser


The System Browser separates code tools from the navigator so that a
variety of code tools may be used with each navigator. Generally, you use
the Source tool to examine class, namespace and variable definitions, and
to browse and edit source code.
The browser includes a feature-set for automated code refactoring (refer
to Chapter 22, “Refactoring” for details). For advanced development, the
browser also provides special tools for code checking, rewriting, and unit
testing (refer to Chapter 23, “Coding Tools”).
To encourage learning and experimentation, each operation in the
browser can be reversed with the Undo function (on the Browser menu).
Editing Source Code
The Source code tool in a System Browser is where you do most writing
and editing of your application’s class and method definitions. Common
editing operations, such as cut, paste, find and replace, are available on
the <Operate> menu for this pane.
When you select a package but no class, a class definition template is
displayed. Similarly, when you select a protocol but no method, a method
definition template is displayed. To create a new class or method, edit the
template with the appropriate definition. When you have edited a
definition, you need to save, or accept, your changes. Select Accept from
the code pane <Operate> menu.
Missing Source Code
Your Smalltalk image is associated with a sources file, as described in
Sources and Changes above. If the sources file is not correctly identified
in the Settings Tool, or your VisualWorks home directory is not correctly
set, or if the sources simply are not available, you may see code in the
browser with a comment explaining that it is decompiled code. If you see
this comment, set the home directory and/or edit the Source Files page of
the Settings Tool, making sure the .sou file name agrees with the image
name. (To open the Settings Tool, choose System ­ Settings in the
Launcher window.)

50 VisualWorks
System Browser

Searching
The navigator tool bar includes an entry field to do a quick search by
name for classes, variables, or methods:

To find a class, simply enter its name and select Accept from the
<Operate> menu, or press the <Return> key. To find a method, enter its
name, preceded by the # (pound) character. Wildcard searches are
possible using the * (asterisk) character.
Drag and Drop
To reorganize code, you can drag and drop methods on classes or
protocols; protocols on other classes or on protocols; classes on other
categories; and categories on other categories.
Controlling Visibility of Methods
By default, the browser’s method list only displays those methods
belonging to the currently selected class and protocol. Several options
are provided for controlling and expanding the visibility of methods.
When a class is selected, the browser may optionally be set to show all
methods in the class when no protocol is selected. To enable this option,
select Show all Methods when No Protocols Selected on the Browser page of the
Settings Tool.
Just as it is often useful to see class inheritance using the Hierarchy view,
so too it is often useful to see inherited methods. To expand the visibility
of the Method List to include inherited methods located in a superclass,
select the name of the superclass from the Method ­ Visibility menu. This
setting remains active until you navigate to another class.
To fix the initial visibility setting so that it remains active while viewing
different classes, select Show All Inherited or Show All Inherited Except for Object.
To disable the expanded visibility, choose Show No Inherited.
Using Multiple Views
The System Browser can have with multiple active “views” on a method.
For example, while editing one method, you can switch to a new view to
look up some value in another method, and then return back to your
edited method without opening a new browser.

Application Developer’s Guide 51


Chapter 1 - The VisualWorks Environment

To create a new view, use View ­ New or corresponding icon in the


browser’s tool bar. Select the entries on the View menu to toggle rapidly
between the different views you’ve created. Use View ­ Remove to delete
the current view.
Source Code Formatting
To format a method using the browser’s integrated code formatter, select
Format from the source code tool’s <Operate> menu.
Many of the browser’s refactoring commands also invoke the code
formatter, so you should expect a formatting change any time you refactor
a method.
The formatting rules are user-accessible and may be changed. The rules
are located in class RBConfigurableFormatter, and they may be changed
using a special tool. To set the browser to use the configurable formatter
by default, evaluate:
RBProgramNode formatterClass: RBConfigurableFormatter
To open configuration the tool, evaluate:
FormatterConfigurationTool open
The Configuration Tool presents about 20 separate rules. When changing
a rule, you must Accept the changed value using the <Operate> menu in
the value’s input field. To examine the effects of the rules on a test
method, click on the tool’s embedded Format button. To save any changes
you make to the rules, click on the OK button.
Method source in the browsers may also be color coded. To enable color
coding, load the ColorEditing parcel (it can be found in the Parcel
Manager’s Environment Enhancements category).

52 VisualWorks
File Browser

File Browser
The File Browser allows you to navigate the local file system, listing and
selecting directories and files. It is commonly used to find Smalltalk
source files to file-in (.st files), and for editing simple text files.
To open a File Browser, choose File ­ File Browser or click on the
corresponding icon in the Launcher window.

Volumes and directories are shown on the right, files and their contents
on the left. When a file is selected in the upper-right view, its contents are
displayed in the lower-right view.
Special structured viewers are included for displaying VisualWorks
source files (.st), parcels, parcel source files, and XML source files. Use
the tab controls on lower-right view to select the desired view.
See the VisualWorks Tools topic in the online Help for more information
about the File Browser (select Help ­ Topics).

Application Developer’s Guide 53


Chapter 1 - The VisualWorks Environment

Inspectors
An inspector allows you to examine objects by exploring their constituent
objects, the values of the object’s instance variables. The VisualWorks
inspector incorporates a number of additional editing tools that greatly
enhance the control you have over live objects.
The inspector has a variety of options, and you can use it to perform a
number of operations that otherwise might require several tools. We
describe a few features here, but you should explore and experiment
further.

Inspecting an Object
At the core of the inspector are two views, with the object’s variables
listed in the left-hand view. When you select a variable, its value appears
in the right-hand view.
For example, to inspect a point, enter this expression in a Workspace,
select it, and then select Inspect it from the <Operate> menu:
50@30
Alternatively, evaluate this expression using Do it:
(50@30) inspect
The resulting Point has two variables, x and y.

To view the value of a variable, select it. The value is shown in the right-
hand view. You can also do a multi-select of values, to see the values of
the selected variables all at the same time.

54 VisualWorks
Inspectors

You can inspect the component objects also, by selecting the object in the
left view and selecting dive in the inspector’s <Operate> menu, which then
shows the selected object in the current inspector. To back out of a diving
inspector, select Back in its <Operate> menu. To open a new inspector on
the object, select Inspect from the Object menu.
For some objects, the Basic view may include extra parts which are not its
instance variables. -self, for example, is a part that is always there even
though it is not an instance variable (these aspects of the object are
distinguished with a leading hyphen “-” character). For a further example,
have a look at a compiled method. Evaluate:
(Object compiledMethodAt: #printString) inspect
The basic view includes -bytecode and -source. These are not really parts of
the receiver, as instance variables are, but they are included in the basic
view as "virtual" attributes, just like -self, the object itself. For more
examples, inspect an Integer or a Character.
Drag-and-drop operations can be performed on the elements. If you
select a variable and drag it on top of another, its value will be assigned to
the target variable.

Inspecting Collections
Specialized inspectors for dictionaries and other collections provide
extended inspecting capabilities. For example, evalutate:
(OrderedCollection with: 1 with: 2 with: 3 with: 4) inspect
The resulting special inspector opens on the elements of the collection.

Notice that there is an additional tab, labeled Elements. This gives a


higher-level view of the object, showing only the elements of the
collection. The Basic tab, which is in all of the inspectors, is the general
inspector, equivalent to evaluating with basicInspect.

Application Developer’s Guide 55


Chapter 1 - The VisualWorks Environment

In addition to using drag-and-drop for value assignment, you can use it to


reorder the elements of a collection. Select an element, drag it between
two other elements, and drop it.

Modifying Objects
The right-hand view is a code view, in which you can type and execute
Smalltalk expressions. In this respect, it is like a workspace. Variables are
resolved within the scope of the code view.
Occasionally it is useful to set the value of a variable. You can do this by
entering an expression in the code view, and then selecting Accept in the
view’s <Operate> menu. This evaluates the expression and assigns the
return value to the variable under inspection.
For example, in the OrderedCollection inspector shown above, select the
first element. It’s current value is 1. In the code view, enter:
1+1
Select it and pick Accept in the view’s <Operate> menu. The expression is
evaluated to 2, which is then assigned to the variable.

Evaluating Expressions
While you can evaluate an expression in a code view, you lose that
expression as soon as you select another variable. A convenience feature
is a code evaluator view that can preserve expressions entered in it.

56 VisualWorks
Inspectors

To open the evaluator, select Options ­ Evaluation Pane. The pane is


opened at the bottom of the inspector.

The pane works much like the workspace. However, the evaluation
context is the object under inspection. Accordingly, you can use self to
refer to the object itself, and can perform operations on the object.
You can also “save” the contents of the evaluation pane, making the
same contents available to all inspectors. The contents are stored in the
inspector’s class variable, and so is shared by all instances. To write the
contents to the variable, select Accept on the pane’s <Operate> menu.

Browsing and Editing Behavior


The Methods tab displays a class browser on the object’s class. This
makes it convenient to modify the object’s behavior without opening a
separate browser.
The features are the usual ones, with one notable addition. The Inheritance
menu lists the class and its superclasses, and allows you to select the
depth of the inheritance for the methods is displays. This makes is easy
to browse and edit the object’s methods no matter where they are
defined.

Application Developer’s Guide 57


Chapter 1 - The VisualWorks Environment

UI Painter
The “Visual” in VisualWorks emphasizes the graphical approach to
Graphical User Interface (GUI) design and development. This is provided
by the UI Painter.

The UIPainter is initially unloaded from the base image. To load it, use the
Parcel Manager (choose System ­ Parcel Manager in the Launcher window;
within the Parcel Manager, the UI Painter can be found in the Essentials
category).
The Painter tool is in three parts:
• Canvas (lower right) - represents a single window, on which you
place widgets, the graphical components of the GUI.
• Palette (top right) - presents a collection of widgets that are
commonly used in a GUI, and some widget arrangement buttons.
• GUI Painter Tool (left) - provides a collection of menu commands and
buttons for performing formatting and other operations on the canvas,
a hierarchical view of the widgets on the current canvas, and the
properties of the selected widget.

58 VisualWorks
Configuring the VisualWorks Environment

The Palette has one button for each type of widget. To add a component,
for example an input field, to your canvas, you simply click on the Input
Field icon in the Palette to select it, and then click in the canvas to place
the widget.
For a fuller description of this and related GUI building tools, as well as a
detailed description of GUI building in VisualWorks, refer to the GUI
Developer’s Guide.

Configuring the VisualWorks Environment


VisualWorks Home
A number of system resources and directories are selected relative to the
VisualWorks home directory. This directory is set by the installer, so
normally you do not need to set it yourself. Occasionally, however, it is
necessary to reset the variable, if, for example, you move the
VisualWorks enviroment to another directory location.
To set the home directory, select File ­ Set VisualWorks Home... in the
Launcher window, which opens the Settings manager to the System page.
Specify the root VisualWorks installation directory, typically the parent
directory for bin and image, by either typing its pathname or clicking the
Browse button and selecting it in the directory tree. Then click OK to save
the change and close the Settings manager, or Apply to save the change
without closing the manager window.
On Windows systems, the home directory is recorded in the system
registry. On Unix and Linux systems, you can set the variable in a startup
script or in your user profile.

Settings
VisualWorks includes a Settings Manager that allows you to control a
variety of global parameters, such as the appearance of windows (Look
and Feel), source file name (Source), default font for text (Text), and so on.
Groups of customizable features are organized as a tree on the left hand
side of the Settings manager. Select a group in the tree to see and
change its settings in the right hand side of the window
To open the Settings Manager, choose System ­ Settings in the
VisualWorks Launcher window, or click the corresponding button.

Application Developer’s Guide 59


Chapter 1 - The VisualWorks Environment

The Settings Manager consists of three parts: a tree of settings pages on


the left, the currently selected page on the right, and a row of buttons at
the bottom:

Settings are organized into pages. Settings on the same page are usually
related and affect the same area of application functionality. Each page
has a context-sensitive Help button that displays additional information to
guide you in the proper setting of each parameter.
Press the OK button to apply all unapplied changes on all pages and close
the window. It is not necessary to apply changes made to a page before
switching to another page. Use the Apply button to apply all changes,
leaving the window open.
The <Operate> menu of the settings tree includes allows you to
manipulate and modify settings. To save all settings on all pages in a file,
select Save... and specify the name of the file. Use Load... to read all
settings from a previously saved settings file. The values are accepted
immediately. To immediately restore the values of all settings to default,
select Reset to Default.
To load, save, or restore the settings of the current page, select Load
Page..., Save Page..., or Restore Page to Default. The values that are loaded or
restored are displayed, but not applied until either the OK or Apply button is
pressed.

60 VisualWorks
2
Object Orientation

Much of the literature on object-oriented programming (OOP) tends to


emphasize how it differs from procedural programming. And it is different,
in many important respects. Working with objects requires some new
ways of thinking, just as touch typing requires that you unlearn hunt-and-
peck habits.
Unfortunately, too often the strangeness of it all is overemphasized. Also,
as object-oriented programming has become increasingly common, most
frequently in the guise of C++ and Java, considerably less defense and
explanation is now required.
This chapter attempts to present object-oriented terms and concepts,
reflecting a definite Smalltalk terminological bias, using your
programming expertise as a bridge to the new world of objects.

Procedures vs. Objects


In a conventional programming language, a procedure typically performs
multiple operations and handles several items of data. For example, when
a user inputs a customer record in an accounts receivable system and
then executes a “save” command, a procedure might be invoked to
validate the dozen or more fields of information in the customer record.
What happens when the five-digit field for a postal code in an application
has to be changed to accommodate the six-character Canadian format?
Three sources of inefficiency become apparent immediately.

Application Developer’s Guide 61


Chapter 2 - Object Orientation

First, what amounts to a single conceptual change (modify postal code)


has to be programmed in two locations (database structure and
procedure code, as shown in part A of the illustration). Wouldn’t it be nice
if the data were somehow bound more tightly to the code, so that only
one system element had to be changed?
Second, there are likely to be multiple procedures that handle postal
codes—besides customer data maintenance, there may be supplier
maintenance, distributor maintenance, and so on (part B). In each such
procedure, the postal code validation routine has to be modified. In an
ideal system, such a change would affect all pertinent procedures
simultaneously.
Third, although only the portion of a procedure’s code pertaining to postal
codes is affected by the change, the entire procedure has to be scanned
by the programmer and recompiled (part C).

A
A B
A C
A
Recompile
Expand postal code Expand postal code validation routines

Customer I.D. No.


Database
Name
Supplier
Address
Procedure
Distributor
(Etc.)

Modifying zip code in procedural programs

Objects and Methods


There has to be a way to isolate the changes more intelligently. In an
ideal programming language, each field in the database would be a
separate entity for the purpose of changing its attributes. Each atomic
routine in a program would be a separate entity for the purpose of
maintaining the code. So we would have a set of atomic data elements
and a set of atomic procedures.

62 VisualWorks
Objects and Methods

It turns out that the procedures cluster very naturally around the data.
The procedure for validating a postal code is something that only the
postal code object needs to know. Likewise, only the address object
needs to know what its valid inputs are. So if we can make each data
object smart enough to perform the useful operations on itself, we no
longer need separate procedures at all.

A
A B
A C
C
Expand postal code Recompile

Postal code
object

Modifying postal code in Smalltalk

This simple strategy of making data smart is at the core of Smalltalk. An


application is no longer a collection of procedures that act on a database,
but a collection of data objects that interact with one another via built-in
routines called methods. The language is object-oriented rather than
procedure-oriented.
In fact, because Smalltalk variables are not statically bound to specific
data types, no change is required for client programs to be able to store a
string rather than an integer in a postal code.
To expand the definition of a postal code in Smalltalk, all you need to do
is broaden the postal code object’s validation routine. When another
object, such as the customer or supplier object, needs to know whether a
postal code is valid, it passes the proposed value to a postal code object,
which uses its built-in mechanisms to do the testing.

Application Developer’s Guide 63


Chapter 2 - Object Orientation

Composite Objects
Most objects are composite objects, being composed of several other
objects. For example, a customer object would contain identifying objects
such as customer number, name, address, city, state, postal code, and
telephone number. Why have a customer object at all? Because some
procedures have to be performed for a customer rather than a postal
code or a telephone number.

Financial Management System object

Accounts Receivable Application object

Account object

Customer object

Postal Code object

Hierarchy of Objects

The create command, for example, is best centralized up at the customer


level of abstraction, because it is an operation that affects all of the data
objects that make up a customer. What does that create operation consist
of? In our example, the customer object simply fires off the same
message to each member of its collection: “Here’s your input— validate it
and store it. Let me know if there’s a problem.”
Theoretically, the customer object would provide the customer-
identification part of an “account” object that handles requests related to
a customer’s account status. A collection of account objects would make
up the accounts-receivable system, itself an object that knows how to
answer questions about its collection of accounts. And the accounts-
receivable object joins an accounts-payable application and a general-
ledger application as parts of a financial-management package. Hence,
programming an application in Smalltalk consists of building a hierarchy
of objects. Another way of looking at it is that you’re creating a single
object (the application) that contains component objects, each of which
may contain smaller components, and so on. The figure above illustrates
a portion of such a hierarchy.

64 VisualWorks
Variables and Methods

Variables and Methods


An object typically is made up of one or more private variables (the data)
combined with a set of methods for manipulating that data. Each method
is a specialized subroutine.

An object containing
variables and methods

Variables Methods
zip getZip
setZipTo:
isValid

Zip Code Object

Variables and methods of an object

The two parts of an object are also known as state and behavior. The
values held by an object’s variables define its state. Its methods—what it
knows how to do—define behavior.
For example, a postal code object might have a variable called zip to hold
the postal code string. It needs at least two methods to be a civilized
object, as listed in the following table.

Method name Description


getZip Return a string containing the postal code
setZipTo: Replace the contents of the zip code variable with
the string that follows the colon

As you can see, each variable typically generates two accessing


methods, one for inquiry and one for update. Even a simple postal code
object will often have other methods. For example, it might have a method
called isValid, which checks to make sure the string conforms to a
recognized postal code format.

Application Developer’s Guide 65


Chapter 2 - Object Orientation

Method Names
The method name is used by other objects to select the operation defined
in a method. The method name is used when sending a message to
specify the requested operation. Accordingly, it is also called method
selector, a message selector, or simply a selector.
A message is sent by specifying a selector plus any argument values. We
frequently refer to, for example, “a getZip message,” meaning a message
selector plus arguments, if any.
The fundamental unit of any Smalltalk expression is an object reference
followed by a message, as in postalCode getZip. This expression asks the
postalCode object to return the value stored in its zip code variable.
Method names may contain letters, numbers, and underscores, but may
not begin with a number. When two or more words are combined to form
a name, as in this case, second and later initials are capitalized to
improve readability. This convention applies to all names in the system:
objects, variables and methods. All method names begin with a lower-
case letter.

Method Categories
It is not uncommon for an object to have dozens of methods. From class
to class, methods tend to cluster in recurring groups—for example,
objects that have data also have a set of methods for accessing the data.
Collectively, such methods are known as accessing methods. You may
encounter the phrase “accessing protocol,” which refers to the set of
methods for accessing data within an object.

Variables Methods
zip getZip
accessing protocol
setZipTo:
isValid testing protocol

postal code object

A message category is a convenient grouping of related methods, much


as a file folder holds related documents. The method editing tools, such
as the Package Browser and Class Hierarchy Browser, use categories to
help you search the code library.

66 VisualWorks
Classes and Instances

Classes and Instances


The question arises: How can there possibly be only one postal code
object that serves both a customer and a supplier when the real-world
customer and supplier might reside in different zip zones? For that matter,
each new customer might have a different postal code.
Obviously, there is a separate postal code object in each instance
because the values stored in the variables are different. On the other
hand, it would be silly to duplicate the postal code object’s methods for
each instance, so there must be one postal code object that is unique in
that it knows how a postal code ought to behave. The data-only object is
known as an instance; the method-holding object is called a class.
Class names may contain letters, numbers, and underscores, but may not
begin with a number. The first letter of a class name is capitalized, as are
all global variable names.
A class can be thought of as the object behavior affixed to a data
template. An instance is created by cloning the template so a new set of
variables can be stored. The ZipCode class has a template specifying that
each instance of ZipCode will have one variable named zip. Any given
instance of that class consists of a value for that variable.

Class Variables
A class can also have its own state values, which serve as system
constants. These states are stored in shared variables. For example, the
class Date has a shared variable called MonthNames, which stores an Array
containing names for the 12 months. Our ZipCode class might have a
shared variable called Formats, to store a collection of known formats. In
either of these examples, it would be wasteful to store a new copy of the
variable in every instance that is cloned from it because the value is
constant for all instances.
Like class names, shared variable names begin with a capital letter.

Class Methods vs. Instance Methods


If an instance doesn’t have its own copy of the methods on board, how
can it respond to messages? In a manner that is transparent to the
programmer, the system looks for the appropriate method in the class
from which the instance was spawned.

Application Developer’s Guide 67


Chapter 2 - Object Orientation

The expression zipCode getZip is equivalent to “ask the ZipCode class to


execute its instance method called getZip using the variables in the
instance called zipCode.” Thus, though each instance does not use up
unnecessary memory space by creating a copy of the instance methods,
the effect is the same.
A message can also be sent to a class, which is also an object. Each
class has two different sets of methods, one for itself and one for its
instances. When a class receives a message directly, it looks for the
corresponding method among its class methods.
Thus, the expression zipCode getZip executes an instance method that
returns the value of the instance variable. On the other hand, the
expression ZipCode formats causes a class method to be performed and
the value of a class variable (i.e., a constant) to be returned.

ZipCode (class)
Class Class
variables methods
Formats formats

aZipCode (instance)
Instance
Template Instance variables
for methods zip
instance getZip
variables setZipTo:
zip isUSZip
isCanadianZip

The parts of a class and an instance, and their interconnections

To summarize, the Smalltalk language consists of thousands of


subroutines called methods that are organized as a library of class
objects. The typical class object consists of class variables, class
methods, instance methods, and a template for instance variables.

68 VisualWorks
Class Inheritance

Class Inheritance
The class library is organized in a hierarchy of specialization, very much
like the taxonomy applied to the animal kingdom. At the root of the tree is
class Object. One kind of Object is a class called Magnitude. If you dig down
through a few more levels of specialization within the Magnitude
subhierarchy, you come to a class called SmallInteger. An instance of class
SmallInteger is an integer such as 3.
If you execute the expression 3 raisedTo: 4, the correct result (81) will be
returned. A raisedTo: message with an argument of 4 is being sent to 3,
which is an instance of SmallInteger. From the prior discussion about
instance methods, one would assume that the class SmallInteger has an
instance method called raisedTo:, but that is not the case.

Object
Magnitude
ArithmeticValue
Number
Integer
SmallInteger

Inheritance hierarchy for the SmallInteger class

Looking up a Method
Smalltalk provides a method-lookup mechanism that starts its search for
a given method in the obvious place—the class of the object to which the
message was sent. If no such method exists there, the method finder
climbs up through the hierarchy, stopping at each level to look for the
method. In our example, the method finder has to go up two levels, past
the Integer class to its parent, Number. There it finds the raisedTo: method.
SmallInteger is a subclass of Number, because it provides specialized
variables and/or methods. Number is a superclass of SmallInteger, as is the
class that sits between them in the hierarchy, Integer. Class Object is the
top-level superclass of all other objects.
The method finder has two ladders at its disposal, one for finding class
methods and the other for locating instance methods. As it climbs upward
through the superclasses, it uses only one ladder or the other, but not
both. Its choice of ladder is determined by the message recipient. If the
message is sent to an instance (3, in our example), only instance

Application Developer’s Guide 69


Chapter 2 - Object Orientation

methods are searched. A message sent to a class such as SmallInteger


would push the method finder onto the class-method ladder. The
expression SmallInteger raisedTo: 4 would cause a fruitless search resulting
in an error.

Object

Magnitude

ArithmeticValue

Number
raisedTo:

Integer

SmallInteger

instance class

The upward search path of the object hierarchy

Overriding an Inherited Method


An instance of any subclass of Number can respond to a raisedTo:
message, but that doesn’t mean they all use Number’s version of it. The
subclass Float, for floating point numbers such as 3847.029, has its own
instance method called raisedTo: because floating-point numbers require
a specialized algorithm for exponentiation. When the method finder goes
to work on the expression 3847.029 raisedTo: 4, it stops at class Float and
never gets as high as Number.
Inheritance also applies to variables. Thus, each class inherits all of the
methods and variables of its superclasses.
For example, the ApplicationModel class provides variables and methods
that support a mechanism for notifying dependent objects of a change in
state. This mechanism is inherited by all subclasses of ApplicationModel.
The Customer class that we mentioned earlier might well be created as a
subclass of ApplicationModel. Then, if we create a View that displays the

70 VisualWorks
Class Inheritance

values in the Customer object, the Customer inherits methods for keeping
that View in sync with the data changes. We don’t have to write any code
for such dependency coordination.

Abstract Classes
Some classes are designed only to provide inheritable features, and are
never meant to be instantiated. For example, the class Object, the ultimate
superclass of all other classes, has an empty template for instance
variables. This may seem odd considering that instance variables hold
the actual data. What would an instance of class Object hold as its nugget
of data? The answer is that Object is not intended to have instances. Its
behavior is inherited and used by its subclasses and their instances.
When a class is not intended to be used to create concrete instances, it is
called an abstract class. An abstract class is frequently useful as a
repository for variables and methods that are useful to two or more
classes, none of which is a logical subclass of the other. Another way of
looking at it is that the similarities shared by a group of objects are
squeezed up from their separate locations into a common superclass.
The postal code can serve as an example once again. Until now, we have
been trying to make a single ZipCode class handle two very different
postal code formats. Presumably, as the customer base expands, more
methods would have to be added to handle other postal systems.
Eventually, a plain old United States numeric zip code would have to be
stored in a class that had more irrelevant methods than relevant ones—
and that’s the sort of awkwardness this object-oriented technology is
supposed to avoid.
Let’s make ZipCode an abstract superclass, with two new subclasses:
USZip and CanadianZip. They can both inherit the zip variable and the
accessing methods (getZip and setZipTo:) as well as any class variables
and class methods. The isValid method must be re-implemented in each
of the subclasses, to handle their specific formats. The ZipCode class’s
version of isValid can then hand off the validation request to the
appropriate subclass. To Customer, Supplier and any other objects that
interact with ZipCode, the mechanism for finding out whether a zip code is
valid has not changed.
A subclass of an abstract class can be abstract itself. One might make
USZip abstract, for example, and create one subclass representing the
five-digit format (OldUSZip) and another for the hyphenated-nine-digit
format (SlowToBeAdoptedUSZip).

Application Developer’s Guide 71


Chapter 2 - Object Orientation

Choosing a Superclass
When you create a new class, choosing its superclass is an important
design decision. The choice is made easier when you employ an
architecture that has been proven in many diverse applications.

Object

Model View Controller

Data & processing Display Menu


Copy
Cut
Paste
Save

The containment hierarchy of the class library

The key to this architecture is to divide your application into two parts.
First develop the data structure and the attendant processing, then invent
the user interface. The user interface is further subdivided into input and
output modules. The data-and-processing module is referred to as the
model. The output module usually consists of the screen displaying
mechanisms—it’s called the view. The input module is called the
controller because it enables the user to control the sequence of events
by entering data and commands.
Not surprisingly, Smalltalk provides an abstract class as the intended
starting point for each of these three modules: Model, View and Controller.
Thus, the architecture is known as model-view-controller, or MVC,
programming. For detailed information about MVC design, see Chapter 8,
“Application Framework.”
We use the term “application” broadly here—an object as lowly as a
postal code can be regarded as a self-contained model that can have an
associated view (a box on the screen in which the postal code is
displayed) and controller (for accepting keyboard input to the model in the
form of data entry). This implies that an MVC application can be a

72 VisualWorks
Class Inheritance

component of a larger MVC application, and so on. That is indeed the


case, furthering the cause of reusability by segmenting any given
program into easily separated components. In this sense, a model-view-
controller triad is the fundamental unit of design just as an object is the
fundamental unit of implementation.
When you choose a superclass for a new class, you are selecting an
inheritance hierarchy—positioning the method finder’s ladder in the class
library, so to speak. Model, View, and Controller head three major
subhierarchies within the library. Your choice of superclass typically
resolves to a class within one of those subhierarchies, and often to the
head classes themselves.
Many of the user-interface components that have been layered on top of
Smalltalk to form VisualWorks are subclassed from Model, View or
Controller. The remaining classes are typically subclassed from Object,
because as linguistic elements they stand apart from the MVC
machinery.

Application Developer’s Guide 73


3
Syntax

Smalltalk has a very simple syntax, consisting of literals, variables,


messages, and block expressions. This simplicity makes Smalltalk syntax
easy to learn.
VisualWorks Smalltalk complies with ANSI standards for Smalltalk
syntax, but employs some extensions.
For an abstract description of the VisualWorks Smalltalk syntax in BNF,
refer to Appendix A, “VisualWorks Smalltalk Syntax Description”.

Literals
A literal is a Smalltalk expression that always refers to the same object.
This reference cannot change.
There are several kinds of literals in VisualWorks, including numbers,
characters, strings, symbols, arrays, byte array literals, and three special
literals: nil, true and false.
Note that literals are strongly typed, meaning that each is a full-blooded
object, an instance of a class, and so respond to the full protocol of their
class.

Numbers
Numbers are represented in the usual way, using a preceding minus sign
and embedded decimal point as required.
Integers
Integers are expressed as numeric literals such as 101, or as the result of
arithmetic operations involving one or more integers such as 55 + 46.

74 VisualWorks
Literals

Floating Point Numbers


Floating point numbers must have at least one digit to the left of the
decimal point, so the compiler can distinguish a decimal point from a
period used as an expression delimiter. Thus, 0.005 is legal, but .005 is
not. In scientific notation, the e is replaced by a d in a Double and a q for
quad-precision.
Fixed-Point Numbers
A fixed-point number is useful for business applications in which a fixed
number of decimal places is required. Fixed-point numbers are
expressed by placing the letter s after a literal integer or a floating-point
number. The number of decimal places preceding the s implicitly specifies
scale of the number (the number of decimal places to be preserved).
Note that an explicit scale takes precedence over an implicit one, so that
99.95s4 is the same as 99.9500s, while 99.9500s2 is an error.
Nondecimal Numbers
Number literals can also be expressed in a nondecimal base by prefixing
the number with the base and the letter r (for radix). For example:

Octal Decimal
8r377 255
8r34.1 28.125
8r-37 -31

When the base is greater than ten, the capital letters starting with “A” are
used for digits greater than nine. For example, the hexadecimal
equivalent of the decimal number 255 is 16rFF.
Numbers in Scientific Notation
Numbers can also be expressed in scientific notation by including a suffix
composed of e (for exponent) or d (for double-precision) plus the
exponent in decimal. Note that you can also use the letter q instead of d.
The q (quad-precision) is available for portability to other Smalltalk
systems, but in VisualWorks, q has the same effect as d.
The base is raised to the power specified by the exponent and then
multiplied by the number. For example:

Scientific Notation Decimal


1.586d5 158600.0
1586e-3 0.001586

Application Developer’s Guide 75


Chapter 3 - Syntax

Scientific Notation Decimal


8r3e2 192
2r11e6 192

Characters
A character literal is always prefixed by a dollar sign. For example:
$a
$M
$-
$$
$1

Strings
A string literal is enclosed in single quotes (double quotes are used to
delimit a comment). Any character can be included in a literal string. If a
single quote is to be included, it must be preceded by a single quote, as
in:
'I won''t fail'

Symbols
A symbol is a label that conveys the name of a unique object such as a
class name. There is only one instance of each symbol in the system. A
symbol literal is preceded by a number sign, and optionally enclosed in
single quotes. For example, #Float and #'5%' are legal symbols. If a
symbol is enclosed in an array, it must still be preceded by a number sign.

Byte Arrays
A literal byte array is enclosed in square brackets and preceded by a
number sign. Elements of the array must be integers between 0 and 255.
They are separated by one or more spaces. The result, as in the following
example, is an instance of class ByteArray:
#[255 0 0 7]

Arrays
An array literal is enclosed in parentheses and preceded by a number
sign. Elements of the array are separated by one or more spaces (extra
spaces are ignored). An array literal embedded in another array must still

76 VisualWorks
Literals

be preceded by a number sign. The following example contains a


number, a character, a string, a symbol and another array (of three
characters):
#(1586.01 $a 'sales tax' #January #($x $y $z))

Note: When you change an element in a nonatomic literal constant


(a String, an Array, or a ByteArray), the change is reflected globally. For
that reason, experienced Smalltalk programmers rarely pass a
mutable literal constant from one method to another, but pass a copy
instead.

Booleans
The boolean constant true is the sole instance of class True, and the
constant false is the sole instance of class False, both of which are
subclasses of Boolean. Unlike most instances, the values of true and false
are hard-wired in the compiler, which qualifies them as constants.
Even though they are constants, their behavior is defined in the instance
methods of the classes True and False, which implement boolean tests
and operations, such as ifTrue:, ifFales:, and:, or:, and not.
A Boolean value is seldom used directly, but is the return value of
comparison operations, and then used in branching control structures.
Refer to “Branching” in Chapter 5, “Control Structures” for more
information.

nil
The nil object is the sole instance of class UndefinedObject. As the class
name implies, nil is the null value given to variable slots that have not yet
been assigned a more interesting value. Like the booleans, nil is hard-
wired in the compiler. Its behavior is defined in UndefinedObject—for
example, it overrides the isNil method implemented by Object (answering
true instead of false).
It is expected that there is only one instance of nil in the system. Do not
create additional instances, even though this is possible using basicNew,
because this will cause VisualWorks to crash.

Application Developer’s Guide 77


Chapter 3 - Syntax

Variables
Objects are referred to by their names. Except in the case of literals,
objects are named by being assigned to a variable.
Variables are of two types, depending on their reference scope. Private
variables can be referenced only by a single object; they are private to
that object. Shared variables are accessible by multiple objects.

Variable Types
Unlike some other object-oriented environments, Smalltalk variables are
untyped, meaning that any variable can hold an object of any type.
Another way to say this, and perhaps better, is that Smalltalk variables
are dynamically typed. What makes this a better way to think of it is that
Smalltalk itself is strongly typed; everything in Smalltalk is a full-blooded
object, an instance of a class. There are no “primitive” types

Variable Names and Conventions


Variable names are made up of letters and digits, and may include the
underscore (_) character. A name must begin with either a letter or the
underscore.
Object names tend to be lengthy in Smalltalk, in comparison with most
other languages, to make the code more readable. For descriptive
purposes, a name is frequently made up of two or more words.
Convention dictates that the first letter of each embedded word is
capitalized. This convention is not enforced by the language or by any of
the development tools, but it does improve readability.
The following table provides conventions that apply to the first letter of a
variable names. In general, the initial capitalization indicates the
variable’s scope: upper-case for shared variables, and lower-case for
private variables.
Capitalization Conventions

Type of variable Initial capital Example

Argument variable No aString

Class instance variable No wordCollection

Class name Yes Date

Class variable Yes Location

78 VisualWorks
Variables

Capitalization Conventions

Type of variable Initial capital Example

Instance variable No year

Name space Yes Smalltalk

Shared variable Yes MaximumUsers

Temporary variable No aDate

In conformance with the ANSI standard, VisualWorks does not allow the
use of periods in identifiers. VisualWorks does, however, employ a
notational extension for referencing bindings (the primary referents of
shared variable, class, and name space names) that does use periods.
This notation provides a way for referencing a binding in terms of the
name space and/or class and/or shared variable in which it is defined.
Refer to “Binding References” on page 131 for more information.

Private Variables
A variables is an association between a name and a changeable value.
The variable’s name is used to reference its value within the variable’s
name resolution scope. VisualWorks Smalltalk has several kinds of
variables for various naming scopes. The following variables are “private,”
in the sense that they are accessible only to specific objects. Shared
variables are discussed later (see “Shared Variables” below).
Temporary Variables
A temporary variable is most often encountered in a method, where it
provides temporary storage for an argument or a calculated value. Its
lifetime begins when it is declaration is evaluated, within the method or a
block expression within the method, and ends when the block or method
finishes processing and returns control to the calling object. The naming
scope of the variable is the method or block in which it is declared, and is
inaccessible outside of that scope.
A temporary variable is declared by enclosing its name between vertical
bars. The declaration must follow the message definition, and usually
follows a comment explaining the method, but is otherwise the first part of
the method definition.

Application Developer’s Guide 79


Chapter 3 - Syntax

For example, the occurrencesOf: method for Dictionary is:


occurrencesOf: anObject
"Answer how many of the receiver's elements are equal to anObject."
| count |
count := 0.
self do: [:each | anObject = each ifTrue: [count := count + 1]].
^count
The third line declares the varible count, which is used as a counter. The
third line assigns its initial value, using the := assignment operator.
Temporary variables are free to change their values through the life of the
method, as is shown in the fourth line, which increments count.
Multiple temporary variables can be declared in the same declaration
expression, by including them between the vertical bars, with one or more
white-space characters (space, tab, etc.) separating each variable name.
For example:
| var1 var2 var3 |
would declare three temporary variables.
Argument Variables
An argument variable is a special kind of temporary variable, declared in
the signature of a binary or key-word method definition. The variables
take their values from the arguments passed with the message send.
For example, the class Time provides an instance method called
hours:minutes:seconds:, defined as:
hours: hourInteger minutes: minInteger seconds: secInteger
"Initialize all the instance variables."
hours := hourInteger.
minutes := minInteger.
seconds := secInteger
This method declares three temporary variables in its method signature,
italicized in the first line above, and names them hourInteger, minInteger
and secInteger.
When a client object sends this message to an instance of Time, which it
might refer to as aTime, appropriate integers are provided. For example:
aTime hours: 11 minutes: 42 seconds: 15
When the method is invoked, the supplied values are assigned to their
respective variables, so hourInteger is set to 11, minInteger to 42, and
secInteger to 15. Argument variables, unlike other temporaries, do not
accept new values by assignment, so these assignments do not change
during the life of the variables.

80 VisualWorks
Variables

As a convention, an argument temporary is named to indicate the object


type it is intended to hold (e.g., aSet, aString, anInteger). However, no
typing is enforced, and any object can be stored in any variable. Errors
might occur at runtime, if the method can’t handle the object provided.
Instance Variables
Instance variables hold data that is specific to an individual instance of a
class. The variable’s value describes a state or attribute of the instance.
An instance variable is created when the instance is generated, and
exists as long as the instance does. The name scope is the instance
itself, which is the only object that can reference the variable itself.
There are two kinds of instance variables, named and indexed. The type
of instance variable is specified for the class in the class definition (refer
to Chapter 4, “Classes and Instances” for more information).
Named instance variables are the most commonly used. The variables
are declared by naming them in the class definition, in a String argument
to the instanceVariableNames: keyword. Accordingly, every instance of the
class will have an instance variable with that name. For example, a
Customer class may define an instance variable firstName as follows:
[Link] defineClass: #Customer
superclass: #{[Link]}
indexedType: #none
private: false
instanceVariableNames: ' firstName '
classInstanceVariableNames: ''
imports: ''
category: 'Customer-Records'
Named instance variables are accessed by name in instance methods,
which either assign or retrieve a value from the variable. For example, in
Customer, an instance method would assign it a value using the usual
assignment syntax:
firstName := ‘Bruce’
and another method would retrieve its value simply by referencing its
name:
^firstName
Indexed instance variables are not named, but are accessed by an
integer index. All indexed instance variables for an object hold the same
kind of value, which are either arbitrary objects or byte values. The type
of value is specified in the class definition, which specifies the index type
as:

Application Developer’s Guide 81


Chapter 3 - Syntax

• #object, for arbitrary objects


• #byte, restricting values to byte values
• #weak, making the class a weak container class, like WeakArray
• #immediate, restricting values to immediate objects such as instances
of Character, SmallInteger, and Symbol
• #ephemeron, restricting values to instances of Ephemeron
If the class does not use indexed instance variables, the index type is
specified as #none. See “Class Types” in Chapter 4, “Classes and
Instances” for details.
Individual instances of a class may have different numbers of indexed
instance variables. Collections, for example, vary in size, and so use one
indexed instance variable for each member.
Indexed instance variables set up an association between an index
location and a value, and so are accessed using at: and at:put: messages.
For example, if names is an instance of Array, the first element in the array
is retrieved by sending the message:
names at: 1
To add a name at the fourth position, send the message:
names at: 4 put: ‘Bruce’
which stores the string ‘Bruce’ as the value of the fourth indexed instance
variable.
A class can define its instances as having both named and indexed
instance variables. For example, the class Set defines its instances as
having both indexed instance variables, which hold object values, and a
single named instance variable, as show in the class definition:
[Link] defineClass: #Set
superclass: #{[Link]}
indexedType: #objects
private: false
instanceVariableNames: 'tally '
classInstanceVariableNames: ''
imports: ''
category: 'Collections-Unordered'
The tally variable is used to record the number of elements in the set, and
the indexed variables hold the individual elements.

82 VisualWorks
Variables

Instance variables are inherited, so an instance has its own copy of the
instance variables declared by all of its superclasses. For example, the
class SystemDictionary is a subclass of Set, so it does not need to declare
its own tally variable because it can use the tally that is declared in its
superclass.
Class Instance Variables
A class instance variable stores data that varies with each subclass in a
hierarchy. It is declared as part of the class definition, and can only be
accessed by a class method.
For example, suppose you have an abstract LanguageDictionary class that
has methods for looking up words to verify spelling, etc. You give
LanguageDictionary a class instance variable named wordCollection. Now
you create a series of subclasses corresponding to the English language,
the Polish language, and so on. The EnglishLanguage class can initialize
wordCollection to hold English words. The other subclasses can initialize it
differently. Then when an instance of any subclass asks for wordCollection,
it gets the appropriate language-specific version.

LanguageDictionary class
wordCollection ()

EnglishDictionary class
wordCollection (‘aardvark’ ... )

PolishDictionary class
wordCollection (‘abak’ ... )

Class instance variable

The advantages of this approach are that you still only have to initialize
the wordCollection once for each subclass (unlike instance variables) and
all subclasses can reuse methods that employ a common variable name
(unlike class variables).

Shared Variables
A shared variable is a variable that can be shared, or referenced, by
multiple objects. In previous releases of VisualWorks, shared variables
included class variables, pool variables, and global variables. These
various variable types are unified as a single type, called simply a “shared
variable.”

Application Developer’s Guide 83


Chapter 3 - Syntax

A shared variable’s value is logically independent of any single instance


of an object. Unlike instance variables, in which each object holds its
individual state, and class instance variables, in which each class holds
its state, shared variables can be shared among multiple objects.
Shared variables are implemented as bindings, which are instances of
either class VariableBinding or its subclass InitializedVariableBinding.
Accordingly, we sometimes refer to “a binding,” and mean specifically an
instance of one of these classes, rather than in the more general sense of
a value assignment.
The value of a shared variable, or of the binding it refers to, is either a
name space, a class, or an arbitrary object. In the third case, they serve
the roles formerly served by globals, pools, and class variables.
When defining a shared variable, give careful consideration to where you
create it, based on the referential scope expected for the variable. For
example, if only a single class needs to reference the variable, define it in
a class, as a class variable. But if it is to be referenced by all objects in a
name space it is probably more appropriate to define it in the name space
itself, as a pool or “global” variable.
To define a shared variable, create a new category (protocol), and use
either the definition template, as described in the following sections, or
the New Shared Variable dialog, Class ­ New ­ Shared Variable....
Class Variables
A shared variable, when defined relative to a class, implements a class
variable.
Class variables are inherited by, and accessible to, the class itself, its
instances, its subclasses, and their instances. This is true even if the
classes are in different name spaces; explicit importing is not necessary.
For example, the class Date has a shared variable called MonthNames,
which stores an array containing names for the 12 months. It would be
wasteful to store the array in every instance that is cloned from it because
the names are the same for all instances. Instead, the array is defined
once in the shared variable. It is then accessible by instances of the class
Date and its subclasses, and by instances of any other class that imports
it.
To define a class variable:
1 In any system browser, select the class that will serve as the name
space for the variable, and select the Shared Variables tab.

84 VisualWorks
Variables

2 Select, or add and select, a category for the new shared variable, in
the methods/shared variables list pane. The shared variable definition
template is displayed in the code pane:
[Link] defineShared: #NameOfBinding
private: false
constant: false
category: 'category description'
initializer: 'Array new: 5'
3 In the template:
• Replace #NameOfBinding with a symbol specifying the shared
variable name, such as #MySharedObject.
• Set the private: field to true to make the variable private; otherwise,
leave it as false. (Refer to “Public and Private Shared Variables”
below.)
• Set the constant: field to true if the variable’s value should not be
changed; otherwise, leave it as false. (Refer to “Constant and
Variable Bindings” below.)
• Enter an initialization expression, as a String, in the initializer: field,
or enter nil. (Refer to “Initializing Shared Variables” below.)
4 Select Accept from the browser’s <Operate> menu to save the
definition and create the shared variable.
Your new shared variable is added to the list. It can be viewed in any
class browser by selecting the Shared Variables tab and its category.
Pool Variables
Shared variables can also be defined directly in name spaces (non-class
name spaces). For example, in the Graphics name space are defined a lot
of classes, and two further name spaces: SymbolicPaintConstants and
TextConstants. These name spaces exist solely as the name scopes for
collections of shared variables.
Each shared variable is defined directly in the name space. Initialization
values for the variables are provided either on the definition’s initializer:
line, as is done for most of the TextConstant variables, or in an appropriate
class initialization method, as is done for the SymbolicPaintConstants
variables.
For these variables to be accessed within a name space other than its
defining name space, the variable must be imported, usually by a general
import of its name space. (Refer to “Importing Bindings” on page 133 for
more information.)

Application Developer’s Guide 85


Chapter 3 - Syntax

You can define a pool by creating a name space, which is the pool, and
then adding shared variables to it using a series of at:put: messages.
Browse SymbolicPaint class method initializeConstantPool for an example.
A better approach is to define the pool name space, and then add shared
variables to it:
1 In the System Browser class/name space list, select the name space
that will contain the pool.
Select the most local name space that makes sense for the breadth
of availability appropriate for this shared variable.
2 Select Add ­ Name space from the browser’s Class menu. The name
space definition template is displayed in the code pane.
3 Complete the template, specifying the name of your pool as the name
space name. (Refer to “Creating Name Spaces” on page 126 for
completing this template.)
4 Select the pool name space, then pick Add ­ Shared Variable from the
browser’s Class menu. The shared variable definition template is
displayed in the code pane:
Smalltalk defineSharedVariable: #NameOfBinding
private: false
constant: false
category: 'As yet unclassified'
initializer: 'Array new: 5'
5 In the template:
• Replace #NameOfBinding with a symbol specifying the shared
(pool) variable name, such as #MySharedObject.
• Set the private: field to true to make the variable private; otherwise,
leave it as false. (Refer to “Public and Private Shared Variables”
below.)
• Set the constant: field to true if the variable’s value should not be
changed; otherwise, leave it as false. (Refer to “Constant and
Variable Bindings” below.)
• Provide an appropriate category: string.
• Enter an initialization expression, as a String, in the initializer:
field, or enter nil. (Refer to “Initializing Shared Variables” below.)
6 Select Edit ­ Accept in the browser to save the definition and create
the shared variable.

86 VisualWorks
Variables

At this point the pool variables are all defined and initialized. You may
which to edit the definitions, however, to make the variables private or
constant, or to change.
To see your new shared variables, open a System Browser, select the
Shared Variables tab, select the pool’s super-name space in the name
space list, select the pool name space in the class/name space list, and
select a category.
As Global Variables
Globals are seldom used in VisualWorks, having been largely replaced
by pool variables. Even before VisualWorks 5i, only a few “system
globals” such as Transcript and Processor have remained in the system. In
general, they are a bad practice in object-oriented programming, because
they break encapsulation, and so are to be avoided.
Instead of globals, these remaining system objects are defined as shared
variables in a name space that is almost certainly accessible to all name
spaces. Transcript, for example, is defined as a shared variable in the
[Link] name space.
To browse these definitions, examine the Smalltalk name space in the
System Browser, and then select Core in the class/name space list, and
browse the shared variables. You can do a search for Transcript using the
browser’s built-in search mechanism (upper-right corner of the tool).
The resulting shared variables aren’t truly “global” to the system, since it
is easy to define a name space that doesn’t import Core.
To define a shared variable:
1 In the System Browser, select a name space in the class/name space
list to be the super-name space.
Select the most local name space that makes sense for the breadth
of availability appropriate for this shared variable. For the widest
availability, select the Smalltalk name space.
2 Select Add ­ Shared Variable from the browser’s Class menu. The
shared variable definition template is displayed in the code pane:
Smalltalk defineSharedVariable: #NameOfBinding
private: false
constant: false
category: 'As yet unclassified'
initializer: 'Array new: 5'
3 In the template:

Application Developer’s Guide 87


Chapter 3 - Syntax

• Replace #NameOfBinding with a symbol specifying the shared


variable name, such as #MySharedObject.
• Set the private: field to true to make the variable private; otherwise,
leave it as false. (Refer to “Public and Private Shared Variables”
below.)
• Set the constant: field to true if the variable’s value should not be
changed; otherwise, leave it as false. (Refer to “Constant and
Variable Bindings” below.)
• Provide an appropriate category: string.
• Enter an initialization expression, as a String, in the initializer:
field, or enter nil. (Refer to “Initializing Shared Variables” below.)
4 Select Edit ­ Accept in the browser to save the definition and create
the shared variable.
To see your new shared variable, open a System Browser, select the
Shared Variables tab, select the variable’s super-name space in the name
space list, select its name space in the class/name space list, and select
its category.
Class and Name Spaces Names
In VisualWorks, both class and name space names refer to shared
variables whose values are classes and name spaces, respectively.
Because of their special roles in the system, these are covered
separately in later chapters.
Constant and Variable Bindings
Sometimes it is desirable to set the value of a shared value and have it be
immutable, or constant. The constant: field in the shared variable definition
provides this option.
When set to false, the variable can be set and initialized by the usual
means by any object in the system. (Refer to “Initializing Shared
Variables”). When set to true, however, the value cannot be changed by
the usual means.
For constant shared variables (which sounds odd, but they are still
variables), changing the value requires rerunning the initializer, and so
the variable is essentially protected from a runtime value change. The
value is, for all intents and purposes, constant. Even a class initialization
method that sets the variable will fail.

88 VisualWorks
Variables

Note that you can change a shared variable’s definition, and so change it
from being variable to being constant. If you do so, be aware that
methods that set the variable will now fail.
Public and Private Shared Variables
Most Smalltalk dialects lack an enforceable distinction between public
and private classes and methods. Variables have traditionally been either
private (instance, class, and class instance variables) or public (global
and pool variables), depending on the kind of variable.
VisualWorks uses name spaces and shared variables provide a way to fill
some of this lack, by allowing you to control imports at two levels:
definition and import.
At either its creation or when imported, a shared variable can be declared
to be either public or private.
• If a binding is public, it is available for import by a name space or
class.
• If a binding is private, it is not available for import by a name space or
class.
Refer to “Importing Bindings” on page 133 for more information on
importing.
Defining a Binding as Private or Public
At one level, in its definition, each individual class, name space, and
shared variable is declared as either public or private by setting the
Boolean argument to the private: field. When set to false the binding is
public, and so can be imported. When set to true the binding is private,
and cannot be imported. At this level, privacy or publicity is set for the
object itself, and so is absolute.
So, for example, a shared variable that is defined in MyNameSpace and
declared as private is accessible only in the scope of MyNameSpace, and
cannot be imported by any name space or class. It is hidden from
anything that imports MyNameSpace.
Name spaces and classes are usually defined as public, since they
should be imported by name spaces that need to access them. Pool
variables also should be defined as public, since they also are meant to
be imported. Class variables, shared variables that are defined within the
scope of a class, are also usually defined as public, so they can be
accessed by the class’s subclasses, and their instances.
Defining a name space, class, or general shared variable as private is the
exception, but an option if appropriate.

Application Developer’s Guide 89


Chapter 3 - Syntax

Initializing Shared Variables


There are a variety of ways to initialize a shared variable.
If you specify an initialization string in the shared variable’s definition, to
initialize the variable either:
• select the variable in a browser, and then select Shared Variable ­
Initialize in the <Operate> menu (or in the Method browser menu), or

• send the initialize method to a binding reference of the variable, for


example:
#{[Link]} initialize
These initialization methods work whether the variable is declared
constant or not (whether the constant: field is true or false).
In the case of class variables and pool variables, initializing shared
variables is frequently done as part of class initialization. In this case, the
value is set in the class initialize method, or in a method called by initialize.
For example, the Dummy class initialize method may simply set a value to
a shared variable (DummyShared) defined in the class, like this:
initialize
"Dummy initialize"
DummyShared := String fromString: ' a b c d e'.
Note that to initialize a shared variable in a method, the variable must not
be set as constant; the constant: field must be set to false.

Assigning a Value to a Variable


The default value for any variable is the nil object. To assign a new value
to a variable, use the assignment operator := (a colon followed by an
equal sign), as in the expression:
prompt := 'Enter your name'
The expression on the right-hand side of the assignment can be any legal
Smalltalk expression. The following examples are all valid assignment
expressions. They have the effect of creating an array of ice cream flavors
and selecting one of those flavors at random:
flavors := #('chocolate' 'vanilla' 'mint chip').
index := (Random new next)* 3.
flavorChoice := flavors at: index truncated + 1
Assignments can be chained when two or more variables are to store the
same value, as in:
majorLoopCounter := minorLoopCounter := 1

90 VisualWorks
Variables

Chained assignments should only be used with literal or read-only


values—otherwise, updating one variable has the side effect of changing
the value of the other variable similarly.

Special Variables
For three special variables, the value changes according to the execution
context but cannot be changed by assignment: self, super, and thisContext.
The most prevalent of these special variables is self, which holds a
reference to the object that is executing the current message.
In the simplest case, self merely allows the programmer to direct a new
message to the specific instance that is executing the current method. In
effect, an object can execute another of its own methods. A hypothetical
doSomething method could use a computeX method to calculate a number,
for example, with the expression self computeX.
A more complicated case arises when inheritance is involved. Suppose
the doSomething method is located in the superclass of the object that
received the doSomething message. But computeX is implemented by the
subclass. How do we send the method finder back to the bottom of the
ladder to search for computeX, rather than just starting from its superclass
location?

doSomething method
aSuperclass
self computeX

anObject computeX method

anObject doSomething

The special variable self is a pointer to the object (in this case, anObject)
that received the message being executed (doSomething)
The surprising but pleasing answer is that the expression self computeX
still works. The new message (computeX) is directed at self, which refers to
the object that received the previous message (doSomething).
It’s important to remember that self does not necessarily point to an
instance of the class whose method is being executed. In our example,
self is used in the parent’s method but it refers to the child. Thus, using
self in a method automatically provides for downward growth in the
hierarchy.

Application Developer’s Guide 91


Chapter 3 - Syntax

The super variable is very similar to self, except super tells the method
finder to begin its search one level above the executing method in the
class hierarchy. The receiver is the same as for self, namely the sending
object. This is useful when a subclass wants to add operations to its
parent’s method without having to duplicate the parent’s code. Note that
super is in the nature of a qualifier applied to the method finder, so it
cannot be assigned to a variable (as self can).

aSuperclass computeX method

getX method
anObject
super computeX

anObject getX

Special variable super

The third special variable, thisContext, is a reference to the stack context


of the current process. While self and super are commonly used by
Smalltalk programmers, thisContext is rarely needed by application
developers. It is used by the system’s exception handler and debugger.

Note: In some of the literature on Smalltalk, self and super are


referred to as pseudovariables. However, other objects have also
been called pseudovariables, so the term is ambiguous—we call
them special variables instead.

Undeclared Variables
When a variable is deleted while references to it still exist, or a reference
to a variable is loaded (by a parcel or package) but never declared, its
name is entered in the Undeclared name space. This name space is
maintained by the system and need not concern you under normal
circumstances— but it can provide useful clues to certain kinds of
program errors.
To inspect the contents of Undeclared, select in the Launcher Browse ­
Global, and enter undeclared in the promptor. This opens a Namespace
Inspector on the name space.

92 VisualWorks
Message Expressions

Message Expressions
A message expression is the fundamental unit of programming in
Smalltalk. It has three kinds of components: a receiver, a method name,
and zero or more arguments. In 9 raisedTo: 2, the receiver is 9, the method
name is raisedTo:, and the argument is 2. The term message technically
refers to the method selector and arguments, while a message
expression includes the receiver.
Every message returns an object to the message sender. In the example
just given, the raisedTo: method returns an instance of SmallInteger—
specifically, 81. There are three ways to denote the object to be returned
from a method:
• By default, the message receiver (self) is returned to the sender.
• A return operator (^, entered as <Shift-6> on most keyboards)
preceding a variable name causes that object to be returned. For
example, the expression ^anObject causes anObject to be returned.
• A return operator preceding a message expression returns the value
of that expression. For example, the expression ^3 + 4 causes the
object 7 to be returned.
A period is used to separate message expressions. No period is
necessary after the final expression in a series.
There are three types of message: unary, binary, and keyword
expressions. In addition, two or more messages can be joined in
sequence. Each of these constructs is described below.

Unary Messages
A unary expression has a receiver and a method name but no argument.
The following are all unary expressions:
1.0 sin. "Returns the sine of 1.0."
Random new."Returns a random number generator."
Date today. "Returns today's date."

Binary Messages
A binary expression uses a special character, such as a plus sign ($+), as
its method name and takes one argument. Some binary selectors are
combinations of two special characters, such as the comparison selector
>= (greater than or equal to). The characters that allowed in a binary
selector and the construction rules for a binary selector are specified
precisely in Appendix A, “VisualWorks Smalltalk Syntax Description”.

Application Developer’s Guide 93


Chapter 3 - Syntax

The most common binary messages have to do with arithmetic


operations, comparisons, and string concatenation. The table below
describes many of the commonly used binary selectors. One or more
white-space characters before and after the selector are optional.
Common Binary Method Selectors
Selector Example Description
+ counter + 1 Add
- 100 - 50 Subtract
* index * 3 Multiply
/ 1/4 Divide
** 4 ** 3 Raised to
// 13 // -2 Integer divide (round the quotient to the
next lower integer; in the example, -7). An
instance of Point can also be rounded via
this operator.
\\ 13 \\ -2 Modulo (return the remainder after division;
in the example, -1).
< counter < 10 Less than
<= index <= 10 Less than or equal
> clients > 5000 Greater than
>= files >= 2000 Greater than or equal
= counter = 5 Values are equal
~= length ~= 5 Values are not equal
== x == y Same object (receiver and argument are
the same object or point to the same
object)
~~ x ~~ y Not the same object
& (x>0) & (y>1) Logical AND (return true if both receiver
and argument are true, otherwise false).
| (x>0) | (y<0) Logical OR (return true if either receiver or
argument is false).
, 'abc','def' Concatenate two collections.
@ 200 @ 300 Return an instance of Point whose x
coordinate is the receiver and whose y
coordinate is the argument.

94 VisualWorks
Message Expressions

Common Binary Method Selectors (Continued)


Selector Example Description
-> #Three -> 3 Return an instance of Association whose
key is the receiver and whose value is the
argument.
<< #All << #labels Create a UserMessage
>> #All << #labels >> 'All' Assign a catalog ID to a UserMessage

The second character of a two-character selector cannot be a minus sign


( $- ). The other permitted characters are: $+, $/, $\, $*, $~, $<, $>, $=, $@,
$%, $|, $&, $?, $!, and $,.
Note that the assignment expression ( := ) is not a method selector. Also,
the linking symbol (>>), as used in the debugger and browsers to refer to
a method and its implementing class (for example, Set>>size to refer to the
Set instance method size), is not a binary selector.

Keyword Messages
A keyword expression has a receiver, one or more argument descriptors
(keywords), and one argument for each keyword. Each keyword ends in a
colon. The following are valid keyword expressions:
aDate addDays: 5 "Add five days to aDate."
anArray copyFrom: startIndex to: stopIndex
"Return a copy of that portion of anArray
that begins at startIndex and ends at stopIndex."
When there is more than one keyword, the method name is formed by
concatenating the keywords. In the second example above, the method
name is copyFrom:to: (formally pronounced “copyFrom colon to colon”).
There is no limit on the number of keywords in a method name.

Pragmas
Pragmas are a special method syntax that simplify specifying some
operations. They are specified with a syntax:
< pragmaKeyword: ... >
You may see these as you browse code.
One set of pragmas you may find useful are for defining menu items.
Refer to the GUI Developer’s Guide for information on these.

Application Developer’s Guide 95


Chapter 3 - Syntax

C programmers will need the c: pragma, which is supported by the DLL


and C Connect add-in, as described in the DLL & C Connect User’s
Guide.
A general mechanism for defining pragmas is provided in the Pragma
class.

Messages in Sequence
Frequently, the receiver of a message is the object returned by the
previous message expression. To avoid creating a temporary variable to
store the returned object, you can create a sequence of messages. For
example, the first and second expressions below can be compressed into
the form of the third expression:
interest := principal * interestRate.
principal := principal + interest.

principal := principal + (principal * interestRate).


This technique reduces the wordiness of the code, though sometimes at
the expense of readability. Parentheses can be inserted, as shown in the
example, to improve the readability and to assure that the intended
parsing order is followed.

Cascading Messages
When two or more messages are to be sent to the same object, a
semicolon can be used to cascade the messages. This avoids having to
repeat the name of the receiver, though frequently at the expense of
readability. For example, the first set of expressions below has the same
effect as the final expression, in which the messages are cascaded:
Transcript show: 'This is line one.'.
Transcript cr. "Carriage return."
Transcript show: 'This is line two.'.
Transcript cr.

Transcript show: 'This is line one.'; cr;


show: 'This is line two.'; cr

Parsing Order for Messages


When two messages have the same parsing precedence, parentheses
are sometimes required. For example, 3 + 4 * 5 is very different from
3 + (4 * 5) because binary selectors are all evaluated from left to right.

96 VisualWorks
Message Expressions

Parentheses are also necessary when a keyword expression is in the


argument expression for another keyword expression. For example, the
first expression below is valid but in the second version the method
selector is interpreted by the compiler as readFrom:on:, which does not
exist.
Time readFrom: (ReadStream on: '10:00:00 pm').
Time readFrom: ReadStream on: '10:00:00 pm'. "WRONG"
The following rules summarize the parsing order:
1. Parse parenthesized expressions before nonparenthesized
expressions.
2. Parse multiple unary expressions left to right.
3. Parse multiple binary expressions left to right.
4. Parse unary expressions before binary expressions.
5. Parse binary expressions before keyword expressions.
The result of the following code fragment is that a number is printed in the
System Transcript—can you trace the logic using the rules above?
| aSet nbr |
nbr := 207.
Transcript show: (aSet := Set new add: nbr + 3 * 5 sin) printString
In the first line, two temporary variables are declared. In the second line,
one of the variables is assigned the number 207. In the third line, the
following sequence of events takes place:

1. Set new Create an instance of Set.


2. 5 sin Calculate the sine of 5 (-0.958924).
3. nbr + 3 Add 3 to nbr (210).
4. ... * .... Multiply 210 by -0.958924 (-201.374).
5. .. add: ... Add -210.374 as an element in the Set created in
Step 1.
6. aSet := Assign the Set to the variable aSet.
7. ... printString Convert the Set to a printable string.
8. Transcript show: Output the printable string to the Transcript.

Application Developer’s Guide 97


Chapter 3 - Syntax

Block Expressions
A block expression represents a deferred sequence of operations. Blocks
are used in several contexts, including control structures, exception
handling, and finalization. The syntactic characteristics of block
expressions are described here.
A block expression is enclosed in square brackets, as in:
[index := index + 1.
anArray at: index put: 0]
The messages inside the block are not sent until the block object receives
the unary message value. The following expressions have the same
effect:
index := index + 1.
[index := index + 1] value.
Up to 255 separate arguments can be passed to a block. Argument
names must be listed just inside the opening bracket. Each argument
name must be preceded by a colon. The final argument name must be
followed by a vertical bar. For example:
[:counter | counter := counter + 1]
The argument variables are private to the block. The values of the
arguments are passed by using variants of the value message. There are
four variants, to be used depending on the number of arguments:
value: anObject
value: anObject value: anObject
value: anObject value: anObject value: anObject
valueWithArguments: anArray
Passing an argument to the example above would be arranged thus:
[:counter | counter := counter + 1] value: 3
Temporary variables can also be declared within a block. They must be
enclosed in vertical bars and placed after the vertical bar that separates
argument variables. They are local to the block.
The full syntax for a block is as follows:
[ :arg1 :arg2 |
| temp1 temp2 |
statement1.
statement2.
...]

98 VisualWorks
Formatting Conventions

Formatting Conventions
The compiler ignores tabs, carriage returns, and extra spaces. Formatting
conventions vary but readability favors the following guidelines:
1. Start the message definition at the left margin and indent all other
contents of the method by one level.
2. Leave a blank line beneath the method comment and as a separator
between sections of a long method.
3. Follow each period that ends an expression by a carriage return.
4. Indent as needed to visually identify each subordinate section of
code.
The code browser provided with VisualWorks Smalltalk provides a format
command for automatically applying these rules.

Application Developer’s Guide 99


4
Classes and Instances

Every object in VisualWorks is an instance of some class (including


classes themselves). Instances have a message interface, which
describes the messages, or operations, that an object will perform. The
class defines the behavior for that message, or how the operation is
performed. The set of messages understood by an object is referred to as
the object’s protocol or message category.
In this chapter we describe how to define a class and its methods,
including how to generate an instance of a class.

Defining a Class
A class is defined in a name space, as the value of a shared variable in
that name space. The variable is defined as “constant,” so the name of
the class cannot easily be changed.

Creating a Class using the New Class Dialog


The New Class dialog provides an easy to understand interface for
creating a class. Select New ­ Class in a system browser’s Class menu, to
open the New Class dialog:

100 VisualWorks
Defining a Class

The class definition properties are on two pages: Basic and Advanced. A
“Caution” icon (yellow triangle with an exclamation point) is displayed next
to any required field that lacks legal value.
The Basic properties are:
Package
The name of the package in which to create the class. The package
must already exist in the system. To define the class unpackaged,
select (none).
Name Space
The name space in which to create the class. The name space
determines the referential scope of the class name.
Name
The name for the class being created. There is no default. The name
must be new and unique in the specified name space, and must
begin with an uppercase letter.
Superclass
The name of the superclass, in literal binding reference (dotted
name) notation, as shown (see “Binding References” in Chapter 6,
“Name Spaces”).
Instance Variables
A space separated list of instance variable names.

Application Developer’s Guide 101


Chapter 4 - Classes and Instances

Create Methods
Three check boxes specify which, if any, stub methods are created in
the class authomatically when the class is created. The methods
generally need to be edited to provide the desired behavior.
Accessors, if checked, creates get and set accessor methods for each
instance variable specified.
Initializer, if checked, creates an initializer method with lines setting the
initial values of each instance variable specified.
Subclass responsibilities, if checked and if any of the superclasses define
methods marked as #subclassResponsibility, creates stub methods in
the new class for all of those methods. Initially, the stubs will signal an
error when evaluated, so you need to replace their bodies with
appropriate implementations.
The Advanced properties are:
Private
If checked, makes the class unavailable for import by another class or
namespace (see “Public and Private Shared Variables” in Chapter 3,
“Syntax”).
Indexed Type
This field specifies the class type, and particularly the type of value
that can be held by its indexed variables. See “Class Types” below for
descriptions of the types.
Class Instance Variables
A space separated list of instance variable names (see “Class
Instance Variables” in Chapter 4, “Classes and Instances”).
Imports
A list bindings to import (see “Importing Bindings” in Chapter 6,
“Name Spaces”).
When the dialog values are set, click OK to define the class and any
specified methods.
Note that class variables are not declared in the class definition, but are
created as shared variables in the class name space. Refer to “Class
Variables” in Chapter 3, “Syntax” for more information.

Creating a Class from a Template


You can also define a class by completing a class definition template,
which is a message send to a name space. To display the template in a
browser code view, deselect all items in the class list:

102 VisualWorks
Defining a Class

Smalltalk defineClass: #NameOfClass


superclass: #{NameOfSuperclass}
indexedType: #none
private: false
instanceVariableNames: ‘ instVarName1 instVarName2 '
classInstanceVariableNames: ''
imports: ''
package: ' NameOfCategory '
Complete the class definition by replacing the message receiver and
keyword arguments as follows (look at any class definition in the system
for examples):
• The message receiver is the name space in which the class will be
created. The default is Smalltalk.
• Provide the name for your class as a symbol literal (see “Symbols”)
following defineClass:. The name must begin with an upper-case letter.
• Identify the superclass in the superclass: field using the literal binding
reference notation shown (see “Binding References”).
• The indexedType: field is filled based on the class type you selected
(see “Class Types” below).
• Set private: to true to make the class unavailable for import by another
class or namespace (see “Public and Private Shared Variables”).
• Enter any named instance variable names as a space-delimited
string following the instanceVariableNames: keyword (see “Instance
Variables”).
• Enter any class instance variable names following the
classInstanceVariableNames: keywork (see “Class Instance Variables”).

• Following imports: list, in a white-space delimited String, any bindings


you want to import, or make freely available to this class (see
“Importing Bindings”).
• Enter a package name in the package: fields. If it is a new package, it
will be created.
When the definition is correct, Accept it (Edit ­ Accept).
Note note that the template does not give the option of generating
methods, but the system browser does provide for generating accessor
methods in the Class ­ (Class) Instance Variables ­ Create Accessors
command.

Application Developer’s Guide 103


Chapter 4 - Classes and Instances

Editing a Class Definition


When a class is created, its definition is represented as a message send
to a name space. The definition is displayed in the source code view of
the system browser when the class is selected, but no method categories
or methods are selected. The definition looks like this:
Smalltalk defineClass: #MyClass
superclass: #{[Link]}
indexedType: #none
private: false
instanceVariableNames: 'oneVar twoVar threeVar more '
classInstanceVariableNames: ''
imports: ''
package: 'MyStuff'
To modify a class definition, you can edit the values in the code view and
save the definition. The arguments are as described above, in “Creating a
Class from a Template”. The class is then recompiled. This is common,
for example, to add and remove instance variables during development.
Do not attempt to rename a class or move it to another name space or
package by editing the class definition. Instead, use the appropriate
menu command; either Class ­ Rename or Class ­ Move.

Class Types
Classes are of different types, determined by the value of the Indexed
Type in the definition. The permissible types are as follow:
#none
A class with zero or more named instance variables (possibly
inherited) and no indexed variables (e.g., True, Point). Can have any
kind of subclass.
#objects
A class of indexable object with zero or more named instance
variables and whose indexed variables hold arbitrary objects (e.g.,
Array, OrderedCollection). Subclasses can be either #objects or #weak),
since subclasses must also be object-indexable.
#bytes
A class of byte indexable object with no named instance variables
and whose indexed variables hold only byte objects (e.g., ByteString).
Indexed variable contents are defined by the at: and at:put: primitive
methods defined in the class defines, providing one and two-byte
character strings, byte and word arrays, etc. A #bytes class cannot
inherit named or indexed instance variables, because the instances
contain only raw binary data. Consequently a #bytes class can only

104 VisualWorks
Locating a Class by Name

inherit from a chain of #none classes with no named instance


variables. Subclasses must also be #bytes classes, because they
must also be byte-indeable.
#immediate
A class of immediate object, an object whose class and value are
encoded directly in the pointer to that object, (e.g., SmallInteger,
Character). An immediate class annot inherit named or indexed
instance variables, because the instances do not have room for
instance variables. Consequently, immediate classes can only inherit
from a chain of #none classes with no named instance variables. Also,
immediate classes cannot have subclasses, because there is no way
to differentiate instances of the subclass in the immediate
representation.
#ephemeron
A class with one or more named instance variables (possibly
inherited) and no indexed variables (e.g., Ephemeron). The first
instance variable is treated specially by the garbage collector.
Consequently, an #ephemeron class must inherit from a chain of #none
classes. Subclasses can only be type #ephemeron.
#weak
A class of object-indexable objects with zero or more named instance
variables and weak indexed variables containing objects (e.g.,
WeakArray). The indexed variables are weak, so do not prevent their
referents from being garbage collected. Consequently, a #weak class
must inherit only from a chain of #none or #objects classes.
Subclasses can only be weak-object indexable (#weak), because
subclasses must also be weak-object indexable.

Locating a Class by Name


Because name spaces allow for multiple classes with the same name, it
is rarely appropriate to ask for a class’s name using the name message,
particularly if that name is being used as a unique identifier. It is also not
appropriate to ask for a class using Smalltalk at: aSymbol, as had been
common in earlier releases.
Instead, use one of the following:
fullName
Returns a fully qualified name.
printString
Returns a String representing the class name.

Application Developer’s Guide 105


Chapter 4 - Classes and Instances

fullyQualifiedReference
When sent to a class or name space, returns a fully quallified name
computed compute from a binding reference (see “Binding
References” in Chapter 6).
asQualifiedReference
When sent to a String or Symbol, returns a binding reference.
For example:
| bindingReference |
bindingReference := stringOrSymbol asQualifiedReference.
bindingReference
ifDefinedDo: [:theClass| theClass ...statements... ]
elseDo: [self error: 'no class named ' , stringOrSymbol].
And, instead of:
Smalltalk at: stringOrSymbol
use:
stringOrSymbol asQualifiedReference value

Working with Instances


Creating an Instance
Smalltalk objects, or instances, are typically generated by sending the
message new to the class, possibly in conjunction with other messages:
MyClass new
If the class has indexed instance variables, the number of variables is set
by sending the new: message with an integer argument for the number of
indexed variables:
MyClass new: 5
These messages, new and new:, are defined in Behavior, and are inherited
by all classes.

Destroying an Instance
Garbage Collecting
In general, there is no reason to explicitly destroy an instance, because
Smalltalk employs garbage collection. When an object no longer has any
other object pointing to it (e.g., holding it in a variable), the system detects
that it is no longer needed, and automatically destroys the instance,
reclaiming the memory and resources.

106 VisualWorks
Working with Instances

Finalization
In some cases, such as if an object uses external resources, garbage
collection is not sufficient. In these cases, use the VisualWorks
finalization features (refer to Chapter 13, “Weak Reference and
Finalization”).
Lingering Instances
It is also possible to have “memory leaks,” caused by an instance that is
not fully released, and so cannot be garbage collected. To find these, look
for unusual memory usage on a per-class basis. Load the AT System
Analysis Parcel, and open the Class Reporter by selecting Tools ­
Advanced ­ Class reports in the Launcher. On the Space page, select the
suspect class, click the Instance size radio button, and click Run. Run this
both against your image and a clean image to identify classes with
possible garbage. Then, send allInstances to the class and inspect them.
Use the Inspector's Utils ­ Reference path command to trace back to a root
holding onto the object. Potential roots are:
• Object classPool at: #DependentsFields
• Object classPool at: #EventHandlers
• ObjectMemory dependents
• sysOopRegistry

Immutable objects
Several objects are “immutable,” meaning that their internal state cannot
be changed. Instances of SmallInteger, Character, and Symbol have always
been immutable in Smalltalk.
VisualWorks, beginning with version 7, extends the notion of immutability.
In addition to the objects listed above, all literals and general instances of
Number are now immutable. Also, facilities have been added to make
individual objects immutable. Except for instances of SmallInteger,
Character, and Symbol, objects which are immutable may be made
mutable.
This change provides several advantages:
• additional language safety by making literals immutable
• debugging aid, by catching where an object is assigned-to
• for persistence, where attempts to modify are caught, retried and the
updated objects written to persistent storage

Application Developer’s Guide 107


Chapter 4 - Classes and Instances

Attempts to modify an immutable object, such as by sending become:,


changing a character in a Symbol or String literal, or changing the class of
immutable objects will raise a NoModificationError exception.
The repercussions of this change can be quite extensive. However,
updating old code to deal with the new restrictions is straight-forward. In
many cases, such as String or Array literals, copy returns a mutable copy
of the object. For example,
Failure:
'' writeStream nextPutAll: 'abc'
Success:
'' copy writeStream nextPutAll: 'abc'
Success:
String new writeStream nextPutAll: ‘abc’
This does not work for Booleans, general instances of Number, or
immediate objects.
You can test for and control the mutability of objects using the following
protocol:
asImmutableLiteral
Returns the receiver as an immutable literal if it can be represented
as a literal.
beImmutable
Makes the receiver immutable.
beMutable
Makes the reciever mutable, except in the case of immediate objects
such as Character, SmallInteger, and Symbol.
isImmutable
Answers true if the receiver is immutable; false otherwise.
isImmutable: aBoolean
Makes the receiver immutable if aBoolean is true, or mutable if aBoolean
is false. Does not apply to immediate objects.
isImmutableLiteral
Answers true if the receiver is an immutable literal; false otherwise.

108 VisualWorks
Methods

Methods
Methods define the behavior of classes and their instances. This is where
the real “programming” takes place in Smalltalk. Methods are the same
as what are often called “functions” in other environments, such as Java
and C++.
You create methods using the System Browser, and completing the
method definition template. You can also use an existing method as your
template.
There are two kinds of methods: instance methods and class methods.
Instance methods specify behavior for messages sent to instances, and
class methods specify behavior for messages sent to the class itself.
Class methods are most often used for creating an instance of the class
and for initializing and accessing class variables.
To promote reusability, keep Smalltalk methods short. For example, you
can usually break a long method into smaller methods to isolate
individual services that other clients may want to use. Similarly, when a
subset of the code is repeated in a large method with only minor
variations, you can usually make that subset into a separate method.
Method names may contain letters, numbers, and underscores, but may
not begin with a number. The first letter should be lowercase.

Creating a Method
1 In a System Browser, select either the instance or class radio button.
2 Select the class for this method.
3 Select the message category or add a new one.
4 Fill in the method template.
You must provide a method name, which is the message selector and
argument names, in the first line of the definition. Next, you should
include a comment briefly describing what the method returns. Then,
enter a sequence of Smalltalk expressions (see Message
Expressions) specifying the processing behavior of the method.
5 Select Accept command in the code view <Operate> menu to save the
method. The method is then compiled.

Application Developer’s Guide 109


Chapter 4 - Classes and Instances

Fixing Common Errors at Compile Time


A few simple errors can occur when you save a method definition:
Undeclared temporary variables
This is an “error” that you can commit on purpose, because the system
will prompt you with a menu of variable types with which you can quickly
and easily declare each of the temporary variables.
Undeclared class and instance variables
When you are prompted to declare an instance or class variable, it’s best
to select Abort in the menu and declare the variables before continuing. To
save your uncompiled method while you use the System Browser to
redefine the class, select Spawn in the code view. This opens a new
browser on the uncompiled code.
Missing period
When you have omitted a period, the system treats what should be two
statements as though they were a single message expression. As a
result, the error description is usually “Nothing more expected.”
Missing delimiters
When you have omitted a parenthesis or bracket, the error description is
“Right parenthesis expected” or “Period or right bracket expected.”

Returning from a Method


Every method returns a single object, which can be a collection of other
objects. By default, a method returns self, the object that received the
message. This returned object may be ignored by clients that are
interested only in the effect of the method, or stored in a variable it the
object needs to be referred to again.
To return an object other than the receiver, you can specify that object by
using a caret symbol ( ^ ) preceding an expression that returns the object.
For example, in an accessor method, place the name of the return object
after a caret.
accountID
^accountID
This returns the current value of the variable accountID.

110 VisualWorks
Methods

Returning From an Enclosed Block


When a return character is enclosed within a block (see “Block
Expressions” on page 98), it forces a return from the entire method. That
is, it does not act as a return from the block back to the containing
method.
Returning the Result of a Message
A return character that is followed by a message causes the result of that
message to be returned. This approach often circumvents the need to
create a temporary variable for the message result.
Place a caret in front of the message receiver.
displayString

^accountID printString, '--', name


Returning a Conditional Value
Frequently, a method performs a test and returns one value if the test
result is true and a second value if the test result is false. Relying on the
fact that a return character that is followed by a message returns the
result of the message, you can use a single return caret to serve both
forks of the branch, rather than placing a caret inside each block.
This approach has the advantage of combining two exit points into a
single exit point, which is better programming style. It also makes the
ifTrue: and ifFalse: blocks clean blocks—that is, blocks that do not
contain a hard return character.
Place a caret in front of the conditional expression.
accountPrefix
"Answer the first four characters of the accountID,
or an empty string if the accountID is empty."

| id |
id := self accountID.

^id isEmpty
ifTrue: [String new]
ifFalse: [id
copyFrom: 1
to: 4].

Application Developer’s Guide 111


5
Control Structures

Control structures in Smalltalk are invoked by sending messages to


various objects. The boolean objects true and false provide the if-then-else
machinery, while numbers, collections and blocks provide the looping
methods. These two types of control structure—branching and looping—
are described in this chapter.
The BlockClosure class provides the machinery with which these control
structures are implemented. You can use the same machinery to create
new control structures. Block syntax is described in Block Expressions.

Branching
The Boolean classes True and False implement methods for performing
conditional selection (if statements).

Boolean Values
Many classes implement methods that test an object for a condition or
compare an object with another, and return a Boolean value—either true or
false.
The most basic tests, implemented in Object, are equality (=) and identity
(==), return true if two objects are equal or identical, respectively, and
return false otherwise.
9=9 “returns true”
9 == 9 “returns true”
9 = 'nine' “returns false”
9 == (5 + 4) “returns true, the same SmallInteger”
'this is a test' = 'this is a test' “returns true”
‘this is a test’ == ‘this is a test “returns false; equal but different”
Array new = Array new “returns true”
Array new == Array new “returns false”

112 VisualWorks
Branching

Similarly, numbers, strings, and a few other objects return a Boolean to >,
<, >=, and <= messages according to how the objects compare in size or
order.
There are also methods defined throughout the system, often named in
the form “isSomething”, where “Something” is the name of a kind of
object or a property, for testing whether an object is that kind, and
returning a Boolean response. For example, isString returns true if the
receiver is a String object, isNil returns true if the receiver has the value
nil, and isReadOnly returns true if the receiver has its “read only” property
set, and otherwise they return false.
anObject isString.
Using testing messages like these are useful in defining specific handling
of objects, based on condition of passing (true) or failing (false) the test, as
described in the next section.

Conditional Tests
Given an expression that evaluates to a Boolean, you can branch the
processing based on that value. The conditional test messages are
ifTrue: aBlock
Evaluates aBlock if the receiver is true.
ifFalse: aBlock
Evaluates aBlock if the receiver is false.
ifTrue: aBlock ifFalse: anotherBlock
Evaluates aBlock if the receiver is true, or anotherBlock if the receiver is
false.
ifFalse: aBlock ifTrue: anotherBlock
Evaluates aBlock if the receiver is false, or anotherBlock if the receiver is
true.
All of these messages must be sent to a Boolean, so of the last two, one of
the blocks in guaranteed to be evaluated.
ifTrue:ifFalse: is the Smalltalk version of common if-then-else construct. In
the following example, a prompt string is selected depending on whether
the application user is a managerial employee:
(userType == #Manager)
ifTrue: [prompt := 'Enter your password']
ifFalse: [prompt := 'Access denied—sorry']

Application Developer’s Guide 113


Chapter 5 - Control Structures

The blocks can be left empty when no action is required. This is so often
the case that ifTrue: and ifFalse: are provided as separate methods. In the
example above, if no password were required, the ifTrue: portion of the
expression could be dropped entirely.
Unless the block does a return (^), which exits the block and its
containing method, processing continues with the next expression.
Note that Smalltalk has no equivalent of the case statement provided in
many languages, because case statements tend not to be object-
oriented.

Compound Conditions
Compound conditions are formed by “and,” “or,” and “not” operations,
producing a Boolean value from one or more other Boolean values. The
following messages are available for performing these operations
and: aBlock
Returns true if the receiver is true and aBlock evaluates to true;
otherwise returns false. aBlock is evaluated only if the receiver is true.
& aBoolean
Returns true if the receiver and aBoolean are both true; otherwise
returns false.
or: aBlock
Returns true if either the receiver is true or aBlock evaluates to true, or
both; otherwise returns false. aBlock is evaluated only if the receiver is
false.
| aBoolean
Returns true if either the receiver or aBoolean is true, or both; otherwise
returns false.
not
Returns false if the receiver is true, or true if the receiver is false.
As suggested in the descriptions above, the alternate forms for the “and”
and “or” operations provide for different processing control. The & and |
binary messages always evaluate both the receiver and aBoolean
expressions when evaluating the value of the compound statement. The
and: and or: keyword messages, on the other hand, only evaluate aBlock if
the value of the compound cannot be determined from the receiver alone.
If the receiver of and: is false, then the value of the compound must be
false regardless of the value of aBlock. Similarly, if the value of the receiver
of or: is true, the value of the compound must be true regardless of the
value of aBlock.

114 VisualWorks
Looping

For example, in this example using the | binary message, both conditions
are evaluated, and an unhandled exception (subscript out of range)
occurs:
| aCollection |
aCollection := #( 'one' 'two' 'three' ).
aCollection notNil | ((aCollection at: 5) = 'five')
ifTrue: [Transcript cr; show: 'true'].
However, since the first condition is true, the complex condition should
evaluate to true. Using the and: keyword message instead defers
evaluating the block until it is needed, which it is not in this case, and the
message goes through as intended.
| aCollection |
aCollection := #( 'one' 'two' 'three' ).
(aCollection notNil or: [(aCollection at: 5) = 'five'])
ifTrue: [Transcript cr; show: 'true'].
This difference can be valuable in writing efficient methods.

Looping
Three types of iterative operation are available: conditional, number, and
collection looping. This section discusses the three types of looping.

Conditional Looping
Conditional looping involves a conditional test that determines whether to
repeat the loop.
whileTrue: and whileFalse:
In the previous example, the expression (userType == #Manager) is
evaluated just once. By contrast, the condition that drives a while loop
has to be evaluated multiple times. In Smalltalk, it is enclosed in the
square brackets that identify it as a block (an instance of class
BlockClosure). The whileTrue: message causes that block to receive a value
message, which triggers execution of the block’s contents. If the
expressions in the receiver block return a true, the argument block is
executed. Then value is again sent to the receiver block to see if it is still
true, repeating the cycle.
The following example might be used in a game that ends when there is
only one player (the winner) left in the game:
[players > 1] whileTrue:
[nextPlayer takeTurn.
(nextPlayer outOfGame) ifTrue: [players := players - 1] ]

Application Developer’s Guide 115


Chapter 5 - Control Structures

To reverse the logic of the test, use whileFalse:. For example, to process a
stream of objects until the endpoint is encountered:
[self atEnd] whileFalse: [aBlock value: (self next) ]
For situations in which no argument block is needed, the unary messages
whileTrue and whileFalse are available.
repeat
When a block of statements contains its own (reliable!) test for returning
from the loop, the simple message repeat can be sent to the block.

Number Iteration
Number looping corresponds to the traditional for loop, and is
implemented via messages to numbers.
timesRepeat:
To repeat a block of expressions a specific number of times, send a
timesRepeat: message to a number and provide the repeatable block as an
argument. For example, to send the string ‘Testing!’ to the Transcript
anInteger times:
anInteger timesRepeat: [Transcript show: 'Testing!']
to:by:do:
A more elaborate sort of for loop comes in the form of the to:by:do:
method, which lets you specify a starting integer, a stopping integer, the
step increment, and the block to be repeated. For example, to print
something like a word processor’s tab-setting ruler on the Transcript:
10 to: 65 by: 5 do: [ :marker |
Transcript show: marker printString.
Transcript show: '---'].
Here’s a translation: Count by fives from 10 to 65. Pass each such value
to the block, which converts it to a string and outputs it to the Transcript,
followed by three hyphens. The output looks like this:
10---15---20---25---30---35---40---45---50---55---60---65---
Notice that, unlike timesRepeat:, the to:by:do: method automatically passes
the value of the counter to the block (picked up by the argument named
marker in this case). The block must declare an argument variable to
catch the passed value.

116 VisualWorks
Looping

to:do:
When the counting increment is 1, you can use the simpler to:do:. The
following example prints the ASCII equivalents of the numbers 65 through
122 in the Transcript.
65 to: 122 do: [ :asciiNbr |
Transcript show: asciiNbr asCharacter printString]

Collection Iteration
Collection looping supports scanning, counting, and other operations
involving one repetition for each member of a collection. It is frequently
useful to repeat a series of operations for each element in a collection of
objects (collections are discussed further in Chapter 17, “Collections”).
The integer iteration discussed above is a special case, dealing
exclusively with numeric intervals—i.e., collections of integers. The
iteration methods discussed in this section apply to other kinds of
collections as well. All are implemented by the Collection class, which is
the superclass of dictionaries, arrays, sets, strings, etc.
do:
The simplest method, do:, evaluates the block for each member of the
collection. For example, to capture the contents of an array during
program execution, we might want to convert each member to a printable
string and output it to the Transcript:
anArray do: [ :anElement |
Transcript show: (anElement printString); cr ]
select:
To filter a collection and wind up with a desired subset, use select:. Each
member of the collection that satisfies the conditions in the block is stored
in a new collection of the same type, which is returned by the method.
The following example counts the number of question marks in a string by
gathering the question marks into a new collection and then finding the
size of that collection:
(aString select: [ :eachChar | eachChar == $? ] ) size
reject:
The reject: method is the opposite of select:. It gathers the members of the
original collection that fail the test rather than those that pass it.
Substituted for select: in the example above, it would create a collection of
non-question-marks, which would then be sized.

Application Developer’s Guide 117


Chapter 5 - Control Structures

detect:
The detect: method, like select:, tests each element of the collection. But
instead of returning a subcollection of those elements that pass the test,
it returns the first such instance (and stops testing at that point). The
following example locates the first instance of the integer 8 in anArray:
anArray detect: [ :each | each == 8 ]
collect:
The collect: method performs a transformation on each element of the
collection and returns a new collection containing the transformed
objects. For example, to get an uppercase version of aString:
aString collect: [ :each | each asUppercase ]
inject:into:
The inject:into: method enables you to pass an explicit argument to the
block in addition to the collection’s elements. This explicit argument (the
inject: part of inject:into:) is used to initialize a counter for a cumulative
operation such as summing. For example, to add the numbers in aSet:
aSet inject: 0 into: [ :subtotal :nextNbr | subtotal + nextNbr]
The following table summarizes the branching and looping methods
discussed in this chapter.
Control Structure Methods
Method name Description
ifTrue: If the prior expression is true, execute the
argument block.
ifFalse: If the prior expression returns false, execute the
argument block.
ifTrue:ifFalse: If the prior expression is true, execute the first
block; otherwise do the second block.
ifFalse:ifTrue: Checks for a false condition first.
whileTrue: Repeat the argument block until the receiver
block is no longer true.
whileFalse: Repeat the argument block until the receiver
block is no longer false.
whileTrue Repeat the receiver block until it no longer
returns true.
whileFalse Repeat the receiver block until it no longer
returns false.

118 VisualWorks
Looping

Control Structure Methods (Continued)


Method name Description
repeat Repeat the receiver block until it executes a
return or otherwise breaks the loop.
timesRepeat: Repeat the argument block, using the receiving
integer as a counter.
to:by:do Repeat for a specified interval, incrementing the
counter by a specified value. Use the repetition
counter as a block argument.
to:do: Same as to:by:do:, using 1 as the increment.
do: Repeat a block for each element in the receiver
collection.
select: Collect all elements that pass a test.
reject: Collect all elements that fail a test.
detect: Return the first element that passes a test.
collect: Transform each element and return the
transformed version of the collection.
inject: into: Perform a cumulative operation such as summing
the elements.

Application Developer’s Guide 119


6
Name Spaces

VisualWorks implements name spaces as a language feature. Name


spaces allow VisualWorks to be very flexible in how it handles add-in
components from a multiplicity of vendors.
Initially, Smalltalk had a single name space, the monolithic Smalltalk pool.
All globals (class names, global variable names, pool names) were
resolved (their referents were determined) within that single context, the
Smalltalk environment. Accordingly, each global name had to be unique
to be identified from all others.
This worked fine as long as Smalltalk remained an environment of small,
individual developers creating applications for their own use or in isolation
from other applications. As Smalltalk went to the “enterprise,” and as
component development and deployment became increasingly common,
the luxury of isolation and control was lost.
For example, a system integrator might want to assemble a supply
management system out of modules from multiple vendors. Each
component may need to access records storing customer data, which
each would quite reasonably represent as instances of a Customer class.
In a single-vendor environment that class definition can be controlled and
made consistent. In a multiple-vendor environment, however, that is much
more difficult or impossible. The vendor, attempting to integrate the
components from these vendors has a major problem with name
conflicts.
As long as all global names were resolved within the single Smalltalk
name space, such naming collisions were inevitable, and increasingly
frequent. This calls for a systemic solution rather than ad hoc work-
arounds.

120 VisualWorks
Multiple Name Spaces

Multiple Name Spaces


The general solution was not difficult, and had precedent in other
programming environments. It was simply to restrict the global name
resolution space, so that names didn’t need to be unique in the whole
Smalltalk environment, but only within a much smaller “name space.” In
effect, a name in one resolution space could be hidden from other
resolution spaces, unless it was explicitly exposed.
By restricting name resolution, references to vendor 1’s Customer class
can cohabit the Smalltalk system with vendor 2’s Customer class, as long
as they are in different name spaces. Each Customer class can be
referred to unambiguously by identifying the containing name space.
There is a little more work in some cases, when both classes need to be
referenced by the same application, or when an object in one name
space needs to reference an object in another name space. References
still need to be unambiguous. But, disambiguation is a relatively simple
matter of specifying a name space, rather than changing all references to
comply with a name change.
To accomplish this, VisualWorks was extended to support additional
name spaces, providing for contexts more specific than just Smalltalk
within which names are resolved. The universal Smalltalk name space is
retained as a “super-name space.” Smalltalk is then divided into several
other name spaces, each providing its own name resolution context.
Additional name spaces can be defined within Smalltalk or within any of
its sub-name spaces, to provide an appropriate separation of contexts.

Getting Started
You can gain experience with name spaces in stages, increasing the
extent of use as you become more comfortable with them. It is possible,
for instance, to define all of your classes in the Smalltalk name space,
and proceed largely as if multiple name spaces don’t exist. For
instructions on how to do this, see to “Taming Name Spaces,” in the
doc/TechNotes directory and from the Cincom Smalltalk
Documentation web page: [Link]

Application Developer’s Guide 121


Chapter 6 - Name Spaces

Name Spaces and Their Contents


In general terms, a name space is a context within which the referent of a
term is determined.
For example, within the context of a gathering of my wife’s family, the
name “Bob,” used without qualification, picks out one unique individual,
while among my own family it picks out a different, though still unique
individual. There is no confusion as long as these contexts are kept apart;
our respective families serve as adequate name-resolution spaces, or
name spaces.
Put our two families together, however, and the name “Bob” becomes
ambiguous, and it’s entirely possible for embarrassing confusions to
occur. However, it is generally quite simple and straight-forward to avoid
such confusions, and the resultant embarrassment, by explaining the
scope more precisely. Including the family name is generally sufficient
and not overly difficult.
In Smalltalk a name space works in the same way. Given a name of a
variable, the object referred to by that name is identified within some
naming scope. Traditionally, the name scope has been the either whole
Smalltalk image in the case of global variables, an individual instance in
the case of instance variables, or a class, its subclasses, and their
instances in the case of class variables. To avoid confusion over the
globals (class names, pools, and general globals), names were required
to be unique within the system; you were only allowed to have one Bob.
In VisualWorks, you are allowed to have as many Bobs as you want, as
long as each of them can be uniquely identified. Unique identification is
possible by making sure that each Bob is defined and resolved in a single
name space, and avoiding name space collisions.

Name Space Contents


A name space is a named object that represents the name resolution
scope of a collection, or pool, of shared variables (see Shared Variables).
A name space is itself the value of a shared variable defined in another
name space. A particular name space, called Root, is the parent of all
other name spaces, forming a name space hierarchy.
The Root name space initially contains two shared variables: Root, the
value of which is the name space itself, and Smalltalk, the value of which
is the Smalltalk name space.

122 VisualWorks
Name Spaces and Their Contents

To explore the structure of a name space, do an inspect on it. For


example, evaluate this expression with doIt:
Root inspect
This opens a Namespace Inspector showing the contents of the name
space. Diving down through the Smalltalk entry, you observe additional
shared variables whose values are the “top level” name spaces defined
immediately in Smalltalk. Initially, the values of these are the name spaces
that contain system code. As you create your own “top level” name
spaces, shared variables for them are added to Smalltalk.
Continue the descent and you find definitions of name spaces, classes,
and general shared variables.
To explore more deeply, seeing the structure of the entries, evaluate:
Root basicInspect
Doing this you see the representation of name spaces as a collection of
bindings.

The Name Space Hierarchy


VisualWorks name spaces are organized in a hierarchy. At the top of the
hierarchy is a single name space, named Root.
Initially, it has a single sub-namespace, Smalltalk. For most practical
purposes, the hierarchy starts with the Smalltalk name space, as the
super-name space of all name spaces containing Smalltalk definitions. A
fragment of the base VisualWorks name space tree, with a couple extra-
base components added, looks like this:
Root
Smalltalk
Core
OS
IOConstants
Graphics
SymbolicPaintConstants
TextConstants
VisualWave
XProgramming
SUnit
In general, new name spaces should be contained within Smalltalk, either
directly or indirectly, rather than directly in Root.

Application Developer’s Guide 123


Chapter 6 - Name Spaces

New “top-level” name spaces, those defined directly in Smalltalk, must be


unique within the Smalltalk name space (there can only be one
[Link]). The VisualWorks team and various vendors have reserved
a number of top-level names. We maintain a Wiki site to allow you and
others to reserve top-level name space names, and to see what names
have been reserved, to help avoid name collisions at this level (for the list,
go to Reserved Top-Level name spaces for VisualWorks).
The exception to keeping name spaces under Smalltalk would be a
product that supports development and execution of another language,
such as Java, within a Smalltalk image. Such a product might create a
name space in Root, perhaps called JavaWorld, as well as various name
spaces nested within it. The resulting name space hierarchy might look
something like this:
Root
Smalltalk
JavaWorld
java
lang
awt
COM
sun
microsoft
If the Frost project were ever to be completed, it would probably take this
approach.
[Link]
In the Root name space there are two shared variables defined: Root and
Smalltalk. (To verify this, evaluate Root inspect.) Root refers to the name
space itself, and Smalltalk refers to the Smalltalk name space.
It is sometimes convenient to be able to refer to the Root name space
from Smalltalk, and so there is a shared variable defined in Smalltalk that
refers to Root. This leads to a circularity that can be confusing, but need
not be.
When working in Smalltalk, references to named objects are assumed to
start with Smalltalk, rather than Root. For most practical purposes, Root
can be ignored.
If for any reason you do need to refer to Root, the circularity allows you to
follow the same convention of starting with Smalltalk. So, to refer to the
Root name space from within Smalltalk, the full path would be
[Link]. But, because of the assumption of the [Link]
initial segment, you can refer to it simply as Root.

124 VisualWorks
Working with Name Spaces

Working with Name Spaces


In VisualWorks 7, the support for name spaces in the toolset has been
considerably changed. As a component model based upon packages is
being gradually introduced into the base system, the use of name spaces
as a mechanism for structuring components has been discouraged.
The biggest apparent change has been in the presentation of name
spaces in the system browser. These changes are covered in the
following sections.

Browsing Name Spaces


For working with name spaces, open the System Browser (select
Browse ­ System in the Launcher window).

In the leftmost pane, with the Packages tab selected, the navigator shows
the bundles and packages in the system. The second pane lists classes
and name spaces, with the name spaces distinguished by a special icon.
For example, in the screen above, the Core name space has been
selected in the class/name space view, and its definition appears in the
code tool (below).

Application Developer’s Guide 125


Chapter 6 - Name Spaces

When a name space is selected in the class/name space view, the


Shared Variables tab appears, and is the only tab that is selectable. That’s
because name spaces only contain shared variable definitions. The next
pane, the traditional method category, or protocol, view, lists the
categories of any shared variable definitions in the name space.
Selecting a shared variable displays a special code tool for inspecting an
existing variable, or defining a new one.
When a class is selected in the class/name space view, the browser
behaves more like the traditional Smalltalk class browser. You now can
select the Instance or Class tabs, as well as the Shared Variables button. If any
shared variables are defined in the class, selecting the Shared Variables tab
will show any categories, and selecting one of those shows its shared
variables.

Creating Name Spaces


To create, select a package, and optionally the name space in which to
create the new one. Then select Class ­ New ­ Name Space... to open the
New Name Space dialog:

The fields are:


Package
The name of the package in which to include this name space
definition.
Name Space
The name of the parent name space for the new name space.
Name
The name for the new name space, such as MyCompany.

126 VisualWorks
Working with Name Spaces

Private
Check if this name space is to be private, i.e., not available for import.
Imports
A list of imports, either specific or general, separated by whitespace,
and including “private” if appropropriate. For example, enter:
private Smalltalk.*
XML.*
See “Importing Bindings” for more information.
Then, click OK to define the name space.
Alternatively, select a package only to display a definition template, or a
name space to display a name space definition. Edit the definition giving
at least a new name space name, and Accept the changes, to create a
new name space.

Naming a Name Space


There’s no particular mystery to naming your name space(s). Most of
your code will be application or add-ins, rather than extensions to the
base system. So, your name space:
• needs to see a lot of the standard VisualWorks library
• does not need to be seen by the standard VisualWorks library
• needs to avoid name clashes with the VisualWorks and other 3rd
party products.
The first of these is handled by imports, but is good to remember. The
second point means that there is no reason, in general, for your code to
be in an existing VisualWorks name space. The third suggests that you
want a name space that will be clearly your own, separate from all others.
To deal with these points, we recommend that you create your own “top
level” name space, immediately in the Smalltalk.* name space. To help
keep it clear that this is yours, it is a good idea to use some form of your
company name or similar designation, as suggested by the example in
“Creating Name Spaces” above.
To help ensure that these top-level name space names are unique, we
maintain a Reserved Top-Level Name Spaces Wiki page. Instructions for
reserving your top-level name are provided on that page. You reserve a
name by adding it to the list. Make sure it hasn’t been taken by someone
else, first, of course.

Application Developer’s Guide 127


Chapter 6 - Name Spaces

As long as your top-level name is unique, subsequent names you select


for name spaces, classes, and shared variables under that top-level
name are protected from clashes with those outside of that name space.
So, you can name additional name spaces under your top-level name
space in any way that makes sense to you.

When to Create a New Name Space


You should always have at least one top-level name space for your own
work. Beyond that, whether you need sub-name spaces depends on the
name-access requirements of your products.
You may well have use for separate name spaces for each of your several
products. Or, maybe not, depending on how tightly they interact.
In deciding, remember that all name spaces and classes created within a
name space have access to all shared values defined in it. Consider that:
• If all of your classes need to see all of your other classes, then they
all can reasonably be defined in a single name space.
• When you create classes that do not need access to some of your
other classes, then it is time to consider creating further name
spaces.
• If you create classes in one name space that need to access objects
in another, you can import that other name space.
It’s a judgement call that will become clear in practice.

Rearranging Name Spaces


Almost certainly you will need to move classes and name spaces around
to other name spaces in the course of development. This is quite simple,
using the System Browser:
1 In the class/name space list, find the class or name space to move.
2 Click and hold on the item, drag it to the target name space in the
name space list, and drop it.
The class or name space is then moved, and the lists are updated to
show the change.
To move a name space, you can also select it in the class/name space
list, and select Move­ to Name space... from the Class menu. Select the
target name space in the dialog that opens, and click OK.

128 VisualWorks
Referencing Objects in Name Spaces

Classes as Name Spaces


In some situations classes can serve as name spaces. In fact, classes
and name spaces are very similar, the main difference being that classes
are restricted as to the kinds of shared variables they can contain; they
can contain only general shared variables, which are its class variables.
Classes cannot contain shared variables that have name spaces or other
classes as their primary reference.
What had formerly been a class’s shared pools are now its imports, with
all the same properties as the imports to a name space. An extension
here is that a class can now import a single shared variable, by using a
specific import, as well as being able to import the whole pool. Refer to
“Importing Bindings” in Chapter 6 for more information about general and
specific imports.
A class's superclass is implicitly an import of the class that can never be
declared private. This means that if A is a superclass of B, and B is a
superclass of C, anything that A does not declare to be private will be
visible to C, regardless of what B may declare private. This preserves
from previous versions the rule that all class variables (assuming that
they have not been declared private) are visible to all subclasses.

Referencing Objects in Name Spaces


Within the native naming scope of a binding, whether for a name space, a
class, or a shared variable, the object can be referenced to by unqualified
name. However, most objects will also have to reference objects that are
not native to the same name space.
For example, within the VisualWorks system, virtually any object needs to
reference objects in the Core name space, even though it is native to
another name space. Your application objects, which will be native to your
own name space(s), have to reference a wide range of objects in
VisualWorks name spaces, and possibly objects from other vendors.
There are a variety of ways to reference these named objects, as
described in the following sections.

Dotted Names and Name Space Paths


Binding names (names of name spaces, classes, and shared variables)
use a dotted notation that describes the path through the name space
hierarchy to the desired binding. While you seldom reference a binding

Application Developer’s Guide 129


Chapter 6 - Name Spaces

using its full dotted name (except when specifying imports), in order to
understand the other referencing methods you need to know about dotted
names.
The full path a dotted name begins with the Root name space, continuing
through the hierarchy to the target binding. For example, the full reference
to the ButtonHilite constant (in its native name space) is:
[Link]
However, the VisualWorks system, when parsing a compound dotted-
name, assumes the [Link] initial segment. So, in practice, the
above reference is shortened to:
[Link]
This is the form of reference used in import statements, providing the
path starting immediately after Smalltalk.
If a binding is imported, the dotted name can specify the importing name
space path, instead of its native name space path. So, for example, if
[Link] imports ButtonHilite, the dotted name
[Link] would also be a legitimate dotted name, and
would reach the variable; it is not necessary to reference ButtonHilite
through its native name space, SymbolicPaintConstants.
Because a dotted name introduces a path starting immediately after
Smalltalk, dotted names do not follow the relative path rules familiar from
file systems. You can, however, reference a binding relative to the current
name space context by beginning the path expression with “_.”
(underscore, dot). Using this notation, if a name space (MyNamespace1)
imports another name space (MyNamespace2), and MyNamespace2 has a
class (Foo) with a class variable (Bar), an instance of any object defined in
MyNamespace1 can reference Bar with:
_.[Link]
Using dotted names in code to reference variables that are neither
defined in nor imported into the current name space, is permitted but
discouraged, because this use breaks encapsulation. There are,
however, occasions when they are needed. In source code, it is
sometimes necessary to refer to a variable that is not visible from the
current name space. For example, if a developer is adding a method to a
class that he does not own, and he may not have the freedom to add a
new import to the class's environment. In future releases we intend to
provide a better mechanism for extending classes, allowing extensions to
use variables not normally visible to the class, but they are not currently
available.

130 VisualWorks
Referencing Objects in Name Spaces

They were also needed in a workspaces before 5i.3, to evaluate an


expression that includes a variable from an arbitrary name space. In 5i.3
and later releases, however, workspaces import name spaces, so this is
no longer an issue.

Binding References
In an environment with name spaces, we need a way to reference a
shared variable that makes no assumptions about which name space
contains its definition. A binding reference provides this facility.
A binding reference is a named object that holds a starting point and a list
of names. It can identify an arbitrary shared variable relative to an
arbitrary name space, by identifying a navigation path from the name
space to the shared variable.
Most of the protocol for binding references is defined in the class
GenericBindingReference, with more specific protocol defined in
BindingReference and LiteralBindingReference. The common protocol
includes useful questions such as:
isDefined
Does the variable exist in the system?
binding
Answer the VariableBinding for the shared variable, or raise an error if it
doesn't exist.
bindingOrNil
Answer the VariableBinding for the shared variable, or nil if it doesn't
exist.
value
Answer the value of the shared variable, or raise an error if it doesn't
exist.
valueOrDo: aBlock
Answer the value of the shared variable, or the value of aBlock if it
doesn't exist.
A binding reference, when asked for its binding, iterates through its list of
names. For each name, it asks the current name space for the variable of
that name. If the name is the last in the list, it answers the shared
variable. If the name is not last, it uses the value of the variable as the
new current name space, and repeats the process with the next name in
the list.

Application Developer’s Guide 131


Chapter 6 - Name Spaces

There are two forms of binding reference, distinguished by how their


environment information is stored, corresponding to classes
BindingReference and LiteralBindingReference. The environment is the name
space scope within which the binding reference is evaluated.
Instances of BindingReference store their environment in their environment
instance variable. Accordingly, each instance knows its compilation
scope. Instances of LiteralBindingReference, on the other hand, store the
method that created them in a method instance variable, and their
environment is then determined from the compilation scope of the
method.
A simple way of creating a BindingReference is by sending
asQualifiedReference to a String, for example:
‘MyBinding’ asQualifiedReference
The syntax #{MyBinding} creates a LiteralBindingReference.
Inspect the results of each expression to compare their object structure.
Be aware that although the printing representation of both is the same,
they are not equal, being different classes of objects. (This inequality may
change at some later time.)
Both of these allow referencing the shared variable without the
programmer having to know or specify the path to the variable. The name
resolution environment determines the object referenced. Consequently,
it is not necessary to know whether the variable’s environment is an
import or native.
Note that the referenced binding does not need to exist when the binding
reference is created. It’s just a reference object, and is resolved at
compile-time.
In both cases, name space path information can be included as well,
using the dotted-name notation. Remember that compound dotted-
names always go back to Smalltalk, so the entire path from that point must
be given. For example:
‘[Link]’ asQualifiedReference
or
#{[Link]}
Other instance creation methods are available (browse class
BindingReference and GenericBindingReference). For example:
BindingReference path: #(Core Object)

132 VisualWorks
Referencing Objects in Name Spaces

which creates a BindingReference to [Link]. Providing the path is often


necessary when specifying imports in name space and class definitions.

Note: Class QualifiedName in VisualWorks 3.0 was replaced by class


BindingReference in 5i and later, so be aware of this if you referenced
that class in your code.

Binding Reference Resolution


Binding reference are resolved in this order:
1. If a bindings is defined in the name space, the binding reference
takes it.
2. Next, bindings imported by a specific import are selected.
3. Finally, bindings imported by a general import are used.
See “Binding Rules and Errors” below for restrictions on imports.
When to Use BindingReference or LiteralBindingReference
The differences between BindingReference and LiteralBindingReference make
these objects not fully interchangeable.
The #{...} syntax is appropriate for asking questions of binding references,
such as isDefined, where the reference is short lived.
If a short-lived method (such as a DoIt) is used to create a reference for
long-term storage (such as in a Dictionary), use asQualifiedReference or
fullyQualifiedReference methods to create a BindingReference. Because a
LiteralBindingReference holds a reference to the method that created it,
putting this reference in long-term storage would prevent the creating
method from being garbage collected.
If the reference will be stored in a long-term data structure, but the
method which creates the reference is presumed to be equally long-lived,
the choice is yours, but using asQualifiedReference, may be the better
choice.
If the exact path of the binding reference is not known at compile time, but
is partially or fully computed at runtime, then you will have to use a
BindingReference, since #{...} syntax is not an option.

Importing Bindings
While it would be possible to require that you reference each object by
explicitly describing the name space path from Root to the target object,
that would be inconvenient, and would violate the object-orientation

Application Developer’s Guide 133


Chapter 6 - Name Spaces

principle of encapsulation. Instead, it is preferred to import the bindings


into the local object’s name space so they can be referenced by
unqualified name.
Name space and class definitions provide for importing bindings, by
including the bindings in the imports list. The binding name is specified
using the dotted-name notation, usually starting with the first name space
in the path under Smalltalk (Smalltalk is assumed, see “Dotted Names and
Name Space Paths”). For example, the XML name space imports its sub-
name space like this:
Smalltalk defineNameSpace: #XML
private: false
imports: '
private Smalltalk.*
[Link].*
'
package: 'XMLParsing'
This is a general import, using the asterisk (*) pattern matcher to import
all bindings defined in the indicated name space. In this example, all
bindings in the Smalltalk and in the [Link] name spaces are
imported. In particular, these lines import all name spaces defined under
Smalltalk (it would import classes, too, if there were any), and all classes
defined in the SAX name space are imported into the XML name space.
Note also that SAX is imported as public. Doing this has XML also export
those imported bindings, so that they are also imported by any class or
name space that imports XML. In this case this is the right thing to do
since there’s no reason for an application to have to import SAX
separately from XML; if it needs XML, it will need SAX, too.
As explained in “Public and Private Shared Variables” on page 89,
including the private keyword in front of the Smalltalk.* import prevents XML
from exporting those bindings. They can be reasonably expected to be
imported by each name space. For this reason, private Smalltalk.* is
included in the name space definition template.
On occasion a name space or class may need to import only a single
binding from another name space. This is done using a specific import.
For example, the TextConstants pool only needs access to one class in the
Core name space, so it uses a specific import:

134 VisualWorks
Referencing Objects in Name Spaces

[Link] defineNameSpace: #TextConstants


private: false
imports: '
private [Link]
'
package: 'As yet unclassified'
Once properly imported, the imported name can be used directly, without
further path qualification.
Given this general explanation, the following specific cases may be
helpful.
Importing Classes and Name Spaces
When we mention “importing a name space,” we usually really mean
importing the contents of the name space, rather than only the name
space itself. The contents of a name space may include:
• class definitions
• other name space definitions
• general shared variable definitions
When defining a name space, you almost certainly want to import the
VisualWorks system classes. To do this, include:
private Smalltalk.*
in the imports list. Smalltalk itself imports several of its base sub-name
space bindings publicly (see “Circular” System Imports below for an
explanation), so this one line, a general import, brings in all of the system
classes, pools, and system variables (such as Transcript). Add-in
components, such as the Net name space used by Net Client support, are
not imported to Smalltalk, and so must be imported by your own name
spaces and/or classes.
In your name space definitions, you will probably import Smalltalk.* as
shown above. You do not need to import all of the individual base
Smalltalk name spaces, since these are already imported to Smalltalk. You
also should not (in general) add your class to the list of name spaces
imported to Smalltalk; there is rarely a need for an application class to be
that generally available to the entire system.

Application Developer’s Guide 135


Chapter 6 - Name Spaces

Importing Class Variables


It is seldom necessary to import a class variable explicitly. They are
visible to the class in which they are defined, and inherited by its
subclasses. Since they are used to store class state information, that is
sufficient. If you do need to import a class variable, import it like a pool
variable, with the class as its pool.
Note that importing a class does not import the class variables defined in
it; these variables must either be imported or referenced by an
appropriate path.
Importing Pool Variables
Pool variables are general shared variables defined in a common name
space, which is their pool. Depending on circumstances, you will either
want to import all of the pool variables, or only one or a few.
To import all pool variables in a pool, use a general import. So, for
example, to import all of the TextConstants, use this general import in
your class or name space definition:
imports: '
private [Link].*
'
(See the definition of class TextAttributes.) This permits you to reference
each text constant by unqualified name.
To import a single pool variable, use a specific import. For example, to
import only the text constant Bold, use:
imports: '
private [Link]
'
This permits you to reference this one variable by unqualified name.
“Circular” System Imports
You may have noticed that the Smalltalk name space definition imports all
of the system name spaces:

136 VisualWorks
Referencing Objects in Name Spaces

[Link] defineNameSpace: #Smalltalk


private: false
imports: '
Core.*
Kernel.*
OS.*
External.*
Graphics.*
UI.*
Tools.*
Database.*
Lens.*
'
package: 'As yet unclassified'
while each of those name spaces’ definitions imports Smalltalk, e.g.:
Smalltalk defineNameSpace: #Kernel
private: false
imports: '
private Smalltalk.*
'
package: 'System-Name Spaces'
What’s happening is that Smalltalk imports each of its sub-name spaces
imports as public (for further export), so all of those bindings are
accessible directly from Smalltalk. Each sub-name space in turn imports,
privately, all of the bindings from Smalltalk, which includes all the bindings
Smalltalk imported from their siblings.
Now, for example, an instance of [Link] can reference
[Link] by its unqualified name, Array. All of the base VisualWorks
classes, pools, and such, are accessible directly from Smalltalk, as before.
For the most part, this also simplifies migrating from pre-5i releases to
later releases, by making sure all the system classes are available. When
code is imported, it is loaded directly into the Smalltalk name space,
where it has access to the essential system classes, and so mostly works
without modification.

Application Developer’s Guide 137


Chapter 6 - Name Spaces

Binding Rules and Errors


Each imported binding name must be unique in the collection of names
defined in and imported into the name space. Accordingly:
• If two specific imports refer to shared variables of the same name,
the name space's definition is in error.
• If a specific import refers to a shared variable whose name is the
same as a shared variable defined locally in the name space, this is
an error.
• If two general imports bind the same name to different shared
variables, and a local definition or specific import of that name does
not exist, it is an error for a method to use that variable name.
However, the name space may define a specific import that clarifies
which of the two shared variables is desired.
• Local definitions of a shared variable and specific imports are
searched before general imports when binding a name to a shared
variable.

138 VisualWorks
7
Managing Smalltalk Code

In VisualWorks, you create Smalltalk code either by defining classes and


methods by writing code directly in a browser or by generating
specifications using the UI Painter and similar tools. Source code for the
changes you make are saved in the changes file while you work, and the
compiled results are stored in the VisualWorks image held in system
memory. When you save the image, those modifications are saved in the
image file.
Together, the image and changes file provide a simple code management
system, but it is limited. Image files are often too large and unwieldy to
use for sharing your work with other developers. There are no facilities for
storing or archiving your work. The change log does provide reasonable
crash recovery (see “Recovering from a Crash” below), but are not
designed for sharing or archiving work.
VisualWorks provides additional strategies and tools for storing and
managing Smalltalk code, which build upon the basic mechanisms and
make code sharing much easier:
• Source (.sou and .cha) and file-out (.st) files are text-based files,
similar to the source files of other languages.
• Change sets can be used to organize small projects, and are suitable
for personal use and use by small teams.
• Parcel files are an external representation of the packages and
bundles that organize your code, and provide an efficient code
deployment mechanism.
• For full team development, Store supports a database repository for
packages and bundles, and supports a wide variety of team
development activities (refer to the VisualWorks Source Code
Management Guide for more information on Store).

Application Developer’s Guide 139


Chapter 7 - Managing Smalltalk Code

Source Code Files


The image (.im) file contains a snapshot of the current state of the
VisualWorks system, consisting of the objects in the system and their
state. The image is the result of successive changes made to the system:
defining classes, methods, name spaces, and shared variables, creating
class instances, and modifying any of these.
The originally shipped image file, [Link], is accompanied by a
source code file, [Link], containing the definitions for the objects
in the system, prior to any changes to it. When you browse any
unchanged item in the base image, the source for that item is found in
this file and displayed. The [Link] file remains unchanged through
subsequent changes to the system. The sources file is an XML file-out
format file, as described below (see “File-Out Format Files”).
When you make changes to the system represented by [Link], the
source code for those changes, that is any new or changed class and
method definitions, doIts, and so on, are recorded in [Link], the
“changes file.” When you browse any of these changes, the source code
for that item is displayed from the changes file. Still, if you browse an item
from the original [Link], its source code is shown from
[Link].
Finally, when you save an image to a new name, the image file and the
changes file take on that new name. For example, saving a modified
image as “working” saves the image file and [Link] and the
changes file as [Link]. The [Link] file, notice, is still
sources file for much of the image, namely those objects defined in the
original [Link].
Consequently, if you copy an image from one location to another, make
sure you copy also both its associated changes file and the original
[Link] file.
The changes file gets very large over time, and also accumulates out-of-
date code. Out of date code would be created, for example, when a class
or method is defined, then modified or deleted. Only the latest definition is
relevant to the system for browsing purposes, unless you want to revert to
an earlier version of a definition (see “Using the Change List”). When the
changes file gets too large, make sure the current definitions are those
you want to keep, and compress the changes file, using the System ­
Changes ­ Compress Changes menu command in the Visual Launcher.

140 VisualWorks
File-Out Format Files

File-Out Format Files


VisualWorks supports filing-out source code in two formats: the traditional
chunk format, and an XML format.
The traditional source code format for Smalltalk code is chunk format.
The format is also called file-out format, because it is also used for
writing, or “filing out,” Smalltalk source code for arbitrary individual or sets
of definitions. These file-out files can then be read into, or filed-in to, any
compatible Smalltalk image (usually a compatible version of the same
Smalltalk dialect).
With the development of XML and its promise for data interchange,
VisualWorks also can save source code into an XML format. This
provides various internal system advantages as well, allowing the system
to take advantage of XML structuring.
XML format is the default file-out format in VisualWorks, and is used to
write out changes and file-out files. To change to traditional chunk format,
use the VisualWorks Settings Tool (System ­ Settings in the Launcher
window), and change the default on the Source page.
Chunk format is only needed if you are porting your code to another
dialect or to a version of VisualWorks prior to 5i. For file-outs to be ported
to a pre-name space version of VisualWorks or another Smalltalk dialect,
load the FileOut30 parcel ([Link]) goodie, which adds this
additional format option to the Source page in the Settings tool.

Filing Out Code


File-out commands are available in many menus throughout the
VisualWorks system. Depending on the menu, the command will file out
different collections of definitions. You are prompted for a file name, to
which is appended a .st filename extension. There is no indication in the
file name whether the file is in XML or chunk format.
For example, in the System Browser, the File Out As... menu selection will
file out either all definitions in a name space, in a class, in a protocol
(method category), or a single method, depending on what is selected
and which menu is invoked. The Category, Class, Protocol, and Method
menus, and the <Operate> menus for each pane, each have a file out
command, and do the appropriate action.
Additional file out commands are available in special browsers,
debuggers, the change list and change set tools, and so on, allowing you
to file out exactly the definitions you want to save. For example, to collect

Application Developer’s Guide 141


Chapter 7 - Managing Smalltalk Code

specific changes for transporting to another image, create a change set


so your changes are recorded in it. When you are ready to save all of the
changes in the change set, use the Change Set tool’s File out as
command.

Filing In Code
Filing in source code from a file-out file is most commonly done using the
File Browser tool (File ­ File Browser, or the corresponding icon in the
Launcher window). Enter the name of the file, or select it in the list pane
after displaying its directory. With the file selected, select File In... from the
<Operate> menu.
Note that if you file in a file from a pre-5i version of VisualWorks, the code
is loaded into the Smalltalk.* name space.
You can also use the Change List tool to view a file-out file method by
method, and replay selected definitions. See “Using the Change List”
below.

Packages and Bundles


Packages and bundles are the primary code organizational structure in
VisualWorks.
When Store is loaded into the image, packages and bundles are
extended with source-code revision management features. Refer to the
Source Code Management Guide for information about these features.
Packages are the basic organizational unit for source code. A package is
a collection of code definitions, which may include class, method,
namespace, and shared variable definitions. Each definition in the
VisualWorks image is associated with a package. Packages make it
simple to group definitions together into units that can be developed and
maintained independently of, but in parallel with, related code, and so to
facilitate building components.
A package may contain as much as an entire application or as little as a
single definition. This flexibility makes packages well-suited for building
application extension components as well as complete applications.
Thus, packages provide developers with a simple means to define
components at any desired granularity.
Packages also specify a variety of dependencies between themselves,
other components, and the VisualWorks environment. Prerequisites
specify other packages and parcels that are required before the package

142 VisualWorks
Packages and Bundles

is loaded, and may require specific versions of those. Pre-load and post-
load actions may execute custom initialization and clean-up code to
manipulate the system as necessary, and restore it.
A bundle is a collection of packages and other bundles, providing a way
to assemble a body of code from smaller units. Each bundle is defined by
a specification that lists the names and versions of each package or
bundle that it contains, in a specific load order.
During development, a bundle can be used to load a set of packages, as
a convenience mechanism. For deployment, a bundle can be the unit that
defines a parcel. These uses are discussed in more detail later. A
package may belong to more than one bundle.
Like packages, bundles may have prerequisites, and initialization and
clean-up actions, which are used in addition to actions defined for their
contained packages and bundles. When deploying a bundle as a parcel,
however, only prerequisites and actions defined for the bundle become
prerequisites and actions for the parcel.

Creating packages
To create a new package choose Package ­ New... in a system browser,
and specify a name for the new package.
The new package is added to the Packages list in the browser. The new
package is represented in the image, and so is saved with the image. It is
also recorded in the Change List.

Assigning Definitions to Packages


In general, all new definitions should be assigned to a package. You can,
however, for temporary code, assign it to (none) rather than to a named
package.
As described elsewhere in this document, you specify the package
whenever you create a new class, name space, or shared variable, either
by selecting the package in the creation dialog, or by selecting the
package when you accept an edited creation template.
If you have unpackaged code, such as code that you initially assigned to
(none), you can assign it to a package at a later time. Select the item and
then choose Move in the <Operate> menu. Depending on the item
selected, you have several submenu selection. For classes and name
spaces, you can move:

Application Developer’s Guide 143


Chapter 7 - Managing Smalltalk Code

Definition to Package...
Prompt for a target package, and move the currently selected class
into it.
Selection to Package...
Prompt for a target package, and move the currently selected class
into it. Only the parts of the class that are within the current package
are moved. If multiple packages are selected, all parts of the class
within all selected packages are moved to the package. If the
navigator is set to view categories, the complete class (methods and
shared variables) are moved.
All to Package...
Prompt for a target package, and move the currently selected class
into it. The complete definition, including shared variables, is moved.
For protocols, methods, and shared variables, select Move ­ to Package...,
and select the package. All definitions in the selection are moved to the
target package.
You can reassign items to a different package using the same menu
commands.

Specifying prerequisites
Prerequisites are parcels, packages, or bundles that must be in the
system before the code unit is loaded. Before loading, a package or
bundle verifies that it’s prerequisites are loaded and, if not, loads them.
Packages and bundle prerequisites can be specified for either
development or deployment.
Deployment prerequisites are parcels, and are turned into parcel
prerequisites when the package or bundle is published as a parcel.
Development prerequisites may be either parcels, packages or bundles,
and are used when loading code from the Store repository. Often,
development prerequisites are a superset of package/bundle correlate of
the deployment prerequisites, including additional items such as
development tools and tests.
To specify prerequisites:
1 Select the bundle in the Refactory Browser package list and select
the Properties tab.
2 Select either the Development Prerequisites or Deployment Prerequisites
property.

144 VisualWorks
Packages and Bundles

3 (Optional) Click Compute Prereqs (in the Deployment Prerequisites property)


to have the system make an initial suggestion of prerequisites.
4 (Optional) Click Copy Deployment (in the Development Prerequisites
property) to copy deployment prerequisites into the development
prerequisites list.
5 Select a parcel, package, or bundle (only parcels are listed for
deployment prerequisites), and click >> to move it to the Prerequisites
list. Code units must be loaded to appear in the list of available units.
6 To remove items from the prerequisite list, select the item and click
<<.
7 When done adjusting prerequisites, click Apply.
For development prerequisites, you can specify that the prerequisite is a
package, a bundle, a parcel, or any of those. You can, using this item,
select a loaded package or bundle, select #parcel, and the item will be
entered as a prerequisite parcel with the same name. Make sure that the
parcel is created before you try to load the package or bundle again.
You can also specify a version string. Only the version number should be
listed, excluding the user name. For example, to load only version 1.1 of a
package, enter “1.1” in the Version: field, not “1.1,bruce”. A parcel also
may have a version string, if the string was assigned to the parcel before
publishing.

Suppress warnings
A package’s (or bundle’s, or parcel’s) warning suppression action is a
one-argument block, where the argument is the name of a prerequisite.
The block suppresses the absent class warnings, that is, the a warning
about an attempt to add code to a non-existent class. It does so on a per
prerequisite basis, so you can suppress warnings for selected
prerequisites.
The block must return true for any prerequisite for which warnings should
be suppressed. For example, to suppress only warnings for MyPrereq, you
could enter:
[ :prerequisiteName |
prerequisiteName = ‘MyPrereq’ ifTrue: [ true ] ]
To suppress warnings for additional prerequisites, simply add them to the
test.
The warning suppression block is run before any of the package code is
loaded. Consequently it should not mention any code in the package.

Application Developer’s Guide 145


Chapter 7 - Managing Smalltalk Code

The mechanism is limited. For example, if a prerequisite loads another


prerequisite that raises warnings, the block will not suppress those.

Specify prerequisite version


A prerequisite version string can be specified in the prerequisite property,
and is adequate if a specific version number is required. For more
general version control, such as to allow a range of versions, create a
three-argument block in the Prerequisite Version property.
[:parcelName :versionString :requiredVersionString |
booleanExpression]
The block arguments ar the name of a prerequisite parcel being loaded,
its version string, and the version string specified in the prerequisite
property.
The block should answer true if the version is acceptable, and loading
continues. Otherwise the loader will continue to search for another parcel
of the same name with a different version. For example, this will load
versions greater than the required version:
[:parcelName :versionString :requiredVersionString |
versionString >= requiredVersionString ]

Managing bundle content


Bundles are used to collect and organize packages and other bundles.
Bundles are used to make loading packages more convenient, allowing
for flexible configurations, and also for assembling the contents of
deployment parcels out of smaller packages.
Creating and arranging bundles
A bundle provides a convenient way for you and your team to publish,
load, and merge the project packages as a set.
To create a bundle:
1 In the Refactoring Browser package list, select Local Image for a top-
level bundle. For a new sub-bundle, select the parent bundle.
2 Select Package ­ New ­ Bundle... to open the Bundle Specification
Editor.
3 In the editor, enter the name for the new bundle.
4 Select packages and/or bundles to include in the new bundle, and
click the Add to bundle contents button.

146 VisualWorks
Packages and Bundles

5 Arrange the load order of packages.


The Specification Editor lists bundles and packages in their load
order. If any definition in one package refers to a definition in another
package, then the referring package should be listed first.
To change the load order for an item, select it and move it using the
up and down buttons.
6 Click the Validate button to verify that the specified order will load.
Validating creates a list of packages that the bundle will load, and
verifies that, in the resulting load order, that each namespace and
class required by each package is either:
• loaded by the package or a package earlier in the ordering, or
• not loaded by any package later in the ordering.
If so, then the package is valid. It makes no attempt to validate
definitions that are not loaded by any of the packages, since they are
outside of the bundle’s control.
Make further adjustments as necessary.
7 When the bundle is complete, click Accept.
This creates the bundle in your image. It will be created in the database
when you publish it.
Editing a bundle specification
To modify the contents of a bundle, use the Bundle Specification Editor,
just as you did for creating the bundle. To open the editor:
1 Select the bundle in the Refactoring Browser package list
2 Select Package ­ Edit Bundle Specifications...
3 Move packages and bundles into or out of the Bundle contents list.
4 Arrange the load order of by selecting a package or bundle and
clicking the up or down button.
5 Click the Validate button to verify that the specified order will load, to
check for conflicts.
6 When the bundle is complete, click Accept.

Application Developer’s Guide 147


Chapter 7 - Managing Smalltalk Code

Specify prerequisites
Prerequisites are parcels or packages that must be loaded before the
bundle is loaded. Prerequisites can be specified for development and for
deployment. Often, development prerequisites are a superset of
deployment prerequisites, including additional items such as
development tools and tests.
Deployment prerequisites are converted to parcel prerequisites if a parcel
is published from the bundle. Otherwise they are ignored.
To specify prerequisites:
1 Select the bundle in the Refactory Browser package list and select
the Properties tab.
2 Select either the Development Prerequisites or Deployment Prerequisites
property.
3 (Optional) Click Compute Prereqs to have the system make an initial
guess at the necessary prerequisites.
4 Select a parcel or package (packages are only available for
development prerequisites) and click >> to move it to the Prerequisites
list.
The item must be loaded in the system to use this method, but it
usually is if you have been doing development. To add a prerequisite
that is not currently loaded, enter its name in the entry field below the
parcels/packages list and click Add. You may also specify a version
number for the parcel/package.
5 To remove items from the prerequisite list, select the item and clicking
<<.
6 When done adjusting prerequisites, click Apply.
Suppress warnings
A package’s warning suppression action must be a block taking one
argument, the name of a prerequisite parcel being loaded. The block
should answer true if any warnings caused by loading the prerequisite
should be suppressed.
The warning suppression block is run before any of the parcel's code is
loaded. Consequently it cannot and should not mention any code in the
parcel.

148 VisualWorks
Parcels

Specify prerequisite version


A Parcel's prerequisite version can be specified as a code block on the
Prerequisite Version page of the bundle’s properties.
The block is a block taking three arguments, the name of a prerequisite
parcel being loaded, its version string, and the version string defined in
the package’s list of prerequisites.
The block should answer true if the prerequisite’s version is acceptable,
and should be loaded. Otherwise the loader will continue to search for
another parcel of the same name with a different version.
Bundle load and unload actions
Action blocks can be set to be evaluated at several stages of loading and
unloading parcels or packages by the bundle: preread, preload, postload,
preunload, postload, and presave. These are all listed as properties of
the bundle. View the help for each action for more information, and
browse the Store bundles for examples.

Parcels
Parcels are the component deployment technology for VisualWorks,
providing a fast object loading mechanism especially suited to deploying
Smalltalk code. All standard VisualWorks add-in components are
provided as parcels. The System Browser provides the facilities for
browsing, creating, modifying, maintaining, and loading parcels.
Parcels provide the following features:
Source code storage
Parcels are saved in two files. Parcel files containing compiled code
in a binary format have a .pcl extension, and files containing the
corresponding source code have a .pst extension. (Note: Despite
the superficial resemblance between .pst source files and .st file-
out format files, .pst files do not “file-in” properly. They are strictly
source files for the .pcl binaries. They can, however, be browsed in
the Change List for comparison with a loaded parcel by viewing
differences between the system and an opened file.)
Partial loading
Partial loading is a powerful new technique for structuring code as
logical components rather than physical components which other
Smalltalk loading technologies impose. See “Partial Loading” on
page 161 for more information.

Application Developer’s Guide 149


Chapter 7 - Managing Smalltalk Code

Override unload support


Parcels remember any methods and classes they replace on load,
and restore these methods on unload. See “Managing Overrides” for
more information.
Save, load, and unload actions
Parcels can have pre-load, post-load, pre-save and pre-unload
actions. These user-defined actions are invoked on load, save, or
unload, and can perform arbitrary actions as required. See
“Guidelines for Clean Loading and Unloading” on page 164 for more
information.
Prerequisites and autoloading
Parcels can include the names of prerequisite parcels, which are
automatically loaded when the requiring parcel is loaded. See
“Defining Parcel Prerequisites” on page 160 for more information.
Shape-change tolerance
One can load Parcels containing both code and objects whose class
definitions differ from the current system versions. See “Shape
Change Tolerance” on page 167 for more information.

Loading and Unloading Parcels


Using the Parcel Manager
During development, you can load parcels using the Parcel Manager:
1 In the Launcher window, select System ­ Parcel Manager.
2 Select either one of the Suggestions or Directories containing parcels
that are available for loading.
The parcels contained in the selected category or directory are
displayed in the upper-right pane of the Parcel Manager tool.
3 Select the name of the parcel you with to load, and choose Load from
the view’s <Operate> menu.
You may select several parcels to load by holding the <Shift> key.

150 VisualWorks
Parcels

4 Frequently, a dialog appears stating that some code will not be


installed until later, and asking for the action to take:

Normally, click yes to all, to continue loading the parcel. This is normal,
and has to do with the partial loading facility of parcels (see “Partial
Loading” below for more information).
Briefly, by clicking yes or yes to all, you permit code that cannot
currently be loaded to be placed in the Undeclared name space until
the prerequisites for its loading are satisfied. Usually these are
satisfied by later code in the same parcel or in another parcel you are
loading. When the prerequisites are satisfied, the code is loaded
automatically.
The parcel then loads.
As a short cut for loading parcels that are not in a defined parcel path,
you may select File ­ Load Parcel File... . A standard file dialog prompts for
the location of the parcel file.
To unload a parcel, highlight its name in the parcel list and select Unload
from the <Operate> menu.
Using the System Browser
When viewing parcels in the System Browser, you may use Load... on the
navigator’s <Operate> menu. Note that this menu item appears only
when the browser is viewing parcels.
If no parcels are selected in the browser, you are prompted for the name
of a parcel to load.
Follow the remaining steps as described in the preceding section.
To unload a parcel, select the parcel in the browser, and select Unload
from its <Operate> menu.

Application Developer’s Guide 151


Chapter 7 - Managing Smalltalk Code

Loading Parcels Programmatically


Your application may load parcels dynamically as needed. For example,
when your application’s user starts a new tool or opens a new window
within your application, your application may load the parcel containing
that tool or window.
The following line of code loads a parcel from a parcel file called
[Link]:
Parcel loadParcelFrom: '[Link]'
Similarly, when the parcel is no longer needed you may unload it:
Parcel unloadParcelNamed: '[Link]'
When deciding whether to use these and similar messages (browse
Parcel for the full API), consider the following:
• how to handle a load request if the parcel is already loaded: reuse
the already-loaded parcel or reload the parcel from a file.
• how easily and regularly you need to replace your application’s
parcels with new, up-to-date parcels. Frequent updates may argue in
favor of dynamic loading.
• how quickly your application should respond. There is time overhead
incurred by dynamic loading and unloading.

152 VisualWorks
Parcels

Parcel Search Path


The parcel loader searches for parcels on the parcel search path. You
can view and change this path using the Settings Tool (System ­ Settings
on the Launcher menu), on the Parcel path page.

To add a path to the list, enter it in the space provided and click Add. The
$(VISUALWORKS) prefix matches the VisualWorks home directory. You
can also specify a full directory path.
To change the search order, select an entry and drag it up or down in the
list. Directories are searched from top to bottom.
To delete a directory from the search path, select it and click Delete.
To edit an entry, select it, edit it in the entry field, and click Change.
When you are finished making changes to the parcel path list, click Accept.

Note: The parcel path is saved with the image. To make the path
permanent, you must save your image.

Application Developer’s Guide 153


Chapter 7 - Managing Smalltalk Code

Managing Parcels
Loading and unloading parcels is generally done with the Parcel Manager
tool (for details on the Parcel Manager, see “Parcel Manager” on
page 44). Virtually all management of code inside parcels is performed
using the System Browser.
To view the loaded parcels within the Browser, select Browser ­ Parcel.
The browser’s menus change to activate parcel maintenance activities.

The browser’s navigator can be used to show only the definitions that are
in the current parcel. Use the <Operate> menu in the parcels list.
Parcel Condition Indicators
Text formats are used in the Browser to indicate various states of code
with respect to parcels.

Bold Marks any class or method that contains something


defined in the selected parcel.
Red Red text identifies items defined in more than one
parcel, as a warning about potential conflicts.

154 VisualWorks
Parcels

In the parcel list, parcels have characters following their name indicating
the parcel’s state:

* the parcel is “dirty”; it has been modified in some way


< the parcel has been loaded
! the parcel has unloaded code (see PartialLoading)
+ the parcel overrode previously loaded definitions upon
loading
- the parcel contains definitions that have been
overridden by other loaded code

Creating a New Parcel


To create a new parcel:
1 Open a System Browser and choose the parcel list view (select
Browser ­ Parcel).
2 In the <Operate> menu in the parcel list, or in the Parcel menu, and
select New... . A dialog prompts you for a name for the parcel.
3 Enter the name of the new parcel and click OK.
The parcel name may be any string, except that leading and trailing blank
spaces are stripped, and any embedded series of blank spaces is
reduced to a single space. Parcel names must be unique within the
image.
Since an application is typically composed of several parcels, you may
want to create a few initial parcels before beginning. For example, make
one as the core parcel, which will contain foundational classes and
methods for your application. Then create a parcel for the application
model and UI code, and another to hold the domain model code. Select
names that indicate the parcels’ relations roles in the application.
Adding and Removing Definitions
When you create new classes or methods while a parcel is selected in
the browser’s parcels list, they are added to the current parcel.
Definitions that don’t belong to any parcel may be viewed by selecting
Unparceled in the parcels list; this is a kind of pseudo-parcel.
To add an existing definition to a parcel, select the item to add. Then,
using the <Operate> menu for the list containing the item, select
Move ­ to Parcel... . In the case of a category, class, or protocol, all of its
members are added to the parcel.

Application Developer’s Guide 155


Chapter 7 - Managing Smalltalk Code

Name spaces are added separately from other definitions. If you parcel
code that is defined in your own name space, make sure the name space
is parcelled as well. Select the name space in the class/name space list.
Then choose Move ­ to Parcel... from the class/name space list
<Operate> menu. Adding a name space to a parcel adds only the name
space definition, not the shared variables defined in it.
If a definition is already contained in one parcel, but you want to move it
to another, select the definition and then select Move ­ to Parcel... in its
<Operate> menu. Then select the destination parcel from the list of
currently loaded parcels.
To remove a definition from a parcel, select it and choose
Move ­ to Parcel... . Then, move the definition to Unparceled.

Saving a Parcel
When you have made changes to a parcel, it is considered dirty until you
save it. Saving a parcel writes all definitions and objects in the parcel to
the external files described earlier and clears the “dirty” indicator.
To save a parcel, select Save from the parcel list’s <Operate> menu. The
Parcel Save dialog is displayed:

156 VisualWorks
Parcels

In the entry field, enter the name for your parcel. The parcel file is created
if necessary, and given the name you specified with a .pcl extension
appended. Long file names are supported, including spaces and periods
in the name. Do not specify the extension, because VisualWorks will
append another.
If the save source file box is checked, sources are saved in a [Link] file,
where name is the name you specified for the parcel.
The hide source on load option has no effect in the current environment, but
will cause source code to be hidden when this parcel is loaded into an
image. This is a deployment option, and should not be used during
development.
The pad source option is needed only for huge parcel files, for efficiency of
the storage mechanism. Since huge parcels are generally a bad idea,
leave this unchecked unless you know you need it. (See the
SourceFileManager class comment for details.)
If you save with sources to a parcel that is currently loaded, you need to
check republish, or save to a different file name. When a parcel is loaded
with sources, the sources are added to the system sources. Writing to
this file would corrupt the sources file. Republishing effectively creates a
new (temporary) parcel and sources file, unloads the old parcel, replaces
the old parcel with the new one, and reloads the parcel. This keeps the
source file and the image consistent. Republishing does not hide
sources, even if you have hide source on load selected.
On republish the system also checks to see if there are any methods that
have their source in the .pst file but have been moved out of the parcel.
It then rescues this source by writing it to the changes file. Source rescue
also happens on unload and empty. This helps prevent you from losing
any source code.
After saving the parcel, you will be prompted to save the image to ensure
that the source file loaded into the current image is the same as the file
on disk. This is not necessary if you load the parcel each time you work
with it, rather than saving the image with it attached.

Caution: If more than one image is saved with a parcel


loaded, saving the parcel will make sources out of sync with
the other image(s). In this situation, do not save the images
with the parcel loaded.

Application Developer’s Guide 157


Chapter 7 - Managing Smalltalk Code

Managing Parcel Contents


For parcels to work well, they need to be organized carefully. The
difficulties are minimal if your entire application is in a single parcel, but
for large applications this does not make good use of parcels. For more
complex parcel structures, care is required especially in connection with
dependencies between parcels.
The System Browser makes many organization tasks easy. Classes and
methods can be added to parcels, or moved between parcels, by simple
drag-and-drop operations. Other operations are facilitated by utilities
accessible on the <Operate> menu in the parcels pane of the System
Browser.
Deciding What to Parcel
Dividing an application into parcels is as much an art as a science. Your
goal should be to create parcels that are small enough to be distributed
and loaded quickly.
When parceling an application think about how you want to load the
parcels into your deployment image or running application. Are there
parts that must always be present? Are there parts that should start up
together? Are there some features that might be used infrequently and
could be loaded only when needed?
Keep in mind dependencies between parts of your code. Be conscious of:
• Subcanvases.
• Embedded and linked data forms.
• Inherited behavior.
• Resources such as bitmaps that are used from a central location.
• Class variables that are used by other classes. For example, if one of
your classes keeps the name of the application’s working directory in
a class variable, it should be loaded first.
Dangling References
While developing an application, you may create methods that are
referred to by objects in a parcel, but that are not themselves in that
parcel. The objects referred to by these methods may be unparcelled, in
other parcels loaded into the system, or in unloaded parcels. These are
referred to as dangling references.
To browse these references, select one of the commands on the
Parcel ­ Browse menu: Extensions of Defined Classes, References to Defined
Classes, or Subclasses of Defined Classes.

158 VisualWorks
Parcels

A method browser opens on a list of methods. The list shows methods


that are not in the parcel but references a class that is defined in the
parcel. Most commonly these are unparcelled methods defined in a
parcelled class, but they may be references to a class in the parcel from
methods in any other class as well. This list can help you track down
definitions missing from the parcel.
Finding Unparcelled Methods
You may well create methods outside of parcels altogether that should be
included in one of your parcels. Two utilities are provided to help you
locate such methods: Browse ­ Unparcelled methods used exclusively and
Browse ­ Unparcelled methods defined in classes.

Since most of the methods in the system are not defined in parcels (such
as base system methods), a list of all unparcelled methods would not be
helpful. These lists filter the collection of unparcelled methods to just
those probably of interest to your parcels.
It is possible that your parcel is the only user of some unparcelled method
that is defined in the base system or saved in the image, so don’t
automatically add any reported method to your parcel. The utilities
display any candidate methods in a method browser. Examine the
methods carefully before deciding to add them to your parcels.
Restrictions on Parcel Contents
A class’s instance and class side definitions must be contained in the
same parcel; they cannot be broken apart. Loading a class definition in a
parcel does not overwrite a class definition already in the image.
(To change a class definition, use a pre-load action.)
Named objects have the following restrictions:
• Named objects cannot be instances of the following classes:

CDatum Context Controller


Exception ExternalInterface GraphicsContext
GraphicsDevice GraphicsHandle GraphicsMedium
LensContainer LensGlobalDescriptor LensSession
OSHandle Process Semaphore
Signal VisualPart WeakArray

• Named objects cannot be block closures that have associated stack


contexts.

Application Developer’s Guide 159


Chapter 7 - Managing Smalltalk Code

Defining Parcel Prerequisites


Parcels can define prerequisite parcels as name/version pairs. Define
deployment and/or development prerequisite parcels using the Parcel
Properties tool (when a parcel is selected in the browser’s navigator view,
click on the Properties tab for the code tool).
Development prerequisites are usually a superset of deployment
prerequisites. You can define prerequisites for deployment, and then
simply copy them to use as prerequisites for development. Care should
be taken when defining prerequisites (for details, see “Guidelines for
Clean Loading and Unloading”.)
Prerequisites as used as follows: on loading a parcel, the Parcel Loader
searches along the parcel path for parcel (.pcl) files that have been
specified as its prerequisites. For each prerequisite, the first parcel file
encountered along the path is loaded. Parcel prerequisites are also used
to order parcels for unloading.
Each parcel can also define a prerequisite version block which it can use
to select appropriate versions of prerequisite parcels.
A parcel’s prerequisites are shown in the Parcel Manager when the
parcel file (.pcl) is selected, and also in the parcel summary.
Defining Deployment Prerequisites
Deployment prerequisites are defined using the System Browser:
1 Choose the parcel in the browser’s navigator view, select the
Properties tab in the code tool, and then select Deployment Prerequisites in
the Properties tool.
2 In the Properties tool, add all prerequisites to the Prerequisites list, as
follows:
a Select the parcels you wish to include as prerequisites from the
Available Parcels list, and click on the >> button (you can select
several parcels to add at once by holding down the Control key).
b Alternately, you can compute the prerequisites automatically, by
clicking on the Compute Prereqs button.
c To include a parcel that is not shown on the list, enter its name
in the input field below the Available Parcels list, and click on the Add
button.
The list of Prerequisites can be edited using the >> and << buttons.

160 VisualWorks
Parcels

3 Optionally, specify a Version that must be matched to load the parcel.


The Version can be an arbitrary string. By default, it is empty.
4 Click on Apply to save the prerequisite definitions.
Defining Development Prerequisites
Development prerequisites are defined using the System Browser:
1 Choose the parcel in the browser’s navigator view, select the
Properties tab in the code tool, and then select Development Prerequisites
in the Properties tool.
2 In the Properties tool, add all prerequisites to the Prerequisites list, as
follows:
a Select the parcels you wish to include as prerequisites from the
Available Parcels list, and click on the >> button (you can select
several parcels to add at once by holding down the Control key).
b Alternately, you can use all the deployment prerequisites for the
same parcel, by clicking on the Copy Deployment button.
c To include a parcel that is not shown on the list, enter its name
in the input field below the Available Parcels list, and click on the Add
button.
The list of Prerequisites can be edited using the >> and << buttons.
3 Optionally, specify a Version that must be matched to load the parcel.
The Version can be an arbitrary string. By default, it is empty.
4 Click on Apply to save the prerequisite definitions.

Partial Loading
Parcels, like other Smalltalk code delivery mechanisms, add classes and
methods to the system. Existing mechanisms, such as file-in, require that
the superclasses of any installed classes, and the classes of any installed
methods are present in the system. This frequently forces one to
decompose a logical component that spans a number of other
components into sub-components.
Parcels eliminate this problem by allowing partially installing definitions
from a parcel. If a parcel contains a class that requires a superclass
which is not present in the system, or a method that requires a class
which is not present in the system, the class or method is not installed.
Instead, these classes and methods are added to either the
uninstalledClasses or uninstallededMethods set for the parcel.

Application Developer’s Guide 161


Chapter 7 - Managing Smalltalk Code

Whenever a parcel is loaded, parcels with uninstalled code check


whether the required absent classes have now been loaded. If so, the
parcel installs the class and method definitions.
Classes that are installed in this way are sent postLoad: to initialize them
when they are installed. They can distinguish being installed after partial
loading because the parcel argument to postLoad: will answer true to
isLoaded. (See “Guidelines for Clean Loading and Unloading”.)
Parcels with unloaded code appear in the System Browser’s parcel list
with an exclamation mark after the name.
You can browse a parcel’s unloaded code by opening the Change List
and selecting File ­ Display Parcel... . The names of unloaded classes and
methods are also listed in a Parcel’s summary.
Limitations
Currently the uninstalled code mechanism works only for loading, not
unloading. If parcel A is extended by parcel B, then unloading A does not
cause B’s extensions to A to revert to unloaded code. Hence a
subsequent reload of A will not see B’s extensions.
Saving a parcel with uninstalled code would lose the uninstalled code. A
dialog notifies you of the condition, and the save is canceled, so you do
not lose code silently.
To correct the condition, you should load any prerequisite parcels until all
uninstalled code has been installed. Typically loading a parcel's
development prerequisites, which are a superset of its deployment
prerequisites, will load the necessary code. See “Defining Parcel
Prerequisites” above.

Controlling Parcel Load and Unload Behavior


Parcels can have user-defined preload, postload, presave, and preunload
actions, which provide a high level of control over the behavior of parcels.
Saving
Two mechanisms are provided for performing pre-save actions on a
parcel:
• Before saving a parcel, all classes defined in the parcel are sent a
preSave: message, with the parcel as argument. The default behavior
of preSave: is to do nothing, but classes can override this behavior.
A typical use of preSave: is to add named objects to the parcel’s set of
named objects. A named object is added by sending addObject:named:

162 VisualWorks
Parcels

to the parcel. The object can then be retrieved in the corresponding


postLoad: method.
• You can also define a pre-save block for the entire parcel, which can
perform any action. By default there is no pre-save block for a parcel.
To create a pre-save block:
1 Choose the parcel and select the Parcel Properties tool (click on the
Properties tab to select the code tool).
2 Select Pre-save Action, and edit the pre-save block definition.
3 Select Accept to save the new block definition.
Loading
The load sequence of a parcel is as follows:
1. Any prerequisite parcels are loaded.
2. The parcel’s pre-load action is performed, if defined.
3. The objects in the parcel are installed into the system.
4. Every class defined in the parcel is sent the postLoad: message with
the Parcel as an argument.
5. The parcel’s post-load action, if defined, is executed.
A pre-load action is typically used to initialize any undeclared variables
used by the code prior to its initialization. Class variables are handled as
shared variables, so can be added to a parcel and do not need to be
defined in the post-load action.
To create a pre-load action, open a System Browser and choose the
parcel. Select the Parcel Properties tool (click on the Properties tab to
select the code tool). Select Pre-load Action, and edit the pre-load block
definition. Select Accept to save the new block definition.
The default behavior of postLoad: (step 4) is to run the class’s initialize
method, if it has one, but subclasses can override postLoad: to perform
any action. A typical override is to retrieve objects saved in the parcel’s
named objects set by the class’s corresponding preSave: method.
Once all code has been installed and initialized, the parcel’s post-load
action, if defined, is run taking the parcel as an argument. This method
can be on an arbitrary class and have an arbitrary selector. We strongly
recommend that it be on a class defined by the parcel.

Application Developer’s Guide 163


Chapter 7 - Managing Smalltalk Code

To define a post-load action, open a System browser and choose the


parcel. Select the Properties tool (click on the Properties tab to select the
code tool), select Post-load Action, and edit the post-load block definition.
Select Accept to save the new block definition.
The post-load block can perform any action, but is typically used to open
initial applications, display installation banners, declare class variables,
and import objects saved by the parcel’s presave block.
Unloading
Before a parcel is unloaded, its pre-unload method, if defined, is called
with the parcel as its argument. This method can be in an arbitrary class
and have an arbitrary selector. We strongly recommend that it be in a
class defined by the parcel. This method can take whatever action is
required, but is typically used to remove any class variables added by the
parcel’s post-load method and to close any applications defined by the
parcel.
Removing a parcel that defined an open application is likely to break the
system, because the open application is obsolete, is unlikely to function
correctly, and may be impossible to close. ApplicationModel will ask the
user if it is OK to close any open applications defined by the parcel. Most
of the VisualWorks parcels provide examples.
To define a pre-unload action, open a System browser and choose the
parcel. Select the Parcel Properties tool (click on the Properties tab to
select the code tool), select Pre-unload Action, and edit the pre-unload block
definition. Select Accept to save the new block definition.
Limitations
Parcel actions are clearly open to abuse. Use them carefully, and keep
actions as simple as possible.
Guidelines for Clean Loading and Unloading
For parcels to load and unload cleanly, observe the following guidelines.
Organize parcels in a tree and do not cross-reference
Unloading parcels with cross-references will create undeclared
references, and cause problems for clean unloading. To avoid cross-
references, arrange the core parcel of your package so that it refers
only to classes in the base or other standard parcels, and to the
classes it defines. Within the core parcel, do not refer to classes
defined in parcels that require the core parcel as a prerequisite.
Cleanly loading parcels with cross-references is not a problem

164 VisualWorks
Parcels

Parcels support extension methods, so you don’t have to put an


entire class in a single parcel but can decompose it across a number
of parcels. The UIPainter is an example; all the painter-related
functionality for Specs is separated out from the builder related
functionality, allowing the painter to unload cleanly.
Order prerequisites carefully
Classes must be ordered correctly if they are to initialize without
error. When a class is initialized, all of the classes that it depends on
for its initialization must be themselves initialized. VisualWorks orders
classes automatically given information defined in the class’s
prerequisitesForLoading method. See ClassDescription>>
#prerequisitesForLoading and Class>>#prerequisitesForLoading for the
defaults. These include a class’s superclasses and the defining
classes of any objects used by a class. But this may not be sufficient.
For example, in the Lens there are a number of classes that require
other unrelated classes to be initialized before they can be.
LensGraphView requires both LDMRelationship and LDMPerspective to be
initialized first. Hence LensGraphView class’s prerequisitesForLoading
includes these classes in its default set of prerequisites.
If a class is not properly loaded, you will get a walkback when you try
to load the parcel. If this happens, use the debugger to trace back the
chain to CodeReader>>installInSystem, where it is sending postLoad: to
the classes in the parcel in the order defined by their prerequisites.
Run the required class initializers by hand until you can proceed
successfully, noting which class caused the error and the class it was
trying to use in its initialize. Once the parcel has loaded, you can add
or extend the offending class’s prerequisitesForLoading method and try
again. Soon you’ll get your parcel to load smoothly.
Unfortunately, due to the way the system sorts classes, a parcel that
loaded cleanly once may fail to load in a different configuration. Again
the solution is to augment relevant prerequisitesForLoading methods.
Order classes carefully
To unload, classes must be ordered and have no references to
themselves or their instances. Unload order is the reverse of the load
order, as defined by prerequisitesForLoading.
When classes are removed from the system, they are sent the
obsolete message. The default behavior is to remove the class from its
superclass and nil all its instances fields. This may be insufficient to
cause the classes to be garbage collected. For example, the class
may be referred to in some collection or have been added as a

Application Developer’s Guide 165


Chapter 7 - Managing Smalltalk Code

dependent of some class (usually ObjectMemory, which is used to get


notification of image load/save/exit). The obsolete method should
remove references such as these made during initialization.
You can use SystemAnalyzer>>#obsoleteClasses in the Advanced Tools
System Analysis parcel to track down problems. The parcel also
contains ReferencePathCollector, which can be used to find the path of
references from global variables to any object and to obsolete
classes and their instances.
Check Undeclared
Take care to check Undeclared when you define, load, or unload a
parcel you’re developing. Eliminate references to declared variables
by restructuring your program.

Definition Overrides
A parcel imports classes to add methods to them. These methods we call
extension methods. Writing the class definitions to the file allows one to
check for shape changing compatibilities at the source level.
A parcel remembers any methods it overrides on load, and replaces them
on unload. This allows you to override arbitrary methods without the risk
of crashing the system when unloading the parcel that overrode some
vital method.
Parcels with overriding method extensions appear in the parcel list with a
+ after their name.
Parcels with overridden method definitions appear in the parcel list with a
- (minus) after their name.
If you try to save a parcel that some other parcel has overridden, the
overriding method would be saved, rather than the original overridden
definition. If you attempt to save a parcel with overridden definitions, a
notifier warns you of the situation, and the operation is cancelled when
you close the notifier. (Refer to “Restoring an Overridden Definition” and
“Removing an Overridden Definition” for clearing the override conditions.)
Note that packages behave differently, and can now save the overridden
definition.
To browse a parcel’s overridden extensions, select Browse ­ Overridden
extensions in the parcel list <Operate> menu. This opens both a Change
List in conflicts mode, displaying the previous versions, and a Method List
Browser displaying the current versions. Overridden extensions are also
listed in a Parcel’s summary.

166 VisualWorks
Parcels

If you attempt to remove an overriding extension method in the System


Browser, you will be prompted whether to restore the previous version. If
you redefine an unparcelled method, you will be prompted whether to add
the existing unparcelled method to the parcel’s overridden extensions.

Shape Change Tolerance


Shape change refers to the redefinition of classes that add or remove
instance variables, or make the class indexable on bytes or objects. This
causes the objects defined by the class to acquire or lose fields, or
“change shape.”
Parcels have a shape-change facility for instances and methods that tries
to adjust objects so they can still be loaded. If a parcel loads an object
whose number of instance variables has changed, it assigns the values of
variables with the same name, discards the values of missing variable
names, and leaves new variables nil. If a parcel loads a class that has
changed shape, for example, because its superclass has changed since
the parcel was defined, then the class’s methods will have their instance
variable offsets adjusted to reflect their correct positions.
A parcel can include a class definition alone, for purposes of changing
class shape. To do this, create the definition and simply add it to the
parcel. The Parcel Browser adds all method definitions, too, so you need
to remove these from the parcel, if there were any.
Limitations
There is currently no mechanism for the user to provide arbitrary shape-
changing code for loaded instances, as was the case for BOSS. This
limitation will be lifted in subsequent releases.
The system cannot cope with shape changes other than the addition or
removal of named instance variables. Changing a byte object into a
pointer object or vice versa will always break the system. This restriction
will not be lifted.
If two class definitions both change a class’s shape, the last definition
loaded will win. Definitions are overwritten, not merged, so, for example,
instance variables from two definitions are not both added.

Filing Parcel Contents In and Out


The Parcel Manager and System Browser each allow you to file out a
parcel. Filing out a parcel writes all of the class and method definitions in
the parcel to a file as source code. Filing out a parcel does not file out the

Application Developer’s Guide 167


Chapter 7 - Managing Smalltalk Code

parcel itself or the association between the parcel and its classes and
methods. Parcel contents that have been filed out can be filed in using
the standard file-in mechanisms.
To file in a set of definitions and associate them with a parcel, first create
a new, empty parcel and select it in the browser’s parcel list. Then choose
Build ­ Add File-In from the <Operate> menu and specify the .st file
containing the definitions to add. All of the definitions within it will be
added to the parcel and loaded into the system.

Working with Change Sets


Named change sets (or simply change sets) provide a “project-based”
view of changes you have made to the system. By using change sets,
you can keep the changes made for different applications or subsystems
separate, while maintaining a single development environment. This is
particularly useful if you work on multiple projects as the same time, and
want to parcel out or file out changes for the projects individually.
You manage change sets using the Change Sets List. In this tool, you set
the current change set and access operations on change sets, using the
menu options.
To open the browser, select Changes ­ Change Sets from the System menu of
the main VisualWorks window.

Selecting a Current Change Set


The change set list always has the Default change set, plus any change
sets that are defined in the image. If no change set is selected, or if Default
is selected, all changes go to the default change set. Otherwise, they go
to the selected change set.
To make a change set active or current, select it in the change set list, so
that it is highlighted. All changes you make to the system will then be
saved in that change set.

168 VisualWorks
Working with Change Sets

Unlike the previous project mechanism, you do not get a new visual
environment, but continue working with the same browsers and
workspaces. You do not have to enter or exit projects to switch change
sets.

Creating a New Change Set


To add a new change set, select ChangeSet ­ Add in the Lite Project
Browser, or select Add in the change set list <Operate> menu. Enter a
name for the change set in the prompter, and click OK.
To make this the current change set, select it so it is highlighted. Your
changes will then go to this change set.

Exploring Changes
Having made changes which you have assigned to a change set, or
project, it’s convenient to be able to review them. Three menu options in
the Change Set Browser ChangeSet (or <Operate>) menu to allow you to
review your change set’s contents.
Unlike the change list, change sets do not track, and these tools do not
show, the evolution of those changes. Instead, the record and report the
affected parts of the system, so you can use the file out as command to
store your work in a set of disk files.
Also, in the spirit of summarization, the change sets do not separately
report changes to methods in a class that has been added in this change
set. When you file out the new class, its methods will be included anyway.
However, if you empty the change set or “forget” the class addition,
successive method changes are recorded.

Application Developer’s Guide 169


Chapter 7 - Managing Smalltalk Code

Browse
This menu pick opens a message browser on messages changed and
recorded in the change set.

Edit
This menu pick opens an editor browser on the current change set. You
can change the selected change set in the Change Set List, and the
editor will update to show the changes for that change set.

170 VisualWorks
Working with Change Sets

The top left pane lists classes that have changed in this change set. The
check boxes at the bottom of the window indicate the kind of change
recorded.
The top center pane lists methods that have changed for the selected
class, if any are recorded. Methods for classes whose definitions are in
the change set are not included, since a file-out will include them anyway.
The radio buttons at the bottom indicate the kind of change recorded.
You can edit the definitions either in this editor, or select browse or spawn in
the <Operate> menus to open a browser on the item. To remove just this
one item from the change set, select forget in its <Operate> menu.
Inspect
This menu option opens an inspector on the change set. Here you can
perform the usual inspector options.
Updating the Changes Display
To update an open Change Set browser after making a change to the
system, select update in the <Operate> menu for a browser pane.

Saving Changes
Change sets are typically used to identify sets of changes that can then
be distributed as file-out format files. Change sets are saved in source
code format, and so can be browsed in the Changes List.
To write out all the changes in a change set, select the change set and
select ChangeSets ­ File out as... . You will be prompted for a file name.
As a shortcut, to file out all save sets, select ChangeSets ­ File out all... .
You will be prompted for a directory name. The directory will be created, if
necessary, and a separate file-out file for each change set is written to it.
You can file out a single method by selecting it in the Change Set Editor
(ChangeSet ­ Edit), then selecting File out as... in the <Operate> menu.
Note that, when filing out a change set that includes defining a class, all
subsequent changes made to methods in that class are also (implicitly)
assigned to the change set. This is true even if a different change set is
“current” when those method changes are made. A file-out the first
change set will include the method definitions.

Application Developer’s Guide 171


Chapter 7 - Managing Smalltalk Code

Creating Install and Remove Scripts


To assist in installing and removing the code filed-out from a change set,
you can create import and removal scripts. Simply select the change set
in the Change Set List, and select either ChangeSet ­ Import Script or
ChangeSet ­ Remove Script.
To be effective, the scripts must be created from the change set while it is
exactly the same as when the file-out was created.

Change Initialization Ordering


Change Sets now have an initializationOrder instance variable which can
be used to override the default class initialization ordering derived from
the class hierarchy. This is useful in some complex change sets where
initialization order is important.

Clearing a Change Set


When a particular change or collection of changes is secure, so that you
do not need to continue to hold it in the change set, you can remove it.
For example, after filing-out a set of changes, you can purge the whole
set, since you can restore them from the file-out file.
To empty all changes from the Change Set for the active project, select
ChangeSet ­ Empty in the Change Set List.
To remove a single change from the change set, select the change in the
Change Set Editor, and select forget in the item’s <Operate> menu.

Using the Change List


VisualWorks maintains and records a running list of changes made to the
system, in the changes file. By default, the file has the same name as
your image but with a .cha extension. The changes file is saved in
source-code format.
The changes file records anything that changes the state of the system,
such as: changes resulting from loading parcels and filing in code; added
and modified class and method definitions; special doIts and related
operations.
Changes are recorded as they are made, preserving a record of changes
even if you exit VisualWorks without saving or if the system crashes. For
this reason, the changes file provides a sure way of recovering lost work.

172 VisualWorks
Using the Change List

To work with the changes file, VisualWorks has a Change List Tool that
allows you to build and manipulate a change list, which is based on the
contents of the changes file, change sets, and other file-in format files
containing descriptions of changes.

The Change List Tool


The Change List tool allows you work with a change list. It provides a
wide variety of operations for reading changes files, comparing the
contents of files to the system, filtering the display, and installing changes
into the system.
To open the Change List tool, select Tools ­ Change List in the Launcher
window.

The Change List window has three views. The view at top left displays a
list of the changes. Entries in the Change List generally identify the
affected object and the nature of the change, such as NotifierController menu
(add). When you select an entry, the affected class or method displays in
the text view as it existed after the change.
The top right-hand view provides on/off switches for filtering the contents
of the change list. Any combination of filter switches can be selected. The
switches filter the list based on the currently selected list item. The filters
have no effect if no item is selected, and so cannot be selected.

Application Developer’s Guide 173


Chapter 7 - Managing Smalltalk Code

For example, to display only changes that affect the same class as the
one affected by the currently selected change list entry, click on the class
switch. To further restrict the listing to identical entries, such as
NotifierController menu, click on the same switch.
Several operations using the Change List are described in this section.
For descriptions of menu items not covered here, refer to the online
VisualWorks Tools help.

Browsing a Change List


The Change List Browser is initially empty when it opens. This allows you
to select what set of changes you want to view, whether in the current
changes file or in some other file. To display changes, use one of the
following options in the File menu:
Read File/Directory
This option reads into the browser the contents of a changes file you
specify, or from all changes files in a directory you specify. If you
specify a directory, the contents are added to the browser in the order
read. To add files in a specific order, read them individually.
Recover Last Changes
This option reads in to the Change List Browser all changes to the
system since the last image save. Use this option to recover lost
work, such as from a a system crash.
Display System Changes
This option appends any changes in the current Change Set (project)
to the list of changes in the browser. Unlike the Change Set browser,
which displays only a summary, this shows the history of changes.
Display All System Changes
This option adds all changes in all Change Sets to the browser
display.
Parcels
This option adds changes from a given parcel that is present in the
system. This can be used to examine a parcel's unloaded code and
its overridden extension methods as well as normal code.

Reordering Items in the Change List


Some errors may be caused by the order in which changes were made in
the system. For example, one operation may require that an object be
initialized to a state, but the initialization was neglected or performed too
late. Rather than repeat the series of operations manually, the Change
List can be used to reorder and then replay the operations.

174 VisualWorks
Using the Change List

To change the order of operations, display system changes. Then select


an operation item to move, then right-click and hold on the item. Drag the
item up or down in the list, to an appropriate position and release (drop)
it.
You can now replay the operations to execute them in the new order.

Removing Items from the Change List


The Remove and Forget menus provide a large number of options for
selectively excluding items in the Change List for processing. For brief
descriptions of each of these, refer to the VisualWorks Tools help topics.
The Remove options mark items for removal from the current list of
changes. Marked items are shown in strike-out type style. Options allow
you to mark either individual items or large groups of items.
Once a collection of items are marked for removal, you can remove them
from the list. In the Forget menu, select either Forget these or Forget all, to
remove the marked items from the change list. The difference is that
Forget all removes even any marked items that are not showing at the
moment due to the filtering selections; Forget these only removes those
currently showing.
To clear removal markings, use the Restore... menu items in the Forget
menu.
Note that removing items only removes them from the current change list,
not from the change list file. You can always get back by re-reading the
changes file.

Resolving Conflicts with the System


Several options in the Change List tool help you assess the impact of a
set of file-ins on the current system. These facilities filter changes based
on their similarity or dissimilarity to the current system.
A major use of the conflicts view is to merge changes made by a
collection of files, and so construct a single file containing only the
desired changes. It can also be an aid in crash recovery, by filtering older
changes from a changes file.
Using the Conflicts Filter
Selecting the Show ­ Show conflicts splits the lower text view into two
adjacent text views (vertically or horizontally, set by Show­Conflicts­
Vertical view or Horizontal view). The left-hand or upper view shows the text
for the selected change. The right-hand or lower view shows the text of

Application Developer’s Guide 175


Chapter 7 - Managing Smalltalk Code

the corresponding system entity (method or class definition, class


comment or organization, etc.) or an explanatory message if this doesn't
exist.

The differences between the two texts are high-lighted. This gives you a
quick, graphic indication or what would be changed by filing-in a specific
change.
Turning off the show conflicts filter hides the conflict text view and returns
the change list view to its usual appearance.
Managing Conflicts
Several items on the Conflicts menu allow adding or otherwise processing
conflicts between the changes list and the system. The full set of menu
items are briefly described in the VisualWorks Tools Help. Here we
comment on a few of the more interesting options.
Add system conflicts
For each displayed change that has a version in the system with
which it conflicts, this option adds the corresponding system version
of the change to the change list.

176 VisualWorks
Using the Change List

Add originals
This option scans the system's source files (excluding the current
changes file) and, for each displayed change for which a
corresponding change exists in the sources file, add the sources file
version to the change list. This is useful comparing your changes
against the original sources.
Add to change set/Remove from change set
These options update the current change set to include or exclude
the changes in the change list, without filing in the changes. This is
useful when you have an old file-in representing a component that
you wish to extract from the system. This can be used together with
the System Browser's Parcel ­ Build ­ Add Changes and
Parcel ­ Build ­ Remove Changes options.

Change/Change Back Changes


Method changes check to see if they're filing in from the sources files. If
so, they set the new compiled method's source pointer to the sources file,
and remove the method from the current change set. This enables a
technique for working with methods that you frequently change and then
change back.
To revert changed-then-changed-back methods do the following:
1 Once you have a set of system changes, use Add originals to pull-in all
corresponding changes from the sources files. These appear after
the current changes.
2 While holding down the Shift key, choose Remove exact duplicates to
remove the changes in the sources files that are duplicates of the
changes further up the list. Holding the Shift key down causes
removal to happen at the end of the list rather than at the beginning.
3 Choose Remove ­ Exchange removed to select the set of source file
changes that match the current system.
4 Select Forget ­ Forget these, and turn on Show ­ Show conflicts and
Show ­ Show file to make sure that these changes are indeed on the
sources files and identical to the current versions in the system.
5 Select Replay ­ All from the top, and watch the transcript to see that
each filed-in method change says “in sources file.”

Application Developer’s Guide 177


Chapter 7 - Managing Smalltalk Code

Reverting to a Prior Version


During the course of development, a class or method may undergo
several changes. The Change List tool makes it easy to see the evolution
of, and to examine the details of, the code at any stage in its
development. This is particularly useful when you need to see a prior
version so you can change the code back.
To display the changes that have occurred since the last snapshot was
taken, select Recover last changes in the <Operate> menu of the list view at
the top. If you want to display changes that are in the Change Set, select
Display system changes instead.
Once you have displayed the change you want to revert to, select that
change and then select Replay selection in the <Operate> menu (or
Replay ­ This Change).
To revert to a whole collection of changes, read in the necessary changes
and set the filters to show exactly the changes you want to load. Then
choose Replay all.

Recovering from a Crash


If some change you made to the system causes it to crash, the Change
List provides a way to recover changes up to, but excluding the change
causing the crash. In this way it provides a powerful crash recovery tool.
To recover from a crash:
1 Launch the last saved image.
2 Open a Change List, and select File ­ Recover last changes.
3 Using a combination browsing and editing operations on the
displayed list, remove unneeded items that may have contributed to
the system crash.
This may involve a good deal of work, browsing the changes first and
understanding what ultimately caused the crash, which was probably
an interaction between several changes.
DoIts in particular are not usually necessary to recovering changes,
and may easily contribute to system instability. To remove all doIts,
select on, and then click the type checkbox. This filters the list to show
only the doIts. You can then select Remove ­ All to clear all doIts from
the list.
4 Once the list contains just those operations you wish to recover,
select Replay ­ All from the top, or another appropriate replay option.

178 VisualWorks
Using the Change List

Recovering Changes to a Clean Image


If your image file is damaged in a crash, you may need to recover your
changes into a clean image. By “clean image” we mean a copy of the
original [Link] that shipped with VisualWorks. If you have modified
this image file, you will need to start with one from the distribution media.
The technique described here uses the changes file ([Link])
related to the damaged image. You should back up this file before
proceeding. Then:
1 Backup your changes file.
2 Launch VisualWorks with the clean [Link] image file.
3 Load any parcels that were loaded in the lost image.
Parcel loading is not included in the .cha file, so they must be loaded
to ensure that code required by the changes is available.
4 Save this image to a new name, different from the name of the
damaged image.
If you use the same name as the damaged image, you will overwrite
the changes file you need for recovering. You will be able to rename
the original name later, after you have recovered your work.
5 Open a Change List (select Tools ­ Change List in the Launcher
window), and load the changes file using File ­ Read file(s)... .
If your changes file is large, reading the file may be slow. Be patient.
6 Remove doIts from the change list by selecting a doIt line, clicking the
Type check box to filter the list, then selecting Remove ­ All. Then,
select Forget ­ Forget these to remove the doIts from the list.
Remove ­ All marks all of the doIts for removal. To unmark one, select
it and choose Forget ­ Restore selection.
Remove at least doIts that you invoked from a workspace or browser,
since this might fail. In general, you may be able to remove all doIts.
7 Uncheck Type, to show all the remaining changes.
8 Examine the list, especially near the end, to see if there is a change
that might have caused the damage. If so, remove it from the list
using Remove ­ Selection and Forget ­ Forget These.
There are a variety of changes you might wish to remove. For
example, a method may be defined several times, which is okay as
long as the last definition is the one you want.

Application Developer’s Guide 179


Chapter 7 - Managing Smalltalk Code

9 Select Replay ­ All from the top, to restore all of your changes.
10 Save the resulting image.
At this point you have recovered your changes into the new image. Test it,
and if you are satisfied that it is stable, you may save it to the original
image name. Note that the old changes file will then be overwritten, so
you will not be able to repeat the process using it.
It frequently takes a few tries to get exactly what you want into the
restored image, so repeat the procedure until you have just what you
want.

Condensing the Change List File


In a large development effort, spanning months or years of programming,
the changes file can become very large. To condense it so that it contains
only the most recent change for each method, execute the expression
SourceFileManager default condenseChanges. Changes involving anything
other than a method—such as a class addition or redefinition—will also
be purged from the file permanently. It’s a good idea to make a backup
copy of the changes file before condensing it.

Changing the Change List File Name


By default, the change list is written to a file with the same file name as
the image file, but with a .cha extension. It is seldom necessary to use a
different file name. If you do need to change the file name, edit the file
name in the Settings Tool, Source Files page.

Filing Out a Set of Changes


When the code you want to share consists of fragments from many
different classes and categories, it may be more convenient to use the
Change List to Write file with the desired code. Begin by loading all
changes into the Change List tool, as described in “Browsing a Change
List” above.
Next, remove the irrelevant changes. For example, doIts are likely
candidates for removal because they rarely affect the image in a lasting
way. Also, remove duplicate entries, as when a method has undergone
several changes, and leave only the last entry in each case. Use
Remove selection and Remove all to mark one or more changes for deletion,
then use Forget to erase them from the list. Use the filter switches to
control the affected range of entries.

180 VisualWorks
Managing Overrides

For example, to remove all doIts, begin by selecting any doIt. Then turn
on the type switch so all of the doIts are listed. Select Remove all in the
<Operate> menu to mark them for deletion, then Forget to erase them.
Then turn off the type switch to see the remaining entries.
When the displayed list of changes is the desired set, select Write file in
the <Operate> menu and supply the name of a file in which to store the
code. That file can then be loaded into another image via the File in
command in a File Editor or File List.
Only the displayed changes are included in a Write file operation, so if it is
possible to define the minimum set of changes by using the filter switches
alone, it is not necessary to Remove and Forget the nondisplayed entries.

Note: When you write selections to a file, be sure to choose a file


name that is different from any file that has been read into the change
list. The change list maintains pointers to the code in the files that are
read in, and these pointers become invalid when you overwrite a file.

Managing Overrides
Code overrides occur when code loaded into the image from a parcel or a
package defines one or more items already defined in the image. In this
case, the newly loaded code overrides the existing code, and becomes
the code currently in the system.
The Override List tool provides a view on overrides very much like the
view the Change List provides on changes. Most of the operations
supported by the Override List are equivalent, and so will not be repeated
here (refer to “Using the Change List” above). There are a couple of
differences, though, in command behavior that we will cover.

Opening the Override Tool


The Override List differs from the Change List tool primarily in that it
focuses exclusively on overrides caused by loading parcels and
packages. There are a couple of styles of this list, invoked by different
menu selections.
To open the an Override List showing only the overridden definitions,
select Changes ­ Open Override List or Changes ­ Browse System Overrides from
the System menu in the Launcher. Browse System Overrides opens a list of all

Application Developer’s Guide 181


Chapter 7 - Managing Smalltalk Code

overrides currently in the system. Open Override List opens an empty list to
which you can selectively add parcels and/or packages containing
overridden definitions.
To compare the overridden and overriding definitions, select the parcel or
package to check in a browser and select:
• Parcel (or Package) ­ Browse ­ Overridden extensions, to browse
extension methods definitions that have been overridden, or
• Parcel (or Package) ­ Browse ­ Methods overridden by others, to browse
any methods defined in the parcel/package that have been
overridden by another parcel or package.
These options open comparison browser versions of the Override List,
putting the overridden and overriding definitions side-by-side for easy
comparison.

182 VisualWorks
Managing Overrides

Selecting Overrides
You can select which overrides are displayed, by selecting the relevant
parcels and packages. To list overrides related to a specific parcel or
package, select File ­ Display Parcel... or File ­ Display Package... in the
Override List. Then select the parcel or package to display in the
displayed list and click OK.
To list all overrides in the system, select File ­ Display System Overrides. All
definitions overridden by a parcel of package are then included in the list.
The check boxes at the right provide filters on the list, to help focus on
specific sets of conflicts. With all boxes unchecked, all conflicts are
shown. When any boxes are checked, only the items checked are shown.
To show conflicts, select Show ­ Show Conflicts. The different versions of
the selected item are then shown in separate panes, with conflicting code
shown in red.

Restoring an Overridden Definition


If a definition has been overridden, and you want to restore it as the
current definition in the system, use the Replay menu options. You have
the option to replay a single definition, all displayed definitions, or all from
the selected definition to the end of the list.
Once restored, the overridden package/parcel now “owns” the current
definition, and competing definitions are removed from all the overbidding
components. The parcels can now be saved, without the conflicts
blocking the operation. (See “Managing Overrides”.)

Removing an Overridden Definition


Alternatively, the overridden definition may be the one that should be
removed.
To remove a single overridden definition from a parcel or package, and so
to remove the conflict between defining parcels or packages, select the
definition in the list and choose Forget ­ Purge selection. The overrider now
owns the definition, and the components can be saved. Note that if the
overriding parcel/package is unloaded, the overridden definition will not
be restored. (See “Managing Overrides”.)
Other options are available for purging blocks of definitions. For example,
marking definitions using the Remove menu items, and then selecting
Forget ­ Purge these removes all of the selected definitions from their
components.

Application Developer’s Guide 183


Chapter 7 - Managing Smalltalk Code

Publishing Parcels and Packages with Overrides


Parcels and packages behave differently when publishing with overrides.
The issue is how to publish code that has been overridden. What
happens is:
• If a parcel contains an overridden definition, an attempt to publish will
fail, and a notifier is displayed.
• If a package contains an overridden definition, an attempt to publish
will succeed, although publishing binary is not allowed, and the
package will include its overridden code.
In a parcel, the result would be to publish the overriding code, and the
overridden code would be lost. Rather than publish under these
conditions, the operation is cancelled. To republish the parcel, you must
remove the override condition, either by removing the overridden
definition from the parcel, or by copying or moving the overriding
definition into the parcel.
In a package, the mechanism allows keeping the overridden and the
overriding code separate, and so the package can be published while
retaining its original (overridden) code. To keep the original code, simply
publish the package. To update the package with the overriding code, you
must copy or move the code into the package.
Since publishing a package in binary creates a parcel format file, which
cannot contain overridden definitions, the binary option is disabled if the
code contains an overridden definition.
Due to differences in how parcels are constructed, this difference is
unlikely to be removed in the future.

184 VisualWorks
8
Application Framework

The VisualWorks application framework greatly simplifies the task of


building an application. The basic framework separates UI objects, such
as windows and the widgets and menus they contain, from the domain
objects, which represent the elements and processes that the application
is modeling. The UI and domain are connected by an application model
which creates the UI from specifications, connects the UI to the domain,
and manages communication between the UI and domain during the
application run.
As with any object-oriented construct, the application framework consists
of objects that provide services to collaborating objects. This chapter
gives an overview of the main mechanisms in the application framework.
While this is useful information and will help make sense of how the
VisualWorks tools operate within the framework, you can skip this
discussion. The following chapters address building an application using
the framework.

Separating the Domain and the User Interface


The first and most fundamental aim of the application framework is this:
Keep the domain model separate from the user interface.
An application has one or more domain models, which define the
structure and processing of data in the domain of the application. For
example, in a sketching application, the domain model is responsible for
storing the lines that make up the sketch, and for adding and removing
lines upon request.

Application Developer’s Guide 185


Chapter 8 - Application Framework

The user interface (UI) is the part of the application that presents data
and application status to the user, and accepts input from the user by
mouse and keyboard actions. The UI display is generally graphical (so
called a GUI), consisting of one or more window containing widgets,
graphical controls such as buttons, input fields and lists.
Separating the domain model from the UI makes the application easier to
maintain, and also promotes reusability of the application components. If
the domain model provides generic services rather than services that rely
on special knowledge about a particular UI, it is easier to substitute a
different interface later as UI technology and user needs evolve.
Separation also makes it easier to provide multiple UIs for a single
domain model, perhaps one for a novice user and another for an expert
user.

Application Model Acts as Mediator


Obviously, the user interface and the domain model need to work
together. To avoid either the model or the UI having to support a lot of
code that really has nothing to do with its proper function, the
VisualWorks framework employs a mediating object, the application
model.
The application model handles the logic of how a window and its widgets,
which know nothing of a particular domain model, collaborates with a
domain model, which knows nothing of the UI, to form a unified
application. The application model is the glue that holds the application
together.
A VisualWorks application is defined as a subclass of ApplicationModel to
act as mediator. This subclass can be created manually, or automatically
generated from the canvas when the user-interface is “installed.”
Windows, menus, some graphics, and other “resources” are defined
within the application model.

186 VisualWorks
Separating the Domain and the User Interface

User interface

Application model

Domain model

Value Model Links Widget to Attribute


An application model coordinates communication between domain
objects and UI objects by defining a relationship between them. Each UI
widget is related to an attribute or operation of domain objects.
A user action on a widget, such as clicking a button or entering data in an
entry field, either modifies an attribute of a domain object or starts an
operation defined in the domain model. For example, for an attribute-
setting widget, such as an entry field, the application model translates the
value received by the widget and sends the appropriate value-setting
message to the domain model. Similarly, if a value changes in the domain
model that affects the UI, the application model picks up that change and
sends it to the UI.
The mechanism that the application model uses is called an adaptor. An
adaptor stands between the specific interfaces of the UI and domain
objects, adapting messages and values so they “fit.” The adaptor is also
referred to as a value model, because it defines the relation between an
attribute’s value and widgets that depend on that value.
There are different kinds of value models for different kinds of attribute
values. For example, a ValueHolder is used when the attribute value is a
simple data value such as a string of characters. An AspectAdaptor is used
when the data value is embedded in a composite attribute or in a domain
model separate from the application model.
Value models are created from the UI by the Define operation in the UI
Painter. The result is generally a “stub” method that requires additional
coding to complete the adaptor operation.

Application Developer’s Guide 187


Chapter 8 - Application Framework

User interface

widgets

Application model

value
models

Domain model

attributes

Builder Assembles User Interface


When a VisualWorks application is started, the application model
delegates the process of building the actual interface to an instance of
UIBuilder. The builder uses the specifications for the user interface,
including the widgets and properties for each widget, defined in the UI
Painter. This builder object is an important part of the application
framework. For example, you can programmatically access a specific
widget by asking for it by name from the builder.

User interface

Builder

Application model

188 VisualWorks
Dependencies Between Objects

Dependencies Between Objects


When Object B is affected by a change in Object A, Object B is said to be
a dependent of Object A. Dependencies of this nature occur commonly in
applications, and the application model collaborates with value models to
notify dependents of relevant changes.
VisualWorks uses two dependency mechanisms: the original
change/update mechanism, and the newer trigger-event mechanism. The
change/update mecahanism is described in this chapter. The trigger-
event system is described in Chapter 9, “Trigger-Event System.”

The Update/Change System


The update/change system is the original dependency mechanism in
VisualWorks. This mechanism is at the core of the GUI system, but is
also generally useful in application development. When an object using
this system changes in some way it sends a “changed” message to itself.
That message then results in sending an “updated” message to all of the
object’s dependents.
For example, in the sketching application, selecting a sketch in the list
widget causes the set of lines for that sketch to be displayed in a
sketching widget. The sketching widget is a dependent because it needs
to know when the selection is changed in the list of sketches.

Value Model Dependent


(selection index) (SketchView)

Application Developer’s Guide 189


Chapter 8 - Application Framework

Note that the sketching widget is not a dependent of the list widget.
Rather, it is a dependent of the value model that holds the list of
sketches. The list widget is the primary dependent of the value model,
and receives notifications much as its sibling widget does.
VisualWorks provides three layers of support for dependent notification:
• Notifications from a value model to an application model. Many
applications rely on this partially automated layer exclusively because
it is the easiest to implement and handles the common cases.
• Notifications from any object to any object. This is the foundation
layer upon which the first layer is built, and which provides broader
functionality for situations involving arbitrary types of objects.
• Event-based notifications for objects of any type. This is actually an
alternative to the second-layer architecture, provided for compatibility
with VisualWorks Smalltalk.

Notifications From Value Model to Application Model


An application model provides a value model to keep a widget in sync
with its data value in the domain model. When a secondary widget also
needs to be kept in sync with that data value, the application model
employs a DependencyTransformer.
A DependencyTransformer is like a single-minded robot that is told, in effect:
“Keep your eye on this value model—whenever its value is changed,
notify me.”
This robot is told what message to send to the application model. By
convention, the message begins or ends with the word “changed,” as in
valueChanged or changedSelection.
The Notification page of the Property Tool enables you to specify this
message, in effect setting up a DependencyTransformer to monitor the
primary widget’s value model.
The application model is expected to implement the corresponding
instance method, in a change messages protocol. That method updates the
value model for the secondary widget, which in turn causes the
secondary widget to update its display, completing the cycle of
dependency.
Using the sketching application as an example, here is how the sequence
of events occurs:
1 The user clicks on the name of a sketch in the list widget, causing the
selectionIndexHolder value model to change its value.

190 VisualWorks
Dependencies Between Objects

2 A DependencyTransformer notices the change and notifies the


application model by sending a changedSketch message to it.
3 The application model, in its changedSketch method, gets the newly
selected sketch and installs it in the sketch widget’s value model.
4 The sketch widget displays the sketch.

Value Model Value Model


(selection index) (SketchView’s model)

Dependency transformer

Application Model

Notifications From Any Object to Any Object


While the Notification page of a widget’s property sheet enables you to
arrange for a notification to an application model, you can use a
DependencyTransformer to arrange for a notification from any object to any
object. Going even further into the dependency mechanism, you can
arrange for a direct notification without the use of a robotic third party.
DependencyTransformer
When a value model changes its value, it sends a changed: #value
message to itself. The changed: method is inherited from Object, and
sends an update: #value message to all dependents of the value model.

Application Developer’s Guide 191


Chapter 8 - Application Framework

A DependencyTransformer, when it receives an update: #value message,


sends a specified message to a specified receiver. In the usual situation,
as discussed above, it sends a specified message to an application
model. But as a general technique, it can be used to send any message
to any receiver.
In addition, when the robot is monitoring an object other than a value
model, it can be made to react to a changed: #selection message, for
example, or any other aspect symbol indicating the nature of the change.
The aspect symbol is used by contract between the object being
monitored and the transformer.
For example, a BankAccount might send changed: #balance to itself, and the
DependencyTransformer might be configured to pay attention to the
corresponding update: #balance message, while ignoring other update:
messages.
Setting up a notification in this way involves creating a
DependencyTransformer with the appropriate aspect symbol, message
selector, and message receiver, and then adding that transformer as a
dependent of the target object (using addDependent:). If the target object is
not a subclass of ValueModel, you must also arrange for it to send changed:
#aspectSymbol to itself in the method that effects that change. Subclasses
of ValueModel take care of that detail, because they are the most common
targets.
Subclasses of ValueModel are capable of setting up a transformer for you.
Just send onChangeSend: #selector to: receiver to the value model.
Any object can set up a transformer in response to expressInterestIn:
#aspectSymbol for: receiver sendBack: selector.
Direct Dependency
You can dispense with the transformer by implementing an update:
method for the dependent object. Then add that object as a dependent of
the target object (using addDependent:). As a result, when the target object
sends changed: #aspectSymbol to itself, the dependent object will receive
update: #aspectSymbol.
Again, the aspect symbol must be agreed upon.

192 VisualWorks
Dependencies Between Objects

Variants of the changed/update: messages are available for situations


requiring a parameter in addition to the aspect symbol (update:with:) and
the target object (update:with:from:).

Dependents

update: #aspectSymbol

self changed: #aspectSymbol

Any object

Removing Dependents
The Object class provides a central dictionary for keeping track of any
object’s dependents. An application that adds a dependent is also
responsible for removing it (using removeDependent:), to avoid having the
dictionary hold onto obsolete dependents and waste increasing amounts
of memory.
The Model class provides an instance variable for storing dependents
locally, avoiding the use of the central dictionary. Thus, instances of
subclasses of Model (including the value model hierarchy) automatically
release their dependents when they expire. Because value models are
the targets of the vast majority of dependencies, this takes care of most
situations.
Circular Dependencies
Because dependencies involve indirect communications, the hazard of
circular message-passing becomes more likely. The most common
situation in which circularity arises involves two mutually dependent
widgets.
For example, in a document display window, the “page number” display
field and “table of contents” treeview widget may be mutually dependent.
That is, changing the page number updates the selection in the treeview,
and changing the selection in the treeview updates the page number.

Application Developer’s Guide 193


Chapter 8 - Application Framework

You can temporarily remove a transformer in such a situation, by sending


retractInterestIn: aspect for: dependent to the target object just before you
change its value. After changing the value, you must reestablish the
transformer (using onChangeSend:to:).
You can temporarily remove a direct dependent by sending
removeDependent: dependent to the target object, and then adding it (using
addDependent:) after changing the value.

Application Startup and Shutdown


The first step in starting an application involves deciding which interface
to open. The process of assembling and opening the chosen interface
proceeds by stages. After each stage, your application model can
intervene in the process to configure the raw interface as needed. The
stages are:
• Create an instance of UIBuilder
• Pass the UI specs to the builder and ask it to construct the UI objects
• Open the fully assembled interface window
By default, when an application model class is sent an open or
openInterface: message, all three stages are performed. You can send
allButOpenInterface: to an instance to perform stages one and two, then
separately send finallyOpen to perform stage three.

Selecting an Interface
An application is typically started by sending an open message to the
appropriate subclass of ApplicationModel. This assumes that the primary
canvas was saved with the default name, windowSpec.
If the primary canvas has a different name, or if you want to open a
different canvas, you can send openWithSpec: to the class, with the spec
name as the argument.
The application model class creates a new instance of itself to run the
interface. If you want to use an existing application model instance, you
can send open or openInterface: to that instance. This is useful when you
want to reuse an instance rather than create a new one, or when you
want to initialize the application specially.

194 VisualWorks
Application Startup and Shutdown

Prebuild Intervention
After an instance of UIBuilder has been created, but before it has been
given a set of specs with which to construct a UI, the application model is
sent a preBuildWith: message. The argument is the newly created
UIBuilder.
Most applications do not need to intervene at this stage. Those that do,
typically take the opportunity to load the builder with custom bindings that
can only be derived at runtime.

Postbuild Intervention
The application model creates a hierarchy of spec objects from the spec
method, and hands the root spec to the builder. The builder then creates
a window and populates it with the appropriate widgets. The builder does
not yet open the window, however.
At this stage, the application model receives a postBuildWith: message,
with the builder as argument. The application model can use the builder
to access the window and any named widgets within the window—that is,
widgets that were given an ID property.
Applications commonly use postBuildWith: to hide or disable widgets as
needed by the runtime conditions.

Postopen Intervention
The builder opens the fully-assembled interface. At this stage, the
application model is sent a postOpenWith: message, again with the builder
as argument. As with postBuildWith:, the application can use the builder to
access the window and its widgets. This time, however, those objects
have been mapped to the screen, which makes a difference for some
kinds of configuration.
For example, the FileBrowser model that drives the File List interface uses
postOpenWith: to insert the default path in the window’s title bar—
something it could not do until after the window had been opened.

Application Cleanup
An application model often needs to take certain actions when the
application is closed. For example, a word-processing application might
need to ask the user whether edits that have been made to the currently
displayed text should be saved or discarded.

Application Developer’s Guide 195


Chapter 8 - Application Framework

Another common cleanup action is to break circular dependencies that


would otherwise prevent the application from being garbage collected.
For example, if application A holds application B, and vice versa, for the
purpose of interapplication communications, neither would be removed
from memory even after both of their windows were closed.
If the application user exits from the application by using a menu or other
widget in the interface, the application model performs the exit procedure
and can insert any required safeguards. But if the user exits by closing
the main window, a special mechanism is needed to notify the application
model.
The application model is held by the application window. When the
window is about to be closed, its controller asks for permission from the
application model, by sending a requestForWindowClose. The application
model can redefine this method to perform any cleanup actions and then
return true to grant permission or false to prevent the window from closing.
Additional cleanup can be performed using the finalization mechanism
described in Chapter 13, “Weak Reference and Finalization.”

User Settings Framework


VisualWorks provides a settings framework to simplify the creation and
management of application settings (often referred to as “preferences” or
“options”). The framework includes an interactive tool — the Settings
Manager — that enables users to view and change pages of individual
settings defined by the application developer. Settings can be saved to a
file, and later restored, possibly in a different VisualWorks image.
For a general description of using the Settings Manager, see “Settings” in
Chapter 1, “The VisualWorks Environment.” This describes how to add
settings to existing pages in the Settings Manager and how to define new
pages.
The settings framework consists of two parts: the settings themselves
and the user interface. The UI presents settings grouped into pages, with
each individual setting element identified by a setting model and a setting
type. The setting value itself is not stored in the settings framework, but
by the application’s domain model.

196 VisualWorks
User Settings Framework

Settings
Each individual setting on a page that appears in the Settings Manager is
defined as a method belonging to the class side of VisualWorksSettings.
The Settings Manager dynamically generates the user interface and the
page layout, so there are no window specifications or subcanvases for
the developer to worry about.
The settings framework requires only a method for each setting that
appears on a page, plus one method defining the page itself (for details
on the latter, see “Settings Pages” on page 200).
Each method used to define an invidual setting has two parts: a pragma
expression, which marks it as a setting definition, and the method body
that answers a setting model. For example:
toolsTranscriptLimit
<setting: #(tools transcriptLimit)>
^(IntegerSetting on: Transcript aspect: #characterLimit)
label: 'Transcript limit'
The pragma expression indicates both that the method is a setting, and
defines its ID. The ID is an array of symbols — in the example above,
#(tools transcriptLimit) — which must be unique to each particular setting.
The ID declares that a particular setting belongs to a specific page. For
example, all settings on the Tools page have IDs of the form #(tools
<aSymbol>) — in other words, their IDs all begin with the same
subsequence of symbols and only differ in the last symbol. Think of a
prefix as a "directory name", identifying the group a setting belongs to,
while the last element of an ID is a "file name" within the group.
The body of the method should return a setting model: an object that
knows how to get and set the value of the setting. In the example above,
an IntegerSetting on the characterLimit aspect of Transcript, i.e., that the
value of the setting will be obtained by sending characterLimit to the
Transcript, and set by sending the message characterLimit:.
Declaring the setting an IntegerSetting affects how it’s presented in the
settings tool: e.g., the setting is shown as an input field into which the
user can type an integer value. Typing anything else is not allowed, and
the settings framework performs simple input validation.
Finally, the label: message sent to the setting model defines its label. This
is a short string used to label the widget displaying this setting. The user
interface for the setting is dynamically generated using the information
provided in this method.

Application Developer’s Guide 197


Chapter 8 - Application Framework

Browsing the Definition for a Setting


The <Operate> menu for the Settings Manager page tree (left-hand view)
includes two menu items: Browse Page and Browse. Select Browse Page to
examine the methods that define the settings for the current page, and
Browse for definitions of all the pages in the tree. Note: these two menu
items — Browse Page and Browse — do not appear in deployment images.
By browsing the methods that define a setting, you can see selectors that
define each setting on the page, as well as the method that defines the
page itself (for details, see “Settings Pages” on page 200).

Defining a Setting
During application development, new settings and settings pages may be
defined simply by adding methods to class VisualWorksSettings.
As an example, we might want to add a setting to specify the number of
characters that can be written to the System Transcript before it starts
discarding the old output.
The following steps illustrate how to define a setting that manipulates the
Transcript object:
1 Open a browser on VisualWorksSettings, and examine its class-side
protocol.
For this example, we add a method in the protocol settings-tools.
2 Add a new method with the following body to the class side of
VisualWorksSettings:
toolsTranscriptLimit
<setting: #(tools transcriptLimit)>
^(IntegerSetting on: Transcript aspect: #characterLimit)
label: 'Transcript limit'
To see the new setting, open the Settings Manager and select the
Tools page.
The Transcript is an instance of class TextCollector, which includes two
methods — characterLimit and characterLimit: (in the private protocol) — for
controlling how many characters can be written to the Transcript. These
are used by the setting model (an instance of IntegerSetting) to manipulate
the Transcript object.
Note that the setting model also performs some minimal input validation.
In this case, the IntegerSetting only allows integers, as we would expect.

198 VisualWorks
User Settings Framework

Additional Setting Parameters


The setting model can also perform simple input validation. For instance,
an IntegerSetting only accepts integer values, as we would expect. Often,
though, the full range of integers would not be appropriate. In the
example of the Transcript limit setting, shown above, it would not make
sense to specify a negative number.
For an IntegerSetting, the values considered valid by the setting model
may be restricted using the messages min:, max: and min:max:.
Using these messages, we can modify the example shown above to
restrict the length of the Transcript. The following code creates a setting
model that only accepts values between 1000 and 50000:
((IntegerSetting min: 1000 max: 50000)
on: Transcript aspect: #characterLimit)
label: 'Transcript limit'
Another setting parameter make be ued to provide on-line help. Clicking
on the Help button in the Settings Manager opens a page of help for all
settings on the current page.
To specify help text for a particular setting, send the message helpText: to
the setting model. E.g.:
((IntegerSetting min: 1000 max: 50000)
on: Transcript aspect: #characterLimit)
label: 'Transcript limit';
helpText:
'The maximum number of characters allowable in the Transcript.'

Controlling the Vertical Position of a Setting


The settings framework dynamically generates the user interface shown
on each page, arranging all settings that belong to the page in a single
column. The ordering of the settings on the page may be changed via
one of two strategies.
The first method involves the selectors for the defining methods. By
default, the settings on one page are sorted using the selectors of the
corresponding definition methods. Thus, the order in which the defining
methods appear in the browser is the order in which they appear in the
Settings Manager.

Application Developer’s Guide 199


Chapter 8 - Application Framework

For example, the protocol settings-tools of class VisualWorksSettings


contains the following definition methods:
tools10iconLabelLength
tools20textSize
tools30showUIForGlobalization
tools40DebugSettingsErrors
By convention, the selector begins with the name of the page (‘tools’), and
is followed by two digits used to indicate the vertical position of the
widget. This scheme has proven very convenient for organizing the layout
of the Settings UI.
As an alternative, the pragma in the setting definition method may include
an additional parameter, position:. For example:
<setting: #(tools transcriptLimit) position: 2>
Setting definitions that do not include the position: parameter are assigned
the default position value of 0. As a rule, when settings are collected as a
settings page, they are first sorted by position. Then, settings with the
same position values are sorted by selector as described above.
You may use either approach to organize groups of settings. It is also
possible to mix the two approaches.
When adding a setting to a group of already existing settings, it is strongly
recommended that you follow the ordering approach used by that group.

Settings Pages
Each page of settings in the Settings Manager is defined in a manner
analogous to the individual settings on that page: using a single method
belonging to the class side of VisualWorksSettings.
Just as in the setting definition methods described previously, the method
that defines a settings page has a pragma expression and a method body
that answers a model for the settings page. For example:
transcriptPage
<settingsPage: #(tools transcript)>

^ModularSettingsPage new
label: 'Transcript';
icon: (ListIconLibrary visualFor: #tools);
settings: (self settingsWithPrefix: #(tools transcript))
The pragma is maked with the selector settingsPage:, which takes an array
argument to specify the page ID. This ID is used to define the hierarchical
relation between the various pages.

200 VisualWorks
User Settings Framework

This may be illustrated with an example. Assuming that an application


defines four different methods with the following IDs:
toolsPage #(tools)
browserPage #(tools browser)
workspacePage #(tools workspace)
transcriptPage #(tools foo transcript)
The settings manager would arrange the pages like this:
toolsPage
browserPage
workspacePage
transcriptPage
In other words, if an ID of one page is the prefix of an ID of another page,
the page with the shorter ID is made the parent of the other one. Thus,
toolsPage is made the parent of the other three pages.
The body of the settings page definition method should create and return
the settings page model; in this case, an instance of ModularSettingsPage.
Use the label: and icon: messages to specify the label and icon displayed
in the page tree.
Use the settings: message to specify the collection of settings displayed
on the page. In the example shown above, all settings whose ID is #(tools
transcript) are included, i.e., any setting with an ID that has the form
#(tools transcript <anySymbol>) is included in the page.

Defining a Page of Settings


During application development, settings pages may be defined simply by
adding methods to class VisualWorksSettings.
For example, we might want to place the Transcript limit setting (see
“Defining a Setting” on page 198) on its own page. The following steps
illustrate how to define a new settings page and add a setting to it:
1 Open a browser on VisualWorksSettings, and examine its class-side
protocol.
2 Create a new method protocol named settings-transcript.

Application Developer’s Guide 201


Chapter 8 - Application Framework

3 Select the new protocol, and add a new method with the following
body to VisualWorksSettings:
transcriptPage
<settingsPage: #(tools transcript)>

^ModularSettingsPage new
label: 'Transcript';
icon: (ListIconLibrary visualFor: #tools);
settings: (self settingsWithPrefix: #(tools transcript))
4 Add the following method to the settings-transcript protocol:
toolsTranscriptLimit
<setting: #(tools transcript characterLimit)>

^((IntegerSetting min: 1000 max: 50000)


on: Transcript aspect: #characterLimit)
label: 'Transcript limit'
To see the new setting page, open the Settings Manager and select
the Transcript page.

Setting Types
As noted above, a setting model does not actually contain the value of
the setting. The actual value is stored in the domain model, which the
setting model knows how to access.
Since the setting model is only a passive, transitive object, it is created by
using a setting type. For example, the following code:
IntegerSetting on: Transcript aspect: #characterLimit
returns a setting model that knows how to access the Transcript. Here,
class IntegerSetting specifies the type of setting that is instantiated.
In addition to class IntegerSetting, the settings framework supports a
number of different setting types.
The currently supported types are:
BooleanSetting
The setting value should be a true or false object. In the Settings
Manager, this type of setting is displayed as a checkbox.
ColorValueSetting
The value is an instance of ColorValue. In the Settings Manager, it is
displayed as a color swatch with a button that opens a color picker
dialog to pick a different color.

202 VisualWorks
User Settings Framework

EnumerationSetting
The value is one of a list of arbitrary objects. In the most general
case, the setting is initialized with three "parallel" sequences: a list of
objects that can be the value of the setting, a list of keys (Symbols)
that are used to represent the objects when the setting is saved in a
file, and a list of labels used to identify the choices in the Settings
Manager. This setting is displayed by default as a drop-down list of
choices, but can also be displayed as a group of radio buttons.
FilenameSetting
The value is a Filename identifying a file. The setting is represented as
an input field with the name of the file. The name can be changed
using the field, or (on Windows) by using the Browse button to pick a
file using the standard file selection dialog.
DirectorySetting
The value is a Filename identifying a directory. Unlike the
FilenameSetting, the Browse button is available on all platforms and
opens a directory selection dialog.
NumberSetting
The value is a Number. An upper and lower bound can be provided.
The setting is represented as an input field displaying the number.
IntegerSetting
Similar to the NumberSetting, but the value is required to be an Integer.
StringSetting
The value is a String. It is represented as an input field. Additionally,
an instance can be created as StringSetting forNameOfFile or
StringSetting forNameOfDirectory. Such StringSettings are represented
just as FilenameSetting and DirectorySetting, but the value of such a
setting is still a String rather than a Filename.
SequenceSetting
Its value is a sequenceable collection of values. The type of the
values is defined when the SequenceSetting type is created (it is
created using the of: message, with the type of the element passed
as the argument). The element type can be any of the types listed
above. These settings cannot be displayed by ModularSettingsPages,
each requiring a page of their own (a SequenceSettingPage).
It should be noted that a setting type and a setting model are not the
same. The setting model is responsible for data access: it knows how to
get and set the value of the setting, and also things like the label and the
help text. The setting type knows what values a setting can take.

Application Developer’s Guide 203


Chapter 8 - Application Framework

Creating a Setting Model


A setting model is created by first sending a message to the class of the
appropriate setting type, generally using the on:aspect: method.
For example:
StringSetting on: userProfile aspect: userName
This setting model gets and sets a value held by userProfile, sending it the
messages userName and userName:.
Several other creation messages are available. For example, to create a
setting on a ValueHolder, use the on: method:
aSettingType on: aValueModel
The setting’s value is obtained by sending value and value: to aValueModel.
In fact, the argument can be any object understanding value and value: (for
example, a LiteralBindingReference may be used to access the value stored
in a shared variable).
It is also possible to use dictionaries, sets, or arrays as domain models,
via the following creation message:
aSettingType on: anObject key: keyObject
A setting created using this expression gets its value by sending the
message at: to anObject with keyObject as the argument, and sets it by
sending the message at:put: with keyObject as the first argument and the
new value as the second one.

Backward Compatibility with VisualWorks UISettings


Prior to VisualWorks 7.1, user settings were stored as elements in a
dictionary called UserPreferences (a shared variable belonging to class
UISettings). Application developers could install and remove preferences
using the methods UISettings class>>addPreterenceSection: and
removePreterenceSection:.
For VisualWorks 7.1 and later, application developers are encouraged to
re-write their settings code using the new framework. However, to simplify
porting applications to the latest versions of VisualWorks, backward
compatibility with the older UISettings facility is also available. Applications
can preserve the existing user preference models, and display them as-is
using the new settings framework.
For example, to create a setting on an existing preference model, use the
onUISetting: creation message. E.g.:
BooleanSetting onUISetting: #showWorkspaceToolbar

204 VisualWorks
User Settings Framework

This example returns a setting model for the old preference model named
#showWorkspaceToolbar that is stored in the dictionary of preference
models in class [Link].

Using Drop-Down List and Radio Button Settings


Settings that appear as drop-down lists or as groups of radio buttons are
both defined using class EnumerationSetting. When building a settings
page using these types of settings, a slightly different approach is
required.
In the setting definition method, an EnumerationSetting is used to create a
setting model. For example:
EnumerationSetting
keys: #(small default large fixed)
choices: #(small default large fixed)
labels: #('Small' 'Medium' 'Large' 'Fixed')
The argument keys specifies the name of each key (used when saving the
setting in a file); the argument choices specifies the actual values used,
while labels takes the strings (or UserMessage instances) that are shown in
the UI of the Settings Manager. This is all as we would expect.
In fact, the code for specifying whether the setting is displayed as a drop-
down list or as a set of radio buttons is located in the method that defines
the settings page. The page definition method is essentially the same,
with the exception of the code for adding the settings to page.
Recall that the page definition returns an instance of ModularSettingsPage
that has its settings initialized using the settings: method. To use drop-
down lists or radio-buttons, you must send addAllSettings:except: instead.

Application Developer’s Guide 205


Chapter 8 - Application Framework

For example:
lookAndFeelPage

<settingsPage: #(lookAndFeel) position: -30>

^ModularSettingsPage new
label: #LookAndFeel << #labels >> 'Look and Feel';
icon: (ListIconLibrary visualFor: #window);
addAllSettings:
(self settingsWithPrefix: #(lookAndFeel)
except: #(windowPlacement mouseButtonOrder));
useRadioButtonsForEnumerations;
addSetting:
(self settingWithId: #(lookAndFeel windowPlacement));
addSetting:
(self settingWithId: #(lookAndFeel mouseButtonOrder))
In this method, we use addAllSettings:except: to indicate that the setting
definition methods for window placement and mouse button order are
given different treatment. Note that these two settings are identified by
the last symbol in their respective IDs (each being an array of symbols).
By default, an EnumerationSetting is displayed as a drop-down list. In the
example code shown above, the message useRadioButtonsForEnumerations
is sent to indicate that these two settings should be shown as radio
buttons. Subsequently, any settings added to the page are displayed
using radio buttons. The remainder of the method adds the two settings
that were previous excluded from the page.

Defining a Settings Domain


So far we have been adding pages and settings to the standard
VisualWorks Settings Manager. These pages are all defined in methods
in the class VisualWorksSettings, which is a subclass of SettingsDomain.
Adding pages and settings in this class has the effect of extending the
Settings Manager. This is appropriate for adding settings to the
development environment.
To implement a settings manager for your application, however, it is more
appropriate to create a separate settings domain. This creates a new
group of settings and setting pages which are shown together in the
same tree in a separate settings manager. Settings grouped in this way
can also be saved into a file and loaded together.

206 VisualWorks
User Settings Framework

For an application whose settings are to be managed separately from


those of other applications and the development system, we recommend
defining its own settings domain (a subclass of SettingsDomain) to manage
its settings. For example, create MyAppSettings as a subclass of
SettingsDomain.
Add pages to the new settings domain class as described above, to
provide the settings options required by your application.
To open the settings manager, send an openManager message to the
settings domain class. For example:
MyAppSettings openManager
There must be at least one page defined for the domain in order for the
manager to open. You can create a menu item in your application to open
the settings manager, which is typically named “Options,” “Preferences,”
or “Settings.”

Saving and Loading Settings


Setting pages, items and their values can be written out to a text file on a
domain-by-domain basis. This allows you to to write the settings for your
application out to a “configuration” file, and then reload them at another
time, such as at application startup.
To write the settings file, send a writeToFile: message to the settings
domain, with a Filename as argument:
MyAppSettings writeToFile: '[Link]' asFilename.
To read the settings back in, send a readFromFile: message to the settings
domain:
MyAppSettings writeToFile: '[Link]' asFilename.
Note that this does not define the settings domain class or pages, which
must be defined by your application.
You can specify a settings file to load at startup on the command line
when launching VisualWorks from a console. The -settings (among
others) image level option is defined in ImageConfigurationSystem for
loading settings files. To load settings this way, include the option followed
by the settings file name, following the image name on the command line.
For example:
> visual ../image/[Link] -settings '[Link]’
The option reads the domain from the file and installs the settings
accordingly.

Application Developer’s Guide 207


Chapter 8 - Application Framework

Responding to System Events


It is frequently necessary to take special actions when certain system
events occur, notably when the system starts up, shuts down, and
immediately before and after an image save. The order in which such
actions occur, relative to other parts of the system, can be critical. For
example, a GUI application probably needs to perform and window
startup routines only after the windowing system itself has been
initialized.
Traditionally, startup events have been handled by registering
dependencies on ObjectMemory. More recently, SystemEventInterest
instances have been supported by the system. Both of these
mechanisms left it difficult to manage the order in which actions were
taken.
Class Subsystem provides VisualWorks a simple way to specify
dependencies on system events as well as a modular approach to
controling their order of execution. Several subsystems are defined for
handling VisualWorks startup procedures.
Two subclasses in particular are of interest to the application developer:
UserApplication and ImageConfigurationSystem. If an application has actions
to perform upon one of the four system events, a subclass of
UserApplication is a convenient place to specify those actions.
ImageConfigurationSystem is useful for applications that process command
line options.

Defining System Event Actions


Subsystem defines four system event messages to which subsystems can
respond: activate, deactivate, pause, and resume. By default, these general
events are invoked as follows:
• activate is invoked by #returnFromSnapshot, which occurs when an
image is launched.
• deactive is invoked by #aboutToQuit, which occurs just before the image
exits
• pause is invoked by #aboutToSnapshot, which occurs just prior to
writing an image file
• resume is invoked by #finishedSnapshot, which occurs just after the
image file has been written

208 VisualWorks
Responding to System Events

Some subsystems invoke activate upon #earlySystemInstallation, but these


are usually system level subsystems. For applications,
#returnFromSnapshot is the appropriate system event.
A subsystem does not respond to these system event messages directly.
Instead, these messages invoke further messages in which a subsystem
configures its response to the system events. The corresponding
messages that a subsystem will implement as needed are:
setUp
Defines actions to perform upon the activate event message, and
activates the subsystem.
tearDown
Defines actions to perform upon the deactivate event message, and
deactivates the subsystem.
pauseAction
Defines actions to perform upon the pause event message.
resumeAction
Defines actions to perform upon the resume event message.
An application seldom needs to perform actions before or after a
snapshot, which is generally a development time activity, so do not
generally have to provide implementations for pauseAction or resumeAction.
An action does, however, frequently have actions to perform upon
launching the image, such as setting up its runtime environment, and
these are specified by an implementation of setUp. Less frequently, but
not uncommonly, an application will also need to perform actions prior to
shutdown, which can be implemented in the tearDown method.
The UserApplication subsystem, which is intended to be the superclass for
application subsystems, implements one additional stub method:
main
This method can be implemented by a subsystem to launch the
application, as well as to perform other application set up tasks.
This method simplifies starting an application upon image launch,
eliminating the need to either save the image with the application open, or
of using Runtime Packager to specify the application to run, or any of the
other methods that have been used.
As an example of using setUp and tearDown methods, consider the task of
saving a random number seed upon shutdown and then reading that
seed to restart a random number generator upon startup. (This is a useful
technique to use for security minded applications, as discussed in the
Security Guide.) DSSRandom, in the Security component, maintains a

Application Developer’s Guide 209


Chapter 8 - Application Framework

default generator, but it is most useful if it is well seeded, and the seed is
updated between image startups. To manage this we can define a
UserApplication subclass, DefaultRandomSystem, and implement two
methods.
setUp
DSSRandom resetDefaultFrom: 'seed' asFilename readStream binary

tearDown
'seed' asFilename writeStream binary;
nextPutAll: (DSSRandom default next changeClassTo: ByteArray);
close
The tearDown method records a seed value by writing it to a file just
before the system shuts down. The setUp method then reads that value
upon system start up, and reseeds the default generator with it. In this
case there is no application to launch.
As another example, we can implement main to launch an application,
such as RandomNumberPicker from the VisualWorks Walk Through. To do
this, we define a subclass of UserApplication, such as RandomPickerSystem,
and implement a main method. Minimally, it might be:
main
[Link] open
(By importing the WalkThru namespace into RandomPickerSystem, the
expression above can be simplified, and is the preferred practice.)
This example also indicates the reason for the main method, which is not
really needed (everything could be done in setUp). Programmers coming
from other development environmes often look for the method that starts
an application, and particularly for a method named “main.” This provides
that method.

Command Line Processing in a Subsystem


ImageConfigurationSystem defines several standard image level command
line options and their handling. You can extend this system’s options, or
define additional command line options.
To define a new command line option, implement a subsystem instance
method defining the handling of the option. The method consists of two
parts: an “option” pragma and the option handling code. For example,
consider the method in ImageConfigurationSystem for handling the
-settings option:

210 VisualWorks
Responding to System Events

loadSettings: fileNameStream
"This handles loading settings from the command line."

<option: '-settings'>

| settingNames |
self class allowSettings ifFalse: [^self].
settingNames := CommandLineInterest argumentsFrom: fileNameStream.
settingNames do: [:each |
self loadSettingsFrom: each asFilename].
The option: pragma keyword identifies this as defining a command line
option, and the String argument identifies the particular option being
defined. The method selector takes an argument, fileNameStream, which
causes the next item on the command line to be handed to the method as
that argument. If the option does not require an argument value, the
method selector would be unary.
The rest of the method defines the processing of the argument. The
whole command line stream is handed into the method in the argument,
fileNameStream. The interesting expression is:
CommandLineInterest argumentsFrom: fileNameStream
which extracts just the argument relevant to the setting being defined; in
this case, the argument following “-setting” on the command line.
To define a new command line option relevant only to your application,
you can define it in your application’s subsystem class. For example, we
have already shown how to launch an application using its subsystem.
Perhaps you want to include an option to prevent launching the
application. Here is one way to do that, modifying the RandomPickerSystem
defined earlier.
First, in the class definition for RandomPickerSystem add an instance
variable, such as launchApp, which will hold a flag:
[Link] defineClass: #RandomPickerSystem
superclass: #{[Link]}
indexedType: #none
private: false
instanceVariableNames: 'launchApp '
classInstanceVariableNames: ''
imports: ''
category: 'System-Subsystems'
Then, implement a method to define the option and its handling:

Application Developer’s Guide 211


Chapter 8 - Application Framework

noLaunchOption

<option: '-nolaunch'>
launchApp := 'nolaunch'.
The handling here is simple, simply setting the flag in the variable, which
we then use to decide whether or not to launch the application. Modifying
the main method to use the value, we might have:
main

launchApp = 'nolaunch' ifFalse:


[[Link] open]
Now we can launch the image but supress opening the application:
> visual ../image/[Link] -nolaunch
This option is specific to this application, so has no effect on any other,
unless configured to be processed.

Activating a Subsystem
Once a subsystem has been defined, as described in the preceding
section, it needs to be activated.
Normally a subsystem is activated upon system startup, by successfully
executing its setUp method. So, to activate a new subsystem you can save
the image, then shut down and relaunch the image. This is also a good
test of the set up operation.
To activate a new system without shutting down and relaunching, set an
activate message to the subsystem. For example:
RandomPickerSystem activate
As long as the setUp method completes successfully, the subsystem is
activated. In this example, the application will also launch.

Dependency Ordering of Subsystems


The Subsystem framework provides a way to control the activation order of
various subsystems. For many of the system level subsystems, activation
order is important. For example, the WindowingSystem is dependent upon
both BasicGraphicsSystem and InputProcessingSystem, which must be
activated before WindowingSystem.
For application purposes, you do not generally need to be concerned with
this, because UserApplication and its subclasses are the last of the
systems to be activated and the first to be deactivated, ensuring that all
system level subsystems upon which the application depends are already

212 VisualWorks
Responding to System Events

activated. It is possible, however, that in a complex application consisting


of several subsystems, it will be necessary to control their activation
order.
Subsystem activation order is determined by the subsystem prerequisites
specified for each subsystem. These are specified in a prerequisiteSystems
instance method defined in the subsystem class. For example, the
WindowingSystem defines its prerequisites as:
prerequisiteSystems
^Array with: BasicGraphicsSystem with: InputProcessingSystem.
The method is expected to return a collection, typically an Array, of
subsystems. The subsystem implementing the method will then not be
activated until its prerequisite systems have been activated.

Application Developer’s Guide 213


9
Trigger-Event System

The trigger-event system is an event-based mechanism for indirect


communication with dependent objects, allowing for a loose coupling of
objects. While the trigger-event system is used primarily in the GUI
environment and some tools, it is a general mechanism that can be used
to communicate between any objects.
Using the trigger-event mechanism, an object can trigger any event. The
object can also define certain events that it promises to trigger under
appropriate conditions. A dependent object can register a handler for an
event in which it is interested. This chapter describes how to define,
trigger, and handle these events.
In the traditional dependency system, an object that was interested in
changes in another object was registered in that object’s dependency list,
and thus added to that object’s state (tightly coupled). In the trigger-event
system, an interest is added instead as a request to send a message to
the interested object when an interesting event occurs. So, the interested
object is not itself held in the target object’s state, and so is “loosely
coupled.” The dependency is only a functional dependency.
Note that the trigger-event system described in this chapter is separate
from the event system used to capture input (mouse and keyboard)
events. While the input-event system responds to events coming in to
VisualWorks from the operating system, the trigger-event system is
completely defined by classes and methods in Smalltalk. There is no
dependency on underlying operating system events, so the mechanism is
completely portable.

214 VisualWorks
Triggering Events

Triggering Events
Any object can trigger any event. Accordingly, there is generally no need
to specify the events an object will trigger, though for some purposes this
can be defined in a constructEventsTriggered message (refer to “Defining
Event Sets” later in this chapter).
To trigger an event, an object simply sends a variant of triggerEvent: to
itself, with the event name as the argument:
self triggerEvent: #foo
Variants are described below.

Event Triggering Messages


The following are the variants of the triggerEvent: message:
triggerEvent: anEventNameSymbol
Trigger the event named anEventNameSymbol. Answer the value
returned by the most recently defined event handler action.
triggerEvent: anEventNameSymbol ifNotHandled: exceptionBlock
Trigger the event named anEventNameSymbol. If the event is not
handled, answer the value of exceptionBlock (a zero-argument block);
otherwise answer the value returned by the most recently defined
event handler action.
triggerEvent: anEventNameSymbol with: anArgumentObject
Trigger the event anEventNameSymbol using the given anArgumentObject
as the argument. Answers the value returned by the most recently
defined event handler action.
triggerEvent: anEventNameSymbol with: firstArgumentObject
with: secondArgument
Trigger the event anEventNameSymbol using the firstArgumentObject and
secondArgumentObject as the arguments. Answers the value returned
by the most recently defined event handler action.
triggerEvent: anEventNameSymbol withArguments: anArgumentCollection
Trigger the event anEventNameSymbol using the elements of the
anArgumentCollection as the arguments. Answers the value returned by
the most recently defined event handler action.

Application Developer’s Guide 215


Chapter 9 - Trigger-Event System

triggerEvent: anEventNameSymbol withArguments: anArgumentCollection


ifNotHandled: exceptionBlock
Trigger the event anEventNameSymbol using the elements of the
anArgumentCollection as the arguments. If the event is not handled,
answers the value of exceptionBlock (a zero-argument block);
otherwise answers the value returned by the most recently defined
event handler action.

Registering an Event Handler


A dependent object can arrange for an action to occur each time the
triggering object triggers a specific event. This is known as registering an
event handler, or registering an interest in the event.
The dependent sends a variant of when:send:to: to the (potentially)
triggering object. The first argument is the event name as a Symbol, the
second argument is a message name as a Symbol, and the third
argument is the handler message receiver, which is frequently self.
Suppose we have two objects, eventTripper and eventResponder, and the
responder wants to register a handler for any time eventTripper triggers
event #foo. eventResponder would register that interest by sending:
eventTripper when: #foo send: #bar to: self
Now, whenever eventTripper triggers #foo, a bar message will be sent to
eventResponder.
The dependent object might not do the registering itself. For example, an
ApplicationModel might use when:send:to: to arrange for a domain model to
send a message to a dependent object, so that dependent object is
notified of the event.
Note that if the triggering object is “strict,” an object that specifies the
events it might trigger in its constructEventsTriggered method, you can only
register handlers with that object for the events it declares. Refer to
“Defining Event Sets” below for more information.
A registering object can verify that a particular event can be triggered by
an object, by sending canTriggerEvent:, either to the triggering object or to
its class. A non-strict class will always answer true, while a strict class will
answer true only if the event is included in its eventsTriggered set.

216 VisualWorks
Registering an Event Handler

Handling an Event with Arguments


When an event is triggered with arguments, as by triggerEvent:with:,
triggerEvent:with:with:, or triggerEvent:withArguments:, it sends the event
notification along with an Array containing the arguments. To make use of
the arguments, handle them using a block that takes the appropriate
number of arguments, by registering using a when:do: message.
For example, suppose a class EventTripper triggers an event with two
arguments:
tripEvent
self triggerEvent: #foo with: #bar1 with: #bar2
A class, EventConsumer, might regisiter a handler to use the arguments as
follows:
initialize
tripper := EventTripper new.
tripper when: #foo do: [ :arg1 :arg2 | arg1 inspect. arg2 inspect. ]
Ensure that the block handles the correct number of arguments.

Handler Registration Messages


Below are descriptions of all event configuring methods:
when: anEventNameSymbol do: aBlock
Append aBlock to the list of actions to evaluate when the receiver
triggers the event named anEventNameSymbol.
when: anEventNameSymbol evaluate: anAction
Append anAction to the list of actions to evaluate when the receiver
triggers the event named anEventNameSymbol. anAction is either a
block or a message.
when: anEventNameSymbol send: aSelectorSymbol to: anObject
Form an action with anObject as the receiver and a aSelectorSymbol as
the message selector and append it to the actions list for the event
named anEventNameSymbol.
when: anEventNameSymbol send: aSelectorSymbol to: anObject
with: anArgumentObject
Form an action with anObject as the receiver, a aSelectorSymbol as the
message selector, and anArgumentObject as the argument and append
it to the actions list for the event named anEventNameSymbol.

Application Developer’s Guide 217


Chapter 9 - Trigger-Event System

when: anEventNameSymbol send: aSelectorSymbol to: anObject


with: firstArgumentObject with: secondArgumentObject
Form an action with anObject as the receiver, a aSelectorSymbol as the
message selector, and the firstArgumentObject and
secondArgumentObject as the arguments and append it to the actions
list for the event named anEventNameSymbol.
when: anEventNameSymbol send: aSelectorSymbol to: anObject
withArguments: anArgumentCollection
Form an action with anObject as the receiver, a aSelectorSymbol as the
message selector, and the elements of the anArgumentCollection as
the arguments and append it to the actions list for the event named
anEventNameSymbol.
whenAny: aCollectionOfEventNames do: aBlock
Append aBlock to the list of actions to evaluate when the receiver
triggers any of the events named in aCollectionOfEventNames.
whenAny: aCollectionOfEventNames evaluate: anAction
Append anAction to the list of actions to evaluate when the receiver
triggers any of the events the event named in aCollectionOfEventNames.
whenAny: aCollectionOfEventNames send: aSelectorSymbol to: anObject
Form an action with anObject as the receiver and a aSelectorSymbol as
the message selector and append it to the actions list for all the event
named in aCollectionOfEventNames.
whenAny: aCollectionOfEventNames send: aSelectorSymbol to: anObject
with: anArgument
Form an action with anObject as the receiver and a aSelectorSymbol as
the message selector and append it to the actions list for the all the
event names in aCollectionOfEventNames.
whenAny: aCollectionOfEventNames send: aSelectorSymbol to: anObject
with: firstArgumentObject with: secondArgumentObject
Form an action with anObject as the receiver, a aSelectorSymbol as the
message selector, and the firstArgumentObject and
secondArgumentObject as the arguments and append it to the actions
list for all the event names in aCollectionOfEventNames.
whenAny: aCollectionOfEventNames send: aSelectorSymbol to: anObject
withArguments: anArgumentCollection
Form an action with anObject as the receiver, a aSelectorSymbol as the
message selector, and the elements of the anArgumentCollection as
the arguments and append it to the actions list for all the event names
in aCollectionOfEventNames.

218 VisualWorks
Removing Event Handlers

Removing Event Handlers


When an event handler is registered, it is either stored in a class variable
named EventHandlers, which is defined in Object, or in its private event
handler instance variable. When an object does not have it’s own event
handler instance variable, the application is responsible for removing
each handler from the EventHandlers event table when the handler is no
longer needed.
To remove an event handler from EventHandlers, send a removeAction
message (see “RemoveAction messages” below for messages) to the
triggering object. For example, if eventResponder had registered an interest
in event #foo triggered by eventTripper, it would unregister that interest by
sending:
eventTripper removeActionsWithReceiver: self forEvent: #foo
This will remove all action registered by eventResponder for #foo with
eventTripper.
You can remove a single action, but you need to have the action. To get
an action from the triggering object, send an actionForEvent: message, with
the event name as argument:
anAction := eventTripper actionForEvent: #foo.
If only one action is registered for this receiver and event, a MessageSend
is returned. If multiple actions are registered, then an ActionSequence is
returned, and you need to select the action you want to remove. Given
the action, you can remove it by sending:
eventTripper removeAction: anAction forEvent: #foo
This removes the first instance of anAction registered for the receiver for
event #foo. For uniform processing, you can use
( eventTripper actionForEvent: #foo ) asActionSequence
so the result is always an ActionSequence.
The triggering object can remove all handlers that have been registered
with it by sending a release message to itself. The more specific message,
releaseEventTable, can be sent to any event-triggering object to remove all
of its registered events without regard to the life cycle stage of the object.
You can remove event handlers from the instance variable using the
same methods, but it is not as important since the registration expires
with the instance.

Application Developer’s Guide 219


Chapter 9 - Trigger-Event System

RemoveAction messages
removeAction: anAction forEvent: anEventNameSymbol
Remove the first occurrence of anAction from the list of actions for the
event named anEventNameSymbol.
removeActionsForEvent: anEventNameSymbol
Remove all actions for the event named anEventNameSymbol.
removeActionsSatisfying: aBlock forEvent: anEventNameSymbol
Remove all actions for the event anEventNameSymbol that satisfy
aBlock.
removeActionsWithReceiver: anObject forEvent: anEventNameSymbol
Remove all actions for the event named anEventNameSymbol in the
receiver's event table which have anObject as their receiver.
removeAllActionsWithReceiver: anObject
Remove all actions for all events in the receiver's event table that
have anObject as their receiver.

Defining Event Sets


Because an object can trigger any event and, in most cases, an object
can register an interest in any event with any object, there is, in general,
no reason to define or declare events. The only exception is in the case of
“strict” objects, which accept registering an interest for specifically
identified events only.

Specifying event strictness


A class can either be strict about which events it allows a dependent to
register an interest, or it can be ambivalent. A class that is strict does not
allow a dependent to register an interest in any event that it does not
know that it triggers. A class that is ambivalent allows a dependent to
register any event at any time, without regard to whether the class ever
triggers it. In the latter case, it is possible to register an interest in an
event that is never triggered.
By default all subclasses of Object are ambivalent. In the GUI system, only
subclasses of DisplaySurface and VisualComponent are strict.
To make a class and its subclasses strict, implement the class method
ambivalentEventChecking to return false. This overrides the definition in
Object, where it is defined to answer true.
ambivalentEventChecking
^false.

220 VisualWorks
How Handlers are Registered

Specifying events to trigger


A class that is strict is responsible for declaring which events it will trigger,
and so in which it will accept a registered interest. To declare events,
implement the inherited class method constructEventsTriggered in each
class that needs to define a set of valid events. The method creates a Set
of event names, specified as Symbols, and returns the set. It can, of
course, invoke super constructEventsTriggered to fetch the parent class’s
events, and then add to that set before returning it. For example,
VisualPart implements constructEventsTriggered as:
constructEventsTriggered
^super constructEventsTriggered
add: #changing ;
add: #changed ;
yourself
Event names, like message selectors, can be unary or keyword names. A
unary event has no parameter, while a keyword event has as many
parameters as it has colons. For example, the code above defines a
#changing event, because the dependent object needs no further
information. MenuBar, on the other hand, defines a #menuItemSelected:
event, because the dependent needs to know which menu item was
selected, and takes the ID of the menu item as the message argument.

Event classes
Several special event classes are defined, as subclasses of Event. In
general, there is no need create such classes, as explained above. These
classes exist as interfaces for operating system events coming in through
the virtual machine.

How Handlers are Registered


By default, all subclasses of Object share a common event handler holder
in the class variable (a shared variable) EventHanders, which is defined in
Object. EventHandlers holds an EphemeronDictionary that is populated when
an object sends a variant of the when:send:to: message to configure an
event handler. The receiver of the message is the key in EventHandlers,
and the value is another IdentityDictionary of all events registered to that
object, where each item is the name of the triggered event, and the value
is the action to perform on receiving the event.
Subclasses of ApplicationModel, VisualPart, EventManager and Window
override this default, and do not use the default EventHandlers. Instead, the
classes each have an instance variable that holds any events registered

Application Developer’s Guide 221


Chapter 9 - Trigger-Event System

to their instances. In the case of ApplicationModel, VisualPart and Window,


that instance variable is named eventHandlers, and in the case of
EventManager it is named events. Classes that have their event handlers
defined in an instance variable have an advantage in that these objects
do not need special code for removing their trigger event dependencies
when the object is no longer in use; the handlers are removed with the
object during garbage collection.
You can create your own classes to use the instance variable approach,
in which case you have two options. The first, and simplest, is simply to
make your classes subclasses of EventManger. Then your object’s event
handlers are simply held in the events instance variable, as mentioned
above.
The second option is a little more complicated. First, you must add an
instance variable to the class you wish to hold the local event handlers.
We suggest that this be named eventHandlers, but that is not required.
Then you need to add two accessor methods to your class: myEventTable
and myEventTable:. These simply need the following form:
myEventTable: anEventTable
eventHandlers := anEventTable
and
myEventTable
^eventHandlers
With these two methods, the trigger-event system will automatically put
any events registered to your class into this instance variable instead of
into the EventHandlers class variable.

Trigger Event System Support Methods


In addition to the methods already described for triggering events,
registering event handlers, and removing event handlers, the following
event support methods are useful.

Trigger Event Support Methods Available to All Objects


actionForEvent: anEventNameSymbol
Answers the action or action sequence to evaluate when the event
named anEventNameSymbol is triggered by the receiver. The action
may be a block or a message.

222 VisualWorks
Trigger Event System Support Methods

actionListForEvent: anEventNameSymbol
Answers an editable list of actions that are evaluated when the event
named anEventNameSymbol is triggered. The actions may be blocks or
messages.
canTriggerEvent: anEventNameSymbol
Answer a Boolean indicating whether the receiver can trigger an event
named anEventNameSymbol.
eventsHandled
Answers a collection of the events name symbols for which there are
actions registered in the receiver's event table.
hasActionForEvent: anEventNameSymbol
Answer a Boolean with regard to if the receiver has an action
registered for the event named anEventNameSymbol.

Trigger Event Support Methods In ApplicationModel


The following methods have been added to ApplicationModel to more
easily support configuring of triggered events for widgets. These methods
are the suggested way of configuring a widget’s triggered events. These
methods require that the widgets being configured have their ID assigned
when they were created with the UIPainter tool. The UIPainter has a
special Name All Unnamed Widgets menu option with which older window
specifications can be upgraded.
The following are shortcut methods that find the widget named
aWidgetIDSymbol, and then apply the appropriate when:send:to: message to
the widget.
widget: aWidgetIDSymbol when: anEventSymbol do: aBlock
widget: aWidgetIDSymbol when: anEventSymbol evaluate: anAction
widget: aWidgetIDSymbol when: anEventSymbol send: anAction to: anObject
widget: aWidgetIDSymbol when: anEventSymbol send: anAction to: anObject
with: anArgument
widget: aWidgetIDSymbol when: anEventSymbol send: anAction to: anObject
with: firstArgument with: secondArgument
widget: aWidgetIDSymbol when: anEventSymbol send: anAction to: anObject
withArguments: aCollection
The following methods allow easy lookup of widgets and widget
components without having to go through the application’s builder object.
We suggest using these message instead of the self builder messages
commonly used in VisualWorks applications.

Application Developer’s Guide 223


Chapter 9 - Trigger-Event System

wrapperAt: aSymbol
Answer the value of the named component at aSymbol. Typically gets
a SpecWrapper or nil. In the case of a toolbar, it gets the actual ToolBar
instance.
controllerAt: aSymbol
Answers the controller for the component associated with aSymbol.
The answer may be nil or a Controller. In the case of a toolbar, it will
be nil.
widgetAt: aSymbol
Answer the widget associated with aSymbol. Typically answers a kind
of VisualPart, which may be nil.
mainWindow
Answer the main window associated with this ApplicationModel
instances. Typically answers a ApplicationWindow. May be nil if the
window is not created yet.
windowMenuBar
Answers the instance of MenuBar associated with the main window.
May be nil if the window is not mapped and opened, or if there is no
menu bar associated with the main window.

224 VisualWorks
10
Debugging Techniques

Debugging is the, often difficult, task of tracking down causes of program


malfunction. Syntax errors are generally caught by the compiler. More
subtle errors, such as the mishandling of unusual assignments to a
variable, can take a lot of exploration to trace and resolve. To trace these
you need a mechanism for tracing the flow of a program and variable
assignments at varous points.
VisualWorks provides several facilities to help you debug your programs.
Software probes insert triggers into the compiled byte code stack, without
changing your source code, which either interrupt processing
(breakpoints) or log status information (watchpoints). A walkback window
is opened when an unhandled exception is detected, showing the last
several message sends. The Debugger tool allows for extensive
exploration of the history of message sends, for modifying variable
values, and modifying code on the fly, and for controlling program
execution. There are also several special-purpose object engines for
debugging problems with calls to external libraries or virtual machine
crashes.
This chapter describes these facilities and techniques for using them to
diagnose problems with your code. For details on using the debugging
engines, see “Debugging Within the Virtual Machine” at the end of this
chapter.

Application Developer’s Guide 225


Chapter 10 - Debugging Techniques

Software Probes
Software probes provide a mechanism analogous to hardware probes
used in troubleshooting electronic components, providing a way to check
the state of the system at a specific point. An electronic probe does not
change the design of an electronic circuit but, when used, it may change
the circuit's characteristics slightly. Similarly, using a software probe does
not change the source code design, but will affect the timing of the
program execution. In regards to a Smalltalk program, this means that the
source code is unchanged, so insertion and removal of a probe is not
logged, but program timing will be slightly changed. Usually, this is not a
problem.
A probe can be inserted before or after any message send, assignment
operation, or upon referencing a variable reference. Inserting a probe
actually inserts a message send to the probe object. Because a probe is
inserted by modifying the compiled method instead of source code, it is
possible to perform actions that are cumbersome to do within the
Smalltalk syntax.
There are two basic types: breakpoint and watchpoint. Every probe has a
conditional expression and an action. If the conditional expression returns
true, then the action is performed. In the case of an breakpoint, the
expression simply returns true. The action performed is determined by the
probe type.

Breakpoint
A breakpoint, which is the simplest kind of probe, immediately opens the
system debugger, skipping the notifier stage, when it is triggered. The top
method in the stack is the method containing the breakpoint. The current
message send depends on placement of the breakpoint. A breakpoint is
a better alternative to inserting self halt in code to invoke a debugger,
because it does not require a change in the source code.
A conditional expression may be used with a breakpoint, allowing you to
test for specific conditions and selectively trigger the breakpoint. The
expression can include any arbitrary operation, such as data collection.
However, it must return a Boolean upon completion. The debugger window
opens if the value is true, and does not open if the value is false.

226 VisualWorks
Software Probes

Watchpoint
Watchpoints display a string message in a watchpoint window, without
interrupting program execution.

The string provides information about the state of some part of the
program when the watchpoint is triggered. In general, the string is a
representation of an object.
There are four watchpoint types, which you select when creating the
watchpoint:
Top of Stack
Displays the value of the variable currently at the top of the argument
stack, which may be an argument or the last message result.
Instance Variable
Displays the value of the specified instance variable.
Temp Variable
Displays the value of the specified temporary variable.
Expression Watch
Displays the result of a Smalltalk expression, which must evaluate to
a String. This probe enables the user to properly display complex
information or to format a string in a more meaningful manner.
The String representing the object displayed by the Top of Stack,
Instance Variable, and Temp Variable probes is obtained by sending the
object the debugString message. This method is defined in class Object as
^self printString. It provides flexibility in representing an object.

Application Developer’s Guide 227


Chapter 10 - Debugging Techniques

Setting Probes
Inserting a probe into source code is done by selecting a menu command
in a browser and, in most cases, providing additional information in a few
probe editors.

Setting a breakpoint
A simple breakpoint is set in a method definition by placing the cursor at
the point at which you want to interrupt processing, and then selecting
Insert Breakpoint from the <Operate> menu. The character at the cursor
location is highlighted, indicating the breakpoint.
A breakpoint can also be made conditional. To do this, select Insert Probe
instead of Insert Breakpoint. Then, in the Select Probe Type dialog, select the
Breakpoint radio button and check the Conditional check box. Refer to
“Making a probe conditional” below for further information.

Setting a variable watchpoint


A watchpoint displays a message in a watch window without interrupting
processing, as does a breakpoint.
To set a variable watchpoint, place the cursor in a method definition, and
select Insert Probe in the <Operate> menu. In the Probe Type editor, select
the Variable Watch radio button.

228 VisualWorks
Setting Probes

Then click Done. The Select Watch Variable dialog opens.

The three buttons allow you to specify what variable to watch. You can
only specify one. Top of Stack will display in the watch window the value on
the top of the argument stack, which is either a message argument or
response. The Instance Variable and Temp Variable buttons pop up a list of
available variables (Temp Variable is active only if the method has
temporaries).
When you have selected the variable to watch, click Done. The probe is
set, and the watch window opens the first time the probe is triggered.

Setting an expression watchpoint


A watch expression provides a good deal of control over the display of
information. The expression must evaluate to a String, which you must
ensure in the expression, since the default debugString method is not
applied.
To set a variable watchpoint, place the cursor in a method definition, and
select Insert Probe in the <Operate> menu. In the Probe Type editor, select
the Variable Watch radio button.

Application Developer’s Guide 229


Chapter 10 - Debugging Techniques

Then click Done. The Expression Watch Probe editor opens.

The expression watch provides maximum probe control, so the editor


provides all of the editors. The top text box, the Conditional Test Expression
editor, is described below under “Making a probe conditional”. The Window
ID text field and associated buttons allow you to select the watch window
in which to display the watch expression.
The Watch Expression text box is where you specify what to display in the
watch window. The expression can be any Smalltalk expression that
returns as a String. This allows you to include descriptive text, values of
Smalltalk expressions, and some formatting. For example, you could use
an expression like this to display the value of currentRandomValue from the
Walk Through:
^‘The current value is: ‘, currentRandomValue value printString, ’. ‘
Carriage returns included in the string are displayed as carriage returns,
or you can include a backslash and send withCRs to the whole string:
^’The current value is:
‘, currentRandValue value printString, ‘.’

230 VisualWorks
Setting Probes

When you’ve entered an appropriate expression, accept the change and


click Done. The probe is set, and the watch window opens the first time the
probe is triggered.

Removing probes
You can remove probes either selectively or from an entire method.
To remove a single probe, select its highlighted character and select
Remove Selected Probe from the <Operate> menu.
To remove all probes from the method, select Remove All Probes from the
<Operate> menu.

Making a probe conditional


A conditional breakpoint interrupts processing at the set point only if the
specified condition is met. To place a conditional breakpoint, place the
cursor and then select Insert Probe from the <Operate> menu. The Select
Probe Type editor opens:

Leave the Breakpoint radio button selected, and check the Conditional
check box. Click Done. The Conditional Text Expression probe editor opens.

Application Developer’s Guide 231


Chapter 10 - Debugging Techniques

Initially the expression is simply false, which will prevent the breakpoint
from triggering. You need to replace this with an expression that will
evaluate to true in just those cases where you want the breakpoint to
trigger, allowing you to further investigate the state.
For example, in RandomNumberPicker from the VisualWorks Walk
Through, you could insert a breakpoint in the nextRandom message, and
set the conditional expression to:
currentRandomValue value < 0.5
to break only when the random value is smaller than .5.
When you have entered the expression, accept the change (Accept on
the <Operate> menu), and click Done. The probe highlight is placed and
the probe is set.
A probe expression is a normal Smalltalk expression, except that it has
additional variable scoping. This scoping permits the expression to
reference variables in the probed method context and instance variables
of its receiver. Additionally, each probe may refer to its own local debug
variables and to global debug variables. Menu commands are available in
the condition editor pane to define new variables.
There are two predefined variables that you can reference to access
information in the context where the probe activation occurs:
DOITCONTEXT and TopOFStack. The variable DOITCONTEXT holds the
context itself, and TopOFStack is the object on the top of the context stack.
An additional predefined variable, ThisProbe, holds probe state

232 VisualWorks
Setting Probes

information, such as its characterIndex. This is useful when constructing an


expression watchpoint that reports method selector or probe character
position.
When a probe is created, it is given a default conditional expression.
Initially, this expression is set to ^DebugActive. DebugActive is a global
debug variable, and is used to permit enabling and disabling of probes
using the Probes menu in the Visual Launcher.
The default expression can be changed by editing the class method
newDefaultMethodFrom:inClass: in class CodeProbe.
The conditional expression editor text view has the following commands
to assist in building expressions:
Insert var
Opens a series of menus and submenus containing all the valid local
variables, providing a convenient means to locate a variable name
and insert it into the text. The selected variable is inserted into the
text.
Define debug var
Allows the user to define debug variables, both local and global.
Inspect debug vars
Opens a dictionary inspector on either the local or global debug
variable dictionary. The actual dictionary inspected is determined by
the submenu item selected, local or global.
Reset method
Resets the expression to a standard expression and method. Using a
standard method can speed up insertion of multiple probes, when
inserting probes using the Message Received or Instance Variable Reference
panels, and probe expression recompilation, as a result of a class
redefinition. This is because these standard expression methods are
known to the system and do not require rebinding.
Insert expression
Displays a menu of expressions currently in the expression library.
The selected expression is inserted at the text insertion point.
Save expression
Prompts the user for a name to identify the expression, and then
saves the expression text in the expression library. There is one
library for the Test expression and another library for the Watch
expression.

Application Developer’s Guide 233


Chapter 10 - Debugging Techniques

Select a watch window


For watch probes, you can specify which window displays the expression.
This allows you to reuse existing watch windows, and to display multiple
watch strings in a single window.
When first specifying the probe, check the Window parameters check box in
the Select Probe Type dialog. For a variable watch probe, the Window ID
dialog opens after you have selected the variable. For an expression
watch, the Window ID selection dialog is included in the expression editor
dialog, as shown above.

Either enter the numeric ID of a watch window in the entry field, or click
Select ID and choose a window from the list.
The Open Window button opens the window immediately, rather than
waiting for the probe to be triggered.
The Change Label button allows you to enter a more descriptive window
label string. You can then use the label later to identify this window for
other watch probes.
When the window parameters have been set, click Done.

Modifying a probe
Watch probes and conditional breakpoint probes can be modified. The
variable of a variable probe cannot be changed, but the conditional test,
watch expression, and window parameters all can be changed.

234 VisualWorks
Setting Probes

To modify a probe, select it by selecting the highlighted character


representing it, and select Modify probe on the <Operate> menu. A probe
editor will be displayed. While the editor varies for different probes, the
editor options are all the same as those described above for setting the
probes.
Make the desired changes and click Done.

Probe location
When a probe is present in a method, its position in the source code is
indicated by highlighting the character at that position. Permanent probes
are indicated by underlining the character and coloring it red. Temporary
probes, which are only available in the debugger, are indicated by
underlining the character and coloring it yellow.
Because the highlighting is done with a text emphasis, operations that
change the text may remove the probe highlight without actually removing
the probe.
The meaning of the highlights in various situations are as follows;
Message selector
• First or only character - probe activation occurs before message
send.
• Last character or following space - probe activation occurs after
message send.
• Last character of first component of keyword - probe activation
occurs after message send.
Variable name
• First character - probe activation occurs before variable access
(usually an assignment).
• Last character - probe activation occurs after variable access
(usually a read).
Examples of probe highlighting are shown later in the paragraph
discussing probe insertion within the browser.

Recompiling a Probed Method


Whenever a method is recompiled, either due to a method accept or
class redefinition, the probes are removed from their method. However,
the browser will give the user the option to reinsert, or discard, the
probes. If the user chooses to reinsert the probes, the probes are

Application Developer’s Guide 235


Chapter 10 - Debugging Techniques

checked to determine if they are still consistent with the recompiled


method. If a probe expression is no longer consistent, the probe is still
reinserted, but it is disabled so the user can correct the problem. If the
variable being watched by a variable watch probe is removed, then the
probe will not be reinserted.

Limitations
Probe highlights
Performing a format operation in a browser causes the probe highlight to
be lost. If you then accept the change, probe is lost as well. Because
probes are maintained by their position in the source code, reformatting
accepting loses that position, so the cannot be reinserted.
Inserting probes at returns
The VisualWorks compiler compiles the following code:
^condition
ifTrue: [ expression1 ]
ifFalse: [ expression2 ]
as though it were written:
condition
ifTrue: [ ^expression1 ]
ifFalse: [ ^expression2 ]
That is, with two returns, one for each expression, rather than just one.
Because probes are added according to the parse tree, if you attempt to
probe the return value by inserting a probe at the return caret in the first
example, the result is as though one probe were inserted at the return
caret for only one of the expressions. The work-around is to insert two
probes, one at the end of expression1 and another at the end of
expression2.
The same situation occurs for the following code block:
[statements...
condition
ifTrue: [ expression1 ]
ifFalse: [ expression2 ] ] value
which is compiled as though it were written:

236 VisualWorks
Class Probes

[statements...
condition
ifTrue: [ expression1 blockReturn ]
ifFalse: [ expression2 blockReturn ] ] value
If a probe is placed at the condition, expecting to reflect the value
returned by one of the expressions, it would actually only capture one of
the expressions. This only occurs when the conditional statement is the
last statement in the block. Again, the proper work-around is to insert a
probe at the end of both expression1 and expression2.

Class Probes
Three commands on the Class menu in browsers provides for managing
probes at a class level: Add Class Probe, Remove Class Probe, and Browse Probed
Methods. These commands are aids to managing probes in bulk rather
than individually.

Adding class probes


The Class ­ Add Class Probe command allows you to insert probes into
several methods in a single operation. The probes share any conditional
expression and any watch expression, thus allowing a single watch
expression or conditional breakpoint to be used for several methods.
However, once inserted, the expressions become independent, so if an
expression is later modified, the change applies only to the one probe.
The command has two submenus; On Instance Variable Access... and On
Message Receipt... .

On Instance Variable Access...


The On Instance Variable Access... command inserts a probe at each point a
selected variable is referenced in each method within a group of
methods. If the reference is a read operation, the probe is inserted just
after the byte code operation that places the object on the stack. If the
reference is a write operation, the probe is inserted just before byte code
that stores the object into the variable.
When you select the command, the setup panel opens.

Application Developer’s Guide 237


Chapter 10 - Debugging Techniques

The panel displays a filtered list of methods that reference the selected
instance variable. Select a variable from the Selected Instance Variable drop-
down list. Then, select filtering criteria by the using the On Read, On Write,
and Include Subclasses check boxes. The type of reference is shown to the
immediate left of the method string. In the list, select the methods into
which you want to support the probe.
The proble action to perform on the selected methods can be one of the
following:
Breakpoint
Inserts a breakpoint at the variable reference point in each selected
method.
Smart Watch
Inserts an expression watch probe at the variable reference point in
each method. The expression returns a string containing the class
name, method selector and character position of the probe in the
method. When triggered, this string is recorded in the watch window.
Next the debugString message is sent to the object on top of the stack.
The resulting string is then recorded in the watch window on the line
following the method identification string.

238 VisualWorks
Class Probes

Simple Watch
Inserts an expression watch probe at the variable reference point in
each method. When triggered, the debugString message is sent to the
object on top of the stack and the returned string is recorded in the
watch window.
N Simple Watches
Inserts an expression watch probe at the variable reference point in
each method. When triggered, the debugString message is sent to the
object on top of the stack and the returned string is recorded in a
watch window. This differs from the "simple watch" in that each probe
has its own watch window.
Expression Watch
Inserts an expression watch probe, with a user defined expression, at
the variable reference point in each method. When triggered, the
expression is executed and the returned string is recorded in a watch
window. After the setup panel closes, an expression editor is opened
for you to define the expression (refer to “Setting an expression
watchpoint” above for an explanation of the editor).
Conditional
After the setup panel closes, a conditional expression editor is
opened for you to define the expression (refer to “Making a probe
conditional” above for an explanation of the editor).
Window parameters
After the setup panel closes, the "Window parameter" panel opens
for you to specify the watch window (refer to “Select a watch window”
for an explanation of the selector).
Generate report
Cause a report to be generated when the setup panel is closed. The
report lists all of the methods that have been selected to have a
probe inserted.
On Message Receipt...
The On Message Receipt... command allows you to insert a probe at the
beginning of each method in a selected group of methods. When you
select this command, the setup panel opens.

Application Developer’s Guide 239


Chapter 10 - Debugging Techniques

The panel displays a list of methods defined in the class and, optionally,
its subclasses. The type of action performed on the selected methods
can be one of the following:
Breakpoint
Inserts a breakpoint before the first statement in each selected
method.
Simple Msg Trace
Inserts an expression watch probe before the first statement in each
method. When triggered, the probes record the method receiver's
class and method selector in the watch window.
Ivar Watch
Inserts an instance variable watch probe into each selected method.
When triggered, the probes record a representation variable’s value
in the watch window. When this button is selected, the Select Variable
menu button is enabled, permitting you to select an instance variable.

240 VisualWorks
Class Probes

Expression Watch
Inserts an expression watch probe in each method. When triggered,
the probes cause the expression to be evaluated and the resulting
string to be recorded in the watch window. After the probe selection
panel closes, the expression editor opens (see “Setting an
expression watchpoint” for an explanation of the editor).
Conditional
After the setup panel closes, a conditional expression editor is
opened for you to define the expression (refer to “Making a probe
conditional” above for an explanation of the editor).
Window parameters
After the setup panel closes, the "Window parameter" panel opens
for you to specify the watch window (refer to “Select a watch window”
for an explanation of the selector).
Generate report
Cause a report to be generated when the setup panel is closed. The
report lists all of the methods that have been selected to have a
probe inserted.

Remove class probes


This command remove all the probes from the selected class's methods.
The command has two submenus: From This Class Only, which removes only
probes in the selected class, and From This Class and Subclasses, which
removes all probes from its subclasses as well.

Browse probed methods


This command will open a browser on all the probed methods in the
selected class.

Application Developer’s Guide 241


Chapter 10 - Debugging Techniques

Debugger
Walkback Notifier
When a program error occurs, a notifier window appears. This notifier
displays the last five message-sends in the context stack. The context
stack lists message-sends that were waiting for a return when the
breakdown occurred.

Sometimes that listing of the context stack is sufficient for you to identify
the problem and correct it. If so, click the Terminate button to close the
notifier and abort the program.
When the error is not so serious as to prevent proceeding with the
program (that is, it is a warning), you can click Proceed to close the notifier
and continue executing the program.

Debugger Window
When you need to examine the conditions that led to the failure more
closely, click Debug. The notifier is replaced by a debugger, which enables
you to trace the program flow leading to the error, proceed with execution
step by step, and examine the operative method and the values of the
variables at each stage of execution.
The VisualWorks debugger enables you to look at the methods that are
waiting for a return value when a program interrupt occurs, examine the
values of variables in each context, dynamically change a value or a
method, insert breakpoints, and restart execution at a chosen location
with the new values and logic experimentally in place.

242 VisualWorks
Debugger

At the top are three stack panes. On the left is the stack view, which lists
the message-sends that were waiting for a return at the time of the error.
The right two panes are the stack inspector, which allows inspection of
the selected expression’s intermediate stack values (see “Inspecting the
Stack” below for more information).
The code view is similar to the System Browser’s code view. When a
message-send is highlighted in the stack view, the corresponding method
is displayed in the code view. Within the method, the current point of
execution is automatically highlighted by the debugger.
At the bottom of the window are the instance-variable inspector, to the
left, and the temporary-variable inspector, to the right, which allow you to
examine the values of the variables. The variables and their values are
updated each time you choose a different position in the execution stack
with the stack view.

Application Developer’s Guide 243


Chapter 10 - Debugging Techniques

The debugger toolbar can be repositioned to below the stack panes by


changing the setting on the Debugger page of the Settings tool.

Reading the Execution Stack


To diagnose a problem, sometimes it is sufficient to see the last few
entries in the context stack. The Debugger’s top view lists as much of the
stack as you want to see, but you may not even have to launch the
Debugger. The error notifier that results from a program interrupt lists the
last five contexts. This error notifier shows the results of a programmatic
error (3 + ‘two’).

The window label tells us that a sumFromInteger: message was sent to an


object that does not implement a method by that name. (This summary is
repeated in the top line of the window, for situations in which the window
label is not wide enough to display all of the message.) Looking at the top
line of the stack, we see that it was an object of type ByteString. (ByteString
didn’t understand the message, so it invoked the doesNotUnderstand
method implemented by its parent class, Object). This is puzzling because
we sent a + message to a SmallInteger, as recorded in the second line of
the stack transcript. The last three lines of the transcript are not
enlightening —they merely expose some of the execution machinery,
which we have no reason to suspect in this case.
This example illustrates two features of the execution stack worth
emphasizing. The first line of the execution stack is often only of marginal
interest, because it usually represents the method that handles the
error—it doesn’t necessarily help you understand what caused the error.
Also, the execution machinery is a frequent inhabitant of the execution
stack—very quickly you learn to read around it.
Back to our example: Something odd happened in the SmallInteger>>+
method. You can either use the System Browser to look at that method,
or you can open a Debugger, as described in the next section.

244 VisualWorks
Debugger

The following figure shows a debugger displaying the results of a


programmatic error (3 + 'two').

Continuing our example from the previous section, in which the


expression 3 + 'two' was executed, we can see that the illegal expression
could not be handled by the primitive method that normally adds two
integers together. The alternative Smalltalk code was then executed.
Here we find the explanation for the mysterious sumFromInteger: message,
which was sent to a ByteString. As you can see, the + method calls the
sumFromInteger: method. But the receiver of the + message is the
argument (self) of the sumFromInteger: message. The message receiver
and argument have traded places. We know that the argument was the
string 'two', so the sumFromInteger: message is being sent to an object of
the wrong class, to a string instead of an integer. In the next section, we’ll
show how to verify this deduction.

Application Developer’s Guide 245


Chapter 10 - Debugging Techniques

Editing a Method Definition


The debugger code pane is a text editor, just like in a browser. You can
modify a method definition in the debugger, then accept the definition and
continue processing using the revised definition.
If you change the method selector of the definition and Accept the change,
the method is accepted and a method browser opens on the method.
After the browser opens, the debugger text pane is reset to the original
method text of the selected context. The effect is to create a new method
definition. The new method will be unparceled/unpackaged.

Inspecting and Changing Variables


The bottom of the Debugger is devoted to two inspectors that allow you to
see the values of variables as they exist at the chosen point in the
execution stack. Each inspector consists of a pair of views, with a list of
variables in the left view and the value of a selected variable in the right
view. The inspector on the left is for instance variables, while the right-
hand inspector displays temporary variables.
In the example that was introduced above, the expression 3 + 'two' has
caused the expression 'two' sumFromInteger: 3 to be executed. Now we
know where sumFromInteger: came from. We can also see why it was
“misunderstood” as indicated in the error notifier’s window label—it was
addressed to a string instead of the expected number. To verify this,
select aNumber in the inspector view.

246 VisualWorks
Debugger

The Debugger’s inspectors let you change the value of a variable and
then restart the program. Simply edit the value, changing 'two' to a legal
value such as the integer 2. Then select accept in the <Operate> menu.
You can then select Execute ­ Restart, and then Execute ­ Run to resume
execution.
In practice, the value 'two' normally would be supplied by another method
rather than a Workspace expression. Having traced the problem to this
value, you can correct its parent method. To do so, edit and Accept the
revised method in any code view such as the one in the Debugger or the
one in the System Browser.

Inspecting the Stack


The stack inspector occupies the upper right corner of the debugger. It
allows inspection of expression intermediate stack values. If the inspector
can determine that a message send will occur next, the intermediate
objects are shown in the field list as "arg1", "arg2", ..., "rcvr". Otherwise,
they are displayed as, "top", "-1", "-2", etc.
When stepping is performed, the inspector will automatically select the
topmost element, if one is present. This will allow immediate observation
of message returns. However, one should be aware that this element is
not always the result of the last message send. If the user deselects the
selected element then the inspector will not automatically select the top
element when a step is performed. Use this feature when an object does
not respond to the printString message properly.

Tracing the Flow of Messages


As described above, the error notifier displays the last five message-
sends in the execution stack. When you need to look at one or more of
those methods, the Debugger is the most convenient tool to use.
The Debugger’s execution stack view, at the top, contains the most recent
message-sends that occurred before the error. To see the associated
method, select a message-send. In the illustration, SmallInteger>>+ has
been highlighted. The code view, in the center of the Debugger, displays
the method. Within that method, the message-send that was being
processed when the program failed is highlighted automatically.
Several commands are provided, by menu and by button, to walk through
the flow of messages. Select a message send in the step, and then use
the following commands to trace the message flow.

Application Developer’s Guide 247


Chapter 10 - Debugging Techniques

Stack menu
Copy Stack Report
Copies the context list to the clipboard so it can be pasted into a
document or workspace.
Show More Stack
This command adds more contexts to the context list. Under normal
conditions the debugger opens with the stack size set to 500, so this
command is seldom needed.
Filter Stack
Enables stack filtering, as specified in Settings tool, on the Debugger
page. The editor allows one to specify coloring of the context items
according to matching rules. For more information view the editor
help.
Use Short Class Names
When selected, displays only the class names, without the dotted-
name prefix for classes that are not visible outside the Smalltalk name
space.
Select Home Context
Searches the stack and selects the home context of the currently
selected context. If the home context is not on the stack, a dialog will
inform the user of the situation.
Inspect Context
Opens an inspector on the method context.
Bookmark Context
Highlights the stack item (context) and adds it as an item on the
Stack ­ Bookmark menu, for easy access to this context.
Clear Bookmark
Clears the bookmark for this context.
Method menu
Most of these menu items are the same as in the System Browser. The
only exception is:
Recompile with Full Blocks
Recompiles the method so that all the blocks are full blocks. This also
has the effect of causing the method to be reentered, i.e., the
execution state of the method is reset. An Accept command also
causes the method to be reentered. This method is a temporary
method and disappears when a method return is executed.

248 VisualWorks
Debugger

Execute menu
Step Into
The most detailed stepping operation. When a message send is
selected, it sends the message and displays the resulting context.
Otherwise, it steps through the method, stepping into blocks along
the way.
Step
Steps through the method, stepping into blocks along the way.
Step Over
Steps through the method, stepping over blocks as they occur.
Restart
Initializes the selected context and restarts execution at beginning of
its method, as if the debugger had just stepped into it. The method
may be either a CompiledMethod or a CompiledBlock.
Return
Allows the selected method or block to discontinue further execution
and return immediately to its sender.
Run to caret
Advances to the caret, either into or out of a block closure. This is
limited to full block closures. If a return is encountered within the
selected context before the caret is reached, execution will stop
before executing the return. However, if the return is within a block
closure the method may return, at which point execution will stop.
Jump to caret
Jumps over code to the next caret, without execution. It causes the
execution point to be positioned at the beginning of the statement
containing the caret. A jump to caret into or out of a block closure
cannot be performed. However, it is possible to jump into and out of
conditional blocks, because they have been optimized by the
compiler and are not real block closures. Also, it is not possible to
jump into a loop, even if it has been optimized by the compiler.
Run
Continues execution from the current location.
Run with Break on Return
This command and the next are useful for debugging loops. This
command is similar to Run, except that an implicit breakpoint is set to
be triggered upon return from the current context. Also, the debugger
remains open. Execution stops either upon return from the context, or
if another breakpoint is encountered before then. Execution is
guaranteed to stop, so runaway loops can be interrupted.

Application Developer’s Guide 249


Chapter 10 - Debugging Techniques

Run with Break Again


Like Run with Break on Return, except that it does not establish a
breakpoint to be triggered on return. This option becomes available
only after a breakpoint has been set up by Run with Break on Return.
Refer to “Iteration debugging” below, under “Debugging Tips”, for
more information
Terminate
Terminates the process being debugged and closes the debugger.
This is the same action that occurs when the window is closed using
a window close command.
Abort
This command is activated when the code is running, during one of
the Step commands or Run with Break on Return. The step has to take a
significant time to run before you will notice that the command is
available.
Correct menu
Define method
Activated when when the top context is a MNU, this command inserts
a new method definition for the not understood selector, which simply
calls halt. The message is defined in UndefinedObject.
Correct selector
Activated when when the top context is a MNU, this command
presents a list of suggested correct spellings of the not understood
message selector. If the correct selector is in the list, select it, and the
command corrects the source code, recompiles the method, then
does the send.

Inserting Probes in the Debugger


The debugger code view <Operate> menu has the same probe
commands as the browser code view, and adds support for temporary
probes. Temporary probes persist only as long as the debugger does,
and are then removed. Probes can be added in the debugger without
having to restart the context (see “Probe context management” below for
details).

Temporary Probes
Temporary probes are probes that disappear when the method returns,
as they only apply to the method context and its blocks. They appear in
the text view as yellow highlights, instead of red like permanent probes.

250 VisualWorks
Inserting Probes in the Debugger

Creating a temporary probe is accomplished by an additional button on


the Probe Selection Panel.

The Default to Temp probes or Default to Perm probes command on the Visual
Launcher Probes menu sets whether the Temporary button is initially set or
cleared. Additionally, the command determines if the "insert breakpoint"
command will insert a temporary breakpoint or a permanent breakpoint.

Probe context management


You can insert and remove probes without having to restart the context.
When a probe is inserted into a method in the browser, the method is
changed from a CompiledMethod to a ProbedCompiledMethod, and all of its
blocks are changed from CompiledBlock to ProbedCompiledBlock.
Furthermore, when a second probe is inserted, a copy of the first probed
compiled method is created and the probe is inserted into the copy. This
is done so that an active process will not inadvertently have its method
changed, thereby causing a VM crash.
However, when a probe is inserted into a method in the debugger, it is
important that the change be reflected in the selected context and any
contexts and closures that are descendants of the home context of the
selected context. This is accomplished by performing the following
procedure whenever a probe is inserted or removed within the debugger.
1. If the home context of the selected context cannot be found, i.e. one
of the block closures between the current context and the home
context is not a full block closure, the operation is terminated.
2. If one of the block closures, between the block containing the text
insertion point and the home context, is not a full block closure, the
operation is terminated. Full closures are required because the home
context of a non-full closure cannot be located.

Application Developer’s Guide 251


Chapter 10 - Debugging Techniques

3. Probes inserted into or removed from a method will only affect the
home context, block closures, and block contexts that are
descendants of the home context. Contexts and closures that are a
result of a different message send, but the same method, will not
have the probe operation performed on them.
Refer to the following section on Debugging tips for assistance with
problems with inserting probes into blocks.

Debugging Tips
Inserting probes into blocks
When a probe is inserted into a method, the compiled method is replaced
with a probed compiled method. If the probe was inserted via the browser
all the blocks are recompiled as full blocks. If the probe was inserted via
the debugger then the block structure is not changed. The importance of
this is that in order to insert a probe in a block via the debugger the block
must be a full block. This also affects the operation of the debugger
Skip to caret command, which operates by inserting a temporary breakpoint
in the method, continuing execution, and then removing the breakpoint
when it is encountered.
If you wish to insert a probe into a block that is not a full block you can
use the debugger Make full blocks menu command, or you can insert a
probe into the method using the browser before the method is executed.
If the method of interest is a method that cannot be halted with a
breakpoint, you can disable it by inserting a conditional breakpoint and
have the conditional expression return false. When the method is
subsequently entered, in the debugger, all its blocks will be full blocks
which will permit temporary breakpoints to be inserted in a block as well
as using skip-to-caret into or out of blocks. The "Implementation Limits"
document, in the doc/TechNotes directory, has a more complete
description of blocks.

Iteration debugging
Frequently, one would like to continue execution in the debugger for the
next iteration for some iterator construct. The Execute ­ Return with Break on
Return and Return with Break Again commands provide this capability.

These two commands are especially useful for debugging loops. You can
set a breakpoint inside a loop, and then use Run with Break on Return to start
execution. It will stop either on the breakpoint inside the loop or, if the

252 VisualWorks
Debugging Tips

loop did not iterate, upon return from the method. When stopped inside
the loop, you can use Run with Break Again to do the next iteration, with a
protection against "running away" in case there is no next iteration.
The following steps illustrate how to do this.
1 Insert a temporary breakpoint in the loop code where you want
control to be returned to the debugger, or in some message that is
sent from the loop.
2 Select the either the home method context or a context between the
block context and the home context.
3 Issue the Run with Break on Return command.
4 When the process stops inside the loop, perform successive
iterations by issuing the Run with Break Again command. It does not
matter what context is selected when the command is reissued.
5 If you want to reset the guard context, select the desired context and
issue the Run with Break on Return command.

Interrupting a Program
In addition to inserting breakpoint probes, you can manually stop a
Smalltalk program by typing a user interrupt key sequence or by inserting
a halt message in the program.
<Control>-y invokes the user interrupt function. Enter this key sequence
when you want to freeze a program that is looping endlessly, or to capture
its state at a specific observable stage.
<Control>-\ freezes all user processes and opens a process monitor,
allowing you to explore them individually.
Inserting the expression self halt in a method at the location where you
want execution to be interrupted, used to be normal practice. In the
presence of breakpoints, this is seldom necessary, but is an option. When
a self halt is encountered, the Debugger is opened immediately, by-
passing the initial walkback.

Application Developer’s Guide 253


Chapter 10 - Debugging Techniques

Global Probe Management


The Visual Launcher has a Debug menu with commands that give general
control over probes and other debugging features. Commands for the
probe and expression libraries are described in the following subsections.
Enable probes / Disable probes
Sets the global debug variable DebugActive, to true for enable, or false
for disable. Unless changed by the user, all probes use the
expression ^DebugActive as their conditional expression. If Disable
probes is listed, then debugging is active; if Enable probes is listed, then
debugging is inactive.
Remove All Probes...
Clears all probes.
Remove Unused Watch Windows
This command removes unused watch windows from the watch
window dictionary.
Remove Unused Debug Variables
This command removes unused debug variables, those that are not
referred to by any probe expression, from the debug variable pool
dictionary.
Browse Probes
Opens a method list browser on all methods with probes.
Inspect Debug Variables
Opens an inspector on the debug variable pool dictionary.
Probe Library
Provides submenu items to Load a probe library or to Save the
current probes into a library.
Watch Library
Provides submenu items to manage the watch expression library.
Test Library
Provides submenu items to manage the test expression library.
Process Monitor
Opens the Process Monitor.

Probe library
The probe library feature stores collections of probes in external files.
This allows you to use a standard set of probes to employ in debugging a
new image. For information about the file format, read
pdp/[Link].

254 VisualWorks
Global Probe Management

The following commands, on the Probes menu in the Visual Launcher, are
used to save and load the probe library files.
Probe Library ­ Load
This command will save all the probes in the image to the specified
file.
Probe Library ­ Save
This command will read the specified file and load the described
probes into the image.

Expression libraries
Two expression libraries are provided to assist in using common probe
expressions. One library is for test expressions ([Link]) and the
other is for watch expressions ([Link]). Both libraries are in the
pdp/ directory.
The following commands for managing the libraries are on the Debug ­
Watch Library and Test Library submenus in the Visual Launcher. Each
command has two submenus which select either the test expression
library or the watch expression library.
Load
This command prompts for the name of a file containing an
expression library. The entries contained in the file will be added to
the entries already in the library.
Save
This command prompts for the name of a file into which to save the
expression library.
Inspect
This command opens an inspector on the library.
Clear
This command clears the expression library of all entries.

Storing CompiledMethods Externally


Occasionally, it becomes necessary to store a CompiledMethod to in
external file. This is currently done with BOSS, parcels, and Store.
Any method that has a probe inserted in it is represented by an instance
of ProbedCompiledMethod. The probed compiled method replaces the
normal compiled method in the method dictionary. Therefore, whenever
an operation to write the method to an external file is performed, one
must insure that the original compiled method is used instead of the

Application Developer’s Guide 255


Chapter 10 - Debugging Techniques

probed compiled method. This package has modified the necessary


methods to insure that the normal operation of the base system will not
write a ProbedCompiledMethod to a file.
The following methods can be used to assist the user in ensuring that a
ProbedCompiledMethod is not written to a file as a result of additional
system enhancements.
CompiledMethod>>isProbed
Returns false.
ProbedCompiledMethod>>isProbed
Returns true.
CompiledMethod>>originalMethod
Returns self.
ProbedCompiledMethod>>originalMethod
Returns the original compiled method.
CompiledMethod>>revert
Does nothing.
ProbedCompiledMethod>>revert
Puts the original compiled method back in the method dictionary.
Behavior>>revertAllProbedMethods
Insures that all the methods in the method dictionary are the original
compiled methods.
Behavior>>revertAllProbedMethodsInTree
Insures that all the methods in the method dictionary of the receiver
and its subclasses are the original compiled methods.

Debugging Within the Virtual Machine


The standard VisualWorks distribution includes several special-purpose
object engines that may be useful when debugging crashes during calls
to external libraries (C or COM, for example) or within the object engine
itself.
During normal development and debugging, we recommend using the
“unstripped” engines, which include symbols for the platform’s debugger.
These engines are named vwPlatformName (e.g., vwlinux86 or
[Link]) to distinguish them from the standard engines.
Object engines with additional platform debugging features are also
available. Refer to Appendix C, “Virtual Machines” for details.

256 VisualWorks
11
Exception and Error Handling

Exceptions are unusual or undesired events that can occur during the
execution of a VisualWorks application. While not all exceptions are
errors, errors are among the most important exceptions that your
application needs to handle.
When an exception occurs, an application might need to take some
special action. For example, if an application is reading data from a file
and unexpectedly encounters an end-of-file, it might stop processing and
display an error message. Using the exception handling features in
VisualWorks, the application can trap the exception and invoke the
special processing.

ANSI Exception Handling


VisualWorks implements an ANSI compliant, class-based exception
handling mechanism. All new applications should use the mechanism
described in this section. For most purposes, and in most parts of the
system, this class-based mechanism has replaced the earlier Signal
based mechanism.
The most conspicuous difference from earlier exception handling
mechanisms is that ANSI style exceptions and errors are represented as
classes in the Exception hierarchy, rather than as instances of Signal.
Support for the Signal mechanism is retained in the system, and is still
used by parts of the system. There may be cases that the Signal
mechanism is preferred. In general, however, we recommend that you
use the class-based system, because it is ANSI Smalltalk compliant.

Application Developer’s Guide 257


Chapter 11 - Exception and Error Handling

Adapting Signal-based Code


There are a few things you must do to old code to continue using the
Signal mechanism.
One change you do not need to make is the use of the signal message.
Rather than change the meaning of signal so that it raises an exception,
as we would in order to be in accord with the X3J20 specification, we
have left signal unchanged and introduced the message raiseSignal. This
eliminates the need to change your code.
Reinitializing Signal Creators and Initializers
The Signal creation code has changed, so you need to reinitialize all of
your signals to register them with the new Exception hierarchy.
Name Signals
Because of the possibility of duplicate instances, signal identity cannot be
used. Each signal creation message invokes nameClass:message:.
The standard signal creation looks something like:
Object errorSignal newSignal
notifierString: 'problem';
nameClass: self message: #problem.
Do Not Depend on Signal noHandlerSignal
Exceptions are not guaranteed to signal an UnhandledException as the old
system did. For example, Notifications do not because they are considered
ignorable.
However, you can handle noHandlerSignal to ensure that there are no
walkbacks, or to give all other handlers the priority. To use Signal
noHandlerSignal, for example to capture notifications, you should change
the method. For example, if you have a method such as:
^Signal noHandlerSignal
handle: [ :ex |
ex parameter getSignal = self class someSignal
ifFalse: [ex reject ].
^true ]
do: [ self class someSignal raiseRequest ]
"ask outerscope caller"
replace it with the equivalent:
^[ self class someSignal raiseRequest ]
on: self class someSignal
do: [ :exp | exp isNested ifTrue: [ exp pass ] ifFalse: [ true ] ] .

258 VisualWorks
Exception Classes

If someSignal #defaultAction is to answer true, then this is equivalent to:


^self class someSignal raiseRequest.

Exception Classes
Exceptions are represented as instances of classes, with Exception at the
top of the class hierarchy. It has several direct subclasses, two of the
most important being Error and Notification. Subclasses of all these define
more specific kinds of exceptions which can be trapped by your
application. Your application can define its own Exception subclasses for
special exceptions and errors.
Each exception class either defines or inherits a defaultAction message,
which is invoked when that exception occurs unless a handler is defined
for it. The table below lists some common exception classes with the
exceptional event represented by the class and the default action it
performs.
Exception Classes and Their Default Actions
Exception Class Exceptional Event Default Action
ArithmeticError Any error evaluating an Inherited from Error
arithmetic operator
Error Any program error Open a notifier
MessageNotUnderstood A message was sent to Inherited from Error
an object that did not
define a corresponding
method
Notification Any unusual event that Do nothing,
does not impair continuing executing
continued execution of
the program
Warning An unusual event that Display a Yes/No
the user should be question dialog and
informed about return a Boolean
value to the signaler
ZeroDivide An attempt to divide by Inherited from
zero ArithmeticError

All instances of Exception and its subclasses respond to the message


description by returning a string that describes the actual exception.

Application Developer’s Guide 259


Chapter 11 - Exception and Error Handling

Your application can have its own exception conditions,which are distinct
from those provided with VisualWorks. To identify the exception, create a
subclass of Exception or Error, as appropriate. If special handling is
required for the exception, you must define a handler for it, as explained
in the following sections.
The occurrence of an exception normally causes VisualWorks to discard
the work in progress. Sometimes a method does something that requires
a subsequent action, regardless of whether or not an exception occurs. In
that case, use the unwind mechanism described at the end of this
chapter.

Handling Exceptions
The default action for most exceptions is to display a notifier. For
development this is useful, allowing the developer to seek out the cause
and repair it. However, for an application, a notifier is not appropriate, and
the exception needs to be handled by the application itself. To handle
exceptions in an application you define an exception handler.
An exception handler has two parts: the class of exception for which it
watches, and the block of code (the handler block) to be executed when
such an exception occurs. The handler block must be a one-argument
block.
Exception handlers are defined using the on:do: message. For example,
the following expression defines an exception handler for an attempt to
divide by zero, and specifies that a message be printed in the Transcript:
| xy|
x := 7.
y := 0.
[x / y]
on: ZeroDivide
do: [ :ex | Transcript show: 'zero divide detected'; cr.]
If a zero divide error occurs while evaluating [x / y], the handler block (the
argument to do:) is evaluated, causing the message to be written to the
transcript.
When creating exception handlers for your application, be as specific as
makes sense in naming the exception to which the handler responds. For
example, it might be reasonable in some contexts to trap any error,
without being any more specific than calling it an Error. In this case, an
expression like the following makes sense:

260 VisualWorks
Handling Exceptions

[... some work ...]


on: Error
do: [ :ex | Transcript show: 'An error occurred'; cr.]
The information returned is minimal, but might be enough. However, you
probably do not want to handle every exception that occurs, so do not use
an expression like this:
[... some work ...]
on: Exception
do: [ :ex | Transcript show: 'An exception occurred'; cr.]
Exception is too general a category, and so your application would
respond to anything, including signals to Notification that have no effect on
your application.
An exception handler normally completes by returning the value of the
handler block in place of the value of the receiver block. The above
example, therefore, would return the Transcript, which might not be
terribly useful.
Suppose you want to return the value 0 when a division by zero occurred.
You could then rewrite the expression as:
[x / y] on: ZeroDivide do: [0]
This could be used in some such code as the following:
fudgeFactor := [x / y] on: ZeroDivide do: [0].
If, instead of returning a value, you want to exit the current method, you
can place an explicit return within the handler block:
fudgeFactor := [x / y]
on: Error
do: [ :ex | ^'uncomputable'].
This example specifies Error as the exception to be handled instead of
ZeroDivide. When you specify an exception class, the exception handler
handles exceptions of the specified class as well as exceptions that are
instances of subclasses of the specified class. ZeroDivide is a subclass of
DomainError, a subclass of ArithmeticError, a subclass of Error. Therefore,
an attempt to divide by zero or any other error that occurs while
evaluating x / y causes the enclosing method to return the string
'uncomputable'.
Sometimes an exception handler needs to obtain information about the
specific exception that it is dealing with. This can be accomplished by
using a single argument block as the exception handler:

Application Developer’s Guide 261


Chapter 11 - Exception and Error Handling

[x / y]
on: Exception
do: [:theException |
Transcript show: theException description.
^'uncomputable'].
The instance of the class of exception that occurred is passed as the
argument to the handler block. In the above example, the exception
object could be an instance of ZeroDivide, ArithmeticError, or Exception.

Exception Sets
Occasionally it is necessary to establish an exception handler to handle
several exceptions that are not necessarily related in a hierarchy. This
can be accomplished by using an ExceptionSet. If any exception in the set
occurs, or any subclass of a listed exception, the handler block is
activated.
You can implicitly create an exception set by specifying a list of
exceptions in a handler. For example:
[do some work]
on: ZeroDivide, Warning
do: [ :theException | whatever]
Sending the , (comma) message to an exception class with another
exception creates an instance of ExceptionSet.
If you need to reuse the same set of exceptions, you can also create an
exception set explicitly and assign it to a variable:
specialExceptions := ExceptionSet with: ZeroDivide with: Warning
The exception set can then be used as the argument to on: in an
exception handler.

Signaling Exceptions
Most of the exceptions that your application needs to handle are detected
by code within the standard VisualWorks class library. Occasionally,
however, you may need to write a new method to signal the occurrence of
an exception, particularly if you have also created a new class of
exceptions.
An exception is signaled by sending the message raiseSignal or raiseSignal:
to the class that defines the exception. For example:
Error raiseSignal

262 VisualWorks
Exception Environment

creates an error exception. If a specific handler has been defined to deal


with the Error exception, it is executed. Otherwise, the default handler is
executed.
It is often useful to provide a textual description of the problem when
signaling an exception. You can do this using the message raiseSignal:.
Warning raiseSignal: 'the disk is almost full'
The argument string to raiseSignal: is incorporated into the value returned
when the message description is sent to the resulting exception object.
It is also useful to raise an exception wth a specific parameter, rather than
the default, which is the error itself. In this case you can send signalWith:,
with the object to be returned as the argument. For example, it is
occassionally more useful to raise the exception passing the object itself
as the parameter, rather than the exception:
Exception signalWith: self
If you define new Exception classes, it is most reasonable to create them
as subclasses of either Error, for non-resumable conditions, or Notifier, for
resumable conditions.

Exception Environment
Each VisualWorks process has a distinct exception environment, which is
an ordered list of active handlers. When a new process begins, the list is
empty. When the receiver block of an on:do: statement is executed, its
exception handler is added to the beginning of the list, and its entry is the
on:do: statement. If another exception handler is defined within the
receiver block, it is added to the beginning of the exception environment
list for the process.
[ block 1 stuff
[ block 2 stuff
[ block 3 stuff
[ block 4 stuff ]
on: ColorError
do: [ handler code for 4 ] ]
on: Warning
do: [ handler code for 3 ] ]
on: Error
do: [handler code for 2] ]
on: ZeroDivide
do: [ handler code for 1 ]

Application Developer’s Guide 263


Chapter 11 - Exception and Error Handling

The following figure illustrates a hypothetical exception environment.

newest or
innermost

ColorError

direction of search
Warning

Error

ZeroDivide

oldest or
outmost

If an exception is signaled within an exception environment, the exception


handling system sends a message to the first entry in the list, the most
recently added, to determine if it handles the specific exception
generated. The first exception handler encountered that can handle the
signaled exception does so.
Suppose the code in this exception environment is executing, and a
ZeroDivide error is signaled. The first active exception handler handles a
ColorError, so it is not executed. The next handles a Warning, so it is not
executed either. The third handles an Error, which is a superclass of
ZeroDivide. It therefore can handle the ZeroDivide exception, and does so.
The Error exception handler executes its do: block, thereby creating a new
exception environment of its own. In this case, the new exception
environment has only a ZeroDivide handler in it, because that was the only
handler created before the Error handler.
When a handler block is executed, the exception environment is
“trimmed” to include only those active handlers created before the
handler that is executing. These older handlers constitute the active
handler’s exception environment. The active handler’s exception
environment is the exception environment as it was at the time that the
on:do: message was sent.
If the exception handler resumes, the original exception environment is
restored; otherwise, it is discarded.

264 VisualWorks
Exiting Handlers Explicitly

If no handler is found for an exception by searching the exception


environment, the defaultAction method for the exception is executed. When
a default action method is executed, the exception environment is the
same as it existed when the exception was signaled.

Exiting Handlers Explicitly


Occasionally you may need to manage the flow of control among multiple
exception handlers. The following messages can be sent to the argument
of a handler block to conclude processing of the handler block before it
reaches its final statement, or to interrupt its processing and return to it
later:

exit or exit: Resumes on resumable exceptions; returns on


nonresumable exceptions. (Note that this is a
VisualWorks extension to the ANSI specification.)
resume or resume: Attempts to continue processing the protected
block, immediately following the message that
triggered the exception.
return or return: Ends processing of the protected block that
triggered the exception.
retry Re-evaluates the protected block.
retryUsing: Evaluates a new block in place of the protected
block.
resignalAs: (See “Translating Exceptions” below.)
pass Exits the current handler and passes to the next
outer handler; control does not return to the
passer.
outer Similar to pass, except it regains control if the
outer handler resumes.

The messages exit:, resume:, and return: return their argument as the
return value, instead of the value of the final statement of the handler
block.
The message exit is provided by VisualWorks for conditionally exiting a
complex handler block. For resumable exceptions, it sends a resume
message, which restores the environment in which the exception
occurred and continues processing. For nonresumable exceptions, it
sends a return message, which trims the exception environment to the
active handler’s exception environment.
For example:

Application Developer’s Guide 265


Chapter 11 - Exception and Error Handling

[Error raiseSignal]
on: Error
do: [:exception |
exception isResumable
ifTrue: [exception exit: 5].
Dialog warn: 'Nonresumable exception']
Because Error is a nonresumable exception, the warning dialog is
displayed. Replacing the protected block with [ Notification raiseSignal ] and
testing for Notification instead will exit (resume) with a return value of 5.
If the argument of a handler block is a resumable exception, the message
resume can be used instead of exit, which behaves in exactly the same
manner as exit for resumable exceptions. Attempting to resume a non-
resumable exception causes an “attempt to proceed” error.
To terminate and return from the block that triggered the exception, send
a return message. When sent to a resumable exception, return forces
control to return from the protected block instead of returning to the
message that triggered the exception. Thus, return can simulate the effect
of a nonresumable exception when an exception is in fact resumable. The
message return trims the exception environment to the active handler’s
exception environment.
Another way to exit a handler block is with the retry message. This
message terminates the handler block and tries again to evaluate the
receiver of the on:do: block. Any cleanup blocks created using the unwind
mechanism are executed before retrying, whether they were created by
the original evaluation of the receiver block or by the handler block.
For example, the following method tries again after a division-by-zero
error:
[^ x / y]
on: ZeroDivide
do:
[:exception|
"make the divisor very small but > 0"
y := 0.00000001.
exception retry]
The message retry therefore trims the exception environment to the active
handler’s exception environment when it retries execution.
The message retryUsing: does a retry, but evaluating the block passed as
argument instead. For example:

266 VisualWorks
Resumable and Nonresumable Exceptions

[self doTaskQuickly]
on: LowMemory
do: [:exception|
exception retryUsing: [self doTaskEfficiently]]
The message retryUsing: also trims the exception environment to the
active handler’s exception environment when it retries execution.
The message pass can be used inside a handler block to terminate the
handler block and execute any enclosing handler blocks for the current
exception. For example:
[n / m]
on: ZeroDivide
do:
[:exception|
"0/0 = 1; otherwise raiseSignal a ZeroDivide exception"
exception dividend ~= 0
ifTrue: [exception pass]
ifFalse: [exception return: 1]
The message pass sets the exception environment to the environment of
the handler to which it passes control.
In this example, the programmer decided to handle the case of 0 / 0
specially. If the dividend is anything other than zero, however, control
passes to the ZeroDivide exception. Control never returns to the sender of
a pass message.

Resumable and Nonresumable Exceptions


A handler block normally completes by executing the final statement of
the block. The value of the final statement is then used as the value
returned by the exception handler. Exactly where control should be
returned with that value, however, depends upon whether an exception is
resumable or not. A nonresumable exception must return from the on:do:
expression that created the handler block. However, a resumable
exception usually returns from the message that signaled the exception.
It is so called because it resumes execution rather than returning from the
exception.
Resumability is an attribute of an exception, not of an exception handler.
Most subclasses of Error are nonresumable and therefore do not return to
the method that signaled the exception, but return directly from the
handler block. On the other hand, exceptions such as Notification and

Application Developer’s Guide 267


Chapter 11 - Exception and Error Handling

Warning are not errors, and are generally resumable. Resumable


exceptions typically return the value of the active handler for the
exception from the signaling message:
Warning raiseSignal: 'Low memory, save files!'
The return/resume behavior must be made explicit by sending a return: or
resume: message in the handler block. For example, the following
expression returns 'Value from handler' as the value of the on:do: message
because the signaled exception is an instance of Error, which is
nonresumable:
( [Error raiseSignal. 'Value from protected block']
on: Error
do: [ :ex | ex return: 'Value from handler'])
The next expression, however, returns 'Value from protected block' as the
value of the string, the last expression in the protected block, because the
signaled exception is an instance of Notification, which is resumable:
([Notification raiseSignal. 'Value from protected block']
on: Notification
do: [ :ex | ex resume: 'Value from handler']).
Exception handling can be generalized by explicitly testing whether the
exception is resumable, using the message isResumable. In the following
example, the exception handler returns either 5 to the signaler or 10 from
the on:do: message, depending upon whether the exception class is
defined to be resumable or nonresumable:
[ someExceptionClass raiseSignal ]
on: Error
do:
[:exception|
exception isResumable
ifTrue: [5]
ifFalse: [10]]
Most exception classes inherit whether they are resumable or
nonresumable from their superclasses. To specify the resumability of a
new exception class, initialize its isResumable instance variable to true.

Note: Signaling a resumable exception while evaluating the


protected block of an unwind message does not cause the cleanup
block to be executed, because execution of the protected block
resumes instead of terminating.

268 VisualWorks
Translating Exceptions

Translating Exceptions
Occasionally, an exception handler might need to translate one exception
into another exception. This is usually done to provide more information,
or to consolidate low level exceptions to a higher level one. For example,
a low-level operating system error exception might need to be translated
into a higher level user exception.
Care is required to avoid executing the wrong handler. The reason is that
the exception environment within the handler signalling the low-level
exception is not necessarily the same as the exception environment
signalling the high-level exception. This problem is solved by using the
message resignalAs: instead of raiseSignal within the handler block. For
example:
[low-level I/O]
on: OperatingSystemException
do: [ex|
ex errorCode = -213
ifTrue: [ex resignalAs: EndOfFile new]
ifFalse: [ex resignalAs:
(Error new messageText: 'OS Error']]
The message resignalAs: aborts the current exception handler, restoring
the exception and execution environments to the states they were in
when the exception that is the receiver of resignalAs: was originally
signaled. (Note that this can cause the execution of unwind blocks). After
the environments are restored, the exception that is the argument to
resignalAs: is signaled. This causes the argument exception to function as
if it had been originally signaled in place of the receiver.

Unwind Protection
When a block of expressions contains opportunities for a premature
return, a means of cleaning up the mess may be required.
Providing such a mechanism is a kind of exception handling, though it is
accomplished with a variant of the value message that initiates a block.
Use ifCurtailed:, with the cleanup expressions as the argument block. The
cleanup block is used if the execution stack is cut back because of a
signal, if a return is used to exit from the block, or if the process is
terminated.

Application Developer’s Guide 269


Chapter 11 - Exception and Error Handling

To execute the cleanup block after either a normal or an abnormal exit,


use ensure:. Remember that these messages are addressed to a block,
not to a signal.

Using a Signal to Handle an Error


The Signal class provides an instance-based mechanism for signaling and
catching an error. This is the original exception handling mechanism
implemented in VisualWorks, and is largely, but not entirely, superceded
by the class-based system described previously.
Catching an error using this mechanism involves creating an instance of
Signal and telling it what you plan to do and how to handle an error. This is
accomplished with a handle:do: control structure. In pseudocode form, the
resulting expression for our calculator’s division method is:
aSignal
handle: [error handling code]
do: [the division operation].
The error that triggers the handle: block is an instance of Exception. Hence,
dynamic error trapping in Smalltalk is usually called exception handling.
An Exception is created by a raise message sent to a Signal. In our
example, the method that performs the actual division would send a
message such as:
aSignal raise
Thus, exception handling involves two steps: Placing a Signal handler to
watch over a block of expressions, and raising an Exception when an error
occurs.

Choosing or Creating a Signal


To create a new instance of Signal, use Signal new. The resulting instance
has a parent of Object errorSignal—the significance of this ancestry is
discussed below. To create a signal with a different parent, use newSignal
and address it to the desired parent, as in the expression
divSignal := (Number errorSignal) newSignal.
Most classes in the system have been updated to use the class-based
exception mechanism. Some still contain instances of Signal as class
variables. For cases that use Signal instances, it may be appropriate to
choose an existing signal instead of creating a new one. These “global”
signals are implemented as class variables, and accessed via class

270 VisualWorks
Using a Signal to Handle an Error

methods. For example, browse class Palette which defines two signals,
PaintNotFoundSignal and PixelNotFoundSignal, and provides accessors in
two class methods, paintNotFoundSignal and pixelNotFoundSignal.
Classes for which error handling has been updated to use the class-
based mechanism still provide class-side accessor methods, but return a
class instead of a Signal instance. For example, Object errorSignal returns
the class Error rather than an instance of Signal.

Proceedabiltiy
A Signal has a proceedability attribute, which indicates whether the error
is harmless enough to permit the process to proceed from that point
onward. By default, a new signal inherits the proceedability setting of its
parent signal. To establish a specific proceedability in a new signal, use
newSignalMayProceed:, as in the following expression:
divSignal := (Number errorSignal) newSignalMayProceed: false

Creating an Exception
In the Signal mechanism, an Exception object is created by sending a raise
message to the appropriate signal. This object then travels back along
the message stack looking for its matching signal (or an ancestor),
triggering the intended handle: block.
For example, a paint program recognizing an error in the paint selection,
would signal that error by sending a raise message to PaintNotFoundSignal,
which raises the exception. This exception then traverses the chain of
calling objects until it finds a handler.
Because such Signal instances are not guaranteed to exist is future
versions, it is safer to use the accessor methods to access a signal. This
accessor method is updated to reference the class instead of the signal,
and using only the accessor method makes this transparent to the
application. So, it would be better to send:
Palette paintNotFoundSignal raise
The raise message effectively transfers control from the method in which
the error was perceived to the handle: block in the calling method. A
variant of raise permits control to proceed from the point of error (usually
after the handle: block warns the user or corrects the cause, or both). To
create a proceedable exception, use raiseRequest (the exception requests
that control be returned to it). A proceedable exception can only be
successfully addressed to a proceedable signal; a nonproceedable
exception can be addressed to either type of signal. Thus, the exception
largely determines its own proceedability.

Application Developer’s Guide 271


Chapter 11 - Exception and Error Handling

Setting Parameters
An exception can carry an argument object back to the handler block,
such as a value that can be used to diagnose the breakdown, an array of
such values, or a block of remedial operations. The default is nil. To set
that value, send a parameter: message to the exception, with the object as
argument.
For situations in which the signal’s notifier string needs to be replaced or
augmented, send errorString: to the exception, with the replacement string
as argument. If the first character of the argument string is a space, the
argument is appended to the signal’s notifier string. Otherwise, the
argument string is used instead of the signal’s string.
By default, an Exception begins its search for a handler in the context that
sent the raise message. To substitute a different starting place, send a
searchFrom: message to the Exception, with the starting-point context as
argument.
Because more than one instance of the same Signal can exist, as
implemented by different methods (with different handlers, possibly), an
Exception can get fielded by the wrong handler unless it has a way to
identify its originator. To do so, send originator to the Exception, with the
object that originated the raise message as argument. To equip the
handler with the originator, so it can spot the matching Exception, send a
handle:from:do: message, supplying the originator as the argument to the
from: keyword.

Passing Control From the Handler Block


A handler block can redirect the flow of control in one of four ways, listed
in order of increasing assertiveness:
• Refuse to handle the exception
• Exit from the handler block and from the method in which it is located
(i.e., a conventional return).
• Proceed from the point at which the error occurred.
• Restart the do: block and try it again.
To refuse control, use reject, as in anException reject. The exception will
then continue its search for a receptive signal.
To exit from the handler block, use return. The nil object will be returned.
To pass a value other than nil, use returnWith:.

272 VisualWorks
Using a Signal to Handle an Error

To return control to the point at which the error occurred, use proceed. To
pass an argument to be used as the value of the signal message, use
proceedWith:. To proceed by raising a new exception—in effect, to
substitute a different signal in place of the original error creator—use
proceedDoing: and raise the new exception in the argument block.
To restart the do: block, use restart. To substitute another block of
expressions for the original block, use restartDo:, as in the expression
theException restartDo: aBlock.
If a handler does not choose one of the four options described here, it
has the same effect as theException returnWith: the value of the block.
Raising a signal within its own handler does not restart the handler.
However, raising a signal within a proceedDoing: or restartDo: block does
invoke the signal’s handle block again.
Returning to the calculator example, let’s fill in the handler code:
ArithmeticValue divisionByZeroSignal
handle: [:theException |
Transcript cr; show: 'Enter a nonzero divisor'.
theException restart]
do: [the division operation]

Using Nested Signals


In some situations, it will be necessary to have more than one hawk
watching the same process. For example, you might want to catch both
numeric errors and dictionary errors, without using the full generality of a
mutual parent such as Object errorSignal. To avoid nesting one handle:do:
construct within another, create an instance of SignalCollection. A
SignalCollection is created via new and an element is appended via add:,
as with any OrderedCollection. Use handle:do: just as you would with an
individual signal. When an exception is raised, it will try each signal in the
collection until it comes to one that it recognizes.
A SignalCollection works fine when the same handler block is to be used
no matter what kind of error crops up. But if each type of signal is the
trigger for a different handler block, use a HandlerList. To create it, use
new.
Each element of a HandlerList consists of a signal and an associated
handler block. To add such an element, use on:handle:, as in aHandlerList
on: aSignal handle: aBlock. To begin execution of the do: block, use
handleDo:, as in anHC handleDo: aBlock.

Application Developer’s Guide 273


Chapter 11 - Exception and Error Handling

A HandlerList can be built in advance and reused in various contexts,


which is both more readable than the nesting approach and more efficient
than building even a single handler on the spot. Bear in mind, however,
that handlers in a HandlerList are not peers—they are effectively nested.
A signal that is raised in a nested series will not be fielded by a handler
that is lower in the hierarchy (or later in the collection). For example, the
first set of expressions below is semantically equivalent to the second.
HandlerList new
on: sg1 handle: [:ex | "response 1"];
on: sg2 handle: [:ex | "response 2"];
on: sg3 handle: [:ex | "response 3"];
handleDo: ["Any arbitrary action"].
sg1 handle: [:ex | "response 1"]
do: [sg2 handle: [:ex | "response 2"]
do: [sg3 handle: [:ex | "response 3"]
do: ["Any arbitrary action"]]].

274 VisualWorks
12
Process Control

Besides control blocks, VisualWorks provides a mechanism for controlling


the flow of execution by separating control into several processes. The
process control mechanism facilitates controlling multiple independent
processes.

UI Processes
Due to process management limitation in the VisualWorks UI architecture
prior to version 7.1, it was not safe to include code that affects the UI in a
process other than the UI process. If a forked process had to affect a UI
process, it was necessary to load the ForkedUI parcel.
Beginning in 7.1, the VisualWorks UI architecture fully supports multiple
processes. The new architecture provides for multiple window managers,
each of which may manage events for either a single or multiple windows.
Managing windows events is described in the GUI Developer’s Guide.
Refer to chapter 3, “Controlling the GUI Programmatically.”

Creating a Process
A Smalltalk process is a light-weight process that is non-preemptive of
other processes of the same or lower priority. It represents a sequence of
actions being performed by the computer. Frequently, two or more such
processes need to be running simultaneously. For example, you might
wish to assemble an index in the background at the same time as your
application user is performing an unrelated activity such as entering data.
In that case, the computer’s attention must be divided between the two
activities—in effect, we want to place a fork in the path so the processor
will progress down both paths at the same time.

Application Developer’s Guide 275


Chapter 12 - Process Control

To split a new process to run alongside an existing one, send the


message fork to a block, creating a new instance of Process. If the
indexing operation mentioned above were capable of being launched
from within the data-entry program, the expression for doing so would
look something like indexingBlock fork, where indexingBlock is a block
containing the launching instructions for the index program.
The fork message triggers execution of the block’s contents just as a value
message would. The difference is that the next instruction following the
fork is executed immediately. The instruction that follows a value has to
wait until the block has finished, which is undesirable in the case of a
background process such as an indexing operation.
A block’s response to fork is to create a new instance of Process, then
notify the Processor to add the new process to its work load. This latter
step is known as scheduling a process.
To create a new process without scheduling it, use newProcess instead of
fork. In effect, the newly created process is immediately suspended,
presumably so it can be restarted by another part of your program at the
appropriate moment. In that way, the creation of the process can be
separated from the scheduling.
To pass one or more arguments to a processing block, use
newProcessWith:, supplying the argument objects in an Array, as in aBlock
newProcessWith: #(2 #NewHire). The number of elements in the Array must
be equal to the number of block arguments.

Scheduling a Process
Processor is the lone, pre-fabricated instance of class ProcessorScheduler,
in the same way that Smalltalk is the unique instance of class
SystemDictionary. Both are global variables. Processor is responsible for
deciding which instruction to execute next, choosing among the next
actions in all of the current processes. It has to be made aware of a
process first—the process has to be scheduled.
The fork message, described above, automatically schedules its newly
created process. To schedule a suspended process (including a process
created with a newProcess message), use resume, as in the expression
aProcess resume.
To temporarily prevent execution of a process’s instructions, use suspend.
Thus, resume and suspend are complementary methods. A resumed
process starts up where it left off when it was suspended.

276 VisualWorks
Setting the Priority Level

To unschedule a process permanently, whether it is in resume or suspend


mode, send it the message terminate.
Thus, a process can be in any of four different states: suspended, waiting,
runnable, and running. The first two are very similar, with the distinction
that explicit suspend and resume messages push a suspended process
from or into runnability, while primitive semaphore methods accomplish
the same for a waiting process. A runnable process is ready to go as
soon as the ProcessorScheduler gives it permission. A running process is
the one that the processor is working on.

Setting the Priority Level


The Processor has a great deal in common with a juggler who spins plates
on the tops of those long, wobbly poles and then scurries from one to
another, acutely attentive. Like the juggler, who services whichever plate
is wobbling the most and spinning the least, Processor lets its processes
set their own priority levels. Otherwise, it handles them in the order in
which they were scheduled.
There are 100 possible priority levels. Eight of the levels are commonly
used and can be accessed by name in code references. The table below
describes the purpose of these priority levels.
Priority Levels
Priority Method Purpose
number
100 timingPriority Processes that are dependent
on real time
98 highIOPriority Critical I/O processes, such as
network input handling
90 lowIOPriority Normal input/output activity,
such as keyboard input
70 userInterruptPriority High-priority user interaction;
such a process pre-empts
window management, so it
should be of limited duration
50 userSchedulingPriority Normal user interaction
30 userBackgroundPriority Background user processes
10 systemBackgroundPriority Background system processes
1 systemRockBottomPriority The lowest possible priority

Application Developer’s Guide 277


Chapter 12 - Process Control

A newly created process inherits the priority level of the process that
created it.
To assign a new priority to a process, use an expression of the form
aProcess priority: (Processor userInterruptPriority). Notice that the priority:
method expects an integer argument, but the sender asks the Processor
for the integer by name.
You can also specify the priority level at process creation time, using
forkAt: with the requisite priority level integer.
The Processor gives control to the process having the highest priority.
When the highest priority is held by multiple processes, the active
process can be moved to the back of the line with the expression
Processor yield—otherwise it will run until it is suspended or terminated
before giving up the processor. A process that is yielded will regain
control before a process of lower priority.

Synchronizing Processes
Sometimes one process has to wait for another process to mature before
it can take a particular action. For example, a printer might be tied up for
the next 20 minutes printing someone else’s job. Does that mean your
printing job should just tie up the Processor and refuse to yield until the
printer is available?

Semaphore
The Semaphore class provides a simple mechanism for resolving such
problems. In the printer example, an instance of Semaphore would be
created to keep an eye on the printer:
printerSemaphore := Semaphore new.
The process that funnels output to the printer, which we’ll call
printerProcess, sends the message printerSemaphore signal each time it
becomes available for more input. The waiting process, which has been
suspended so it won’t lock up the processor, is then resumed.
How did the waiting process get suspended in the first place? Instead of
just sending its contents and assuming it will be caught by printerProcess,
the waiting process sends the message printerSemaphore wait. Because
printerSemaphore has not yet received a signal message from
printerProcess, the waiting process was suspended. If the printerProcess
had already sent a signal message that was not consumed by another
process, printerSemaphore would have done nothing, permitting the waiting
process to send its data.

278 VisualWorks
Synchronizing Processes

If a Semaphore receives a wait message from two or more processes, it


resumes only one process for each signal message it receives from the
process it is monitoring. A Semaphore resumes the oldest process of the
highest priority.

job3 job3 job3


job2 job2 job2
job1 job1 job1

wait
resume

semaphore semaphore semaphore

raiseSignal

print print print


queue queue queue

The three steps involved in using a semaphore

A Semaphore is like a guard who permits one person to approach the


Queen at a time. Each time the Queen finishes an audience, she looks
up at the guard and says signal. The guard then lets the next courtier in.
(To add to the indignity, a courtier will not receive a place in line unless he
or she gives the password to the guard: wait.)

Application Developer’s Guide 279


Chapter 12 - Process Control

Sharing Data Between Processes


When an application needs to match the output of one process with the
input for another process, care must be taken to make sure the transfer of
data goes as planned. The SharedQueue class provides a means of
coordinating this transfer.
To create a SharedQueue, use new or new: with an integer argument
specifying the number of desired slots.
To store an object in the SharedQueue, send it a nextPut: message with the
data structure as argument. If another process has been waiting for an
element to be added to the queue, which it indicated by sending next to
the SharedQueue, that process will be resumed.

Using a Delay
The Delay class answers the common need for a means of postponing a
process for a specific amount of time. To create a Delay, use forSeconds:,
as in Delay forSeconds: 30. Or use forMilliseconds: if you require a finer
quantification of time.
To create a Delay that continues until the system’s millisecond counter
reaches a particular value, use untilMilliseconds:. To find out the current
value of the counter, use the expression Delay millisecondClockValue.
Merely creating a Delay has no impact on the current process. The
process must send the wait message to the instance of Delay. Thus, the
following expression in a method would suspend the current process for
30 seconds: (Delay forSeconds: 30) wait.

280 VisualWorks
13
Weak Reference and Finalization

In early versions of Smalltalk, all object pointers (OOPs) were treated as


strong pointers. A strong pointer is a reference that cannot be broken by
any of the virtual machine’s (VM) garbage collection mechanisms. Thus,
if any object is reachable from the system roots via a chain of strong
references, that object is exempt from being reclaimed as garbage by the
VM.
In most cases, the fact that all references were strong was desirable,
because most objects are not prepared to have the objects to which they
refer suddenly disappear with the rest of the garbage. In some
circumstances, however, strong references caused objects to live longer
than their designers intended.
Suppose, for example, you want to profile the performance characteristics
of an application. You might place some of the objects created by that
application into an array so you could tabulate statistics on them.
Unfortunately, the mere fact that you referenced these objects from such
an array guaranteed that the objects would not be reclaimed as garbage
even if the application code ceased to reference them.
This unintended side effect can be avoided by using the class WeakArray.
A related facilities, ephemerons, provides additional options for weakly
holding keys, allowing garbage collection.

Application Developer’s Guide 281


Chapter 13 - Weak Reference and Finalization

Weak Arrays
A WeakArray is similar to an ordinary Array, the prime difference being that
a WeakArray references its elements weakly.
When an element of a WeakArray is no longer referenced by any object
other than another WeakArray, then that element is eligible for reclamation
by the garbage collector. During reclamation, the reference to that
element is removed from the WeakArray and replaced by zero.
Only the indexed variables of the WeakArray class are weak references.
The named instance variable, dependents, is strong. Further, this is the
only class whose references can be weak. Even subclasses of this class
can contain only strong references.
It is possible, however, to add named instance variables to this class, if
you are willing to redefine the class. As stated above, such variables will
be strong. The fact that this is the only class that can have weak
references may seem to be a substantial restriction, but you can easily
construct more complicated objects with a mix of strong and weak
references by using a WeakArray as a subcomponent.

Finalization
WeakArray also provides the system with a way of performing a final set of
actions when an object expires. For example, an application might need
to release some external resource when the objects using that resource
have all been garbage collected. Finalization provides the system the
ability to notify the application that the objects using the external resource
have all expired, thus letting the application know that it is safe to loosen
its hold on the external resource.
The mechanism involves sending a changed message to any WeakArray
that has had one of its elements zeroed out as described above. This
notification is then propagated to each of the dependents of that
WeakArray, allowing them to take the actions necessitated by the death of
the WeakArray’s element.
Of course, any such dependent will need to store whatever information it
needs prior to receiving such notification, because the object that was
once an element of the WeakArray will already have been destroyed. The
dependent must also ensure that it can subsequently locate that
information based solely on the dead element’s index in the WeakArray
(the dependent can find the index of a WeakArray’s dead element by
invoking the indexOf:replaceWith:startingAt:stoppingAt: primitive).

282 VisualWorks
Finalization

To be more exact, the dependents of a given WeakArray are notified that


one or more of its elements have expired as follows:
• When an element of a WeakArray expires, the VM zeros out the slot in
the WeakArray that was previously occupied by the now dead object.
• In addition, the VM places this WeakArray on a finalization queue that
is managed by the VM, and then signals the FinalizationSemaphore.
• Signalling the FinalizationSemaphore causes the FinalizationProcess
(which is generally waiting on the FinalizationSemaphore) to resume,
and the FinalizationProcess then sends a changed message to every
WeakArray on the finalization queue (it uses a primitive to fetch the
WeakArrays that are on the finalization queue).
• Eventually, every dependent of each WeakArray that suffered a loss
will receive an update message.
For example, consider an application that has a set of objects that act as
proxies for external resources. The application wishes to free these
external resources when the proxies are no longer in use. Further,
assume that the proxies know which external resource they are
associated with by virtue of a proxy instance variable that contains an
external handle.
The application could arrange for the external resources to be freed
automatically by simply placing the proxy objects in a WeakArray and
copying their associated external handles into the corresponding
locations of a strong Array. Then, when one or more of the proxy objects
was no longer in use, the memory manager would reclaim the proxy
object, zero out its location in the WeakArray, place the WeakArray on the
finalization queue, and signal the FinalizationSemaphore, eventually
resulting in an update message being sent to the application, assuming
that the application had registered one of its objects as a dependent of
the WeakArray. The application could then identify which proxy objects
actually expired and free their associated external resources as follows:
weakArrayOfProxies
forAllDeadIndicesDo:
[:deadIndex | externalConnection
freeResource: (externalHandleArray at: deadIndex)]
There is alternative protocol to make nil the value at each dead index of
the WeakArray as it is uncovered (nilAllCorpsesAndDo:) as well as for
replacing the value with an arbitrary object
(forAllDeadIndicesDo:replacingCorpsesWith:). Because these methods use

Application Developer’s Guide 283


Chapter 13 - Weak Reference and Finalization

the indexOf:replaceWith:startingAt:stoppingAt: primitive, which finds a given


element and replaces it atomically, they can be used to prevent another
process from mistakenly duplicating the finalization actions.
This scheme requires some extra work on the part of the application,
because it forces the application to save a copy of the external handles in
a parallel array. However, it completely avoids the problems that can
occur if the proxy object that we are finalizing is resurrected, either by the
code performing the finalization or by some other code that happens to
get a handle on the proxy object before it is actually destroyed by the VM
and after the finalization action has been completed.

Variable Values Description


Instance variable for WeakArray:
dependents nil, Object, Objects that must receive
dependentsCollection notification when one of the
WeakArray’s elements dies.
Class variables for WeakArray:
FinalizationProcess Process The process responsible for
sending a changed message to any
WeakArray that has died
FinalizationSemaphore Semaphore The semaphore signalled by the OE
whenever a WeakArray has died
QueueOverflowSigna Signal The signal that indicates that the
OE’s finalization queue has
overflowed. It may be appropriate in
this event to send a changed
message to every WeakArray

284 VisualWorks
WeakDictionary

WeakDictionary
A WeakDictionary is a dictionary whose valueArray is a WeakArray. Such a
dictionary is fully protocol-compatible with IdentityDictionary. The lookup is
done using == rather than =.
For finalization, WeakDictionary also stores an array of executors for its
elements. The default executor for each element is a shallow copy of the
element. An element’s executor is responsible for finalization after the
element has been reclaimed. An element with special finalization
requirements should implement the finalize message, which is sent to the
executor to actually perform the finalization. The default implementation
of finalize in the Object class performs no finalization.

Variable Value Description


Instance variable for WeakDictionary:
executors Array The array in which the
shallow copies of the
values are stored

HandleRegistry
A HandleRegistry is a WeakDictionary whose values all respond to a key
message. The elements of a HandleRegistry are registered using their
response to the key message as the dictionary key and using the element
as the value. Access functions are all implemented as critical regions so
that multiple processes can operate on an instance at the same time.

Variable Value Description


Instance variable for HandleRegistry:
accessLock Semaphore Mutex semaphore
protecting accesses

Finalization Example
To illustrate the finalization mechanism outlined above, we provide an
example in the form of code for an Executor class. An Executor is an object
that executes the last will and testament of a familyMember. To try it, enter
the code into the system, then evaluate the expression in the class
comment.

Application Developer’s Guide 285


Chapter 13 - Weak Reference and Finalization

Class definition:
[Link] defineClass: #Executor
superclass: #{[Link]}
indexedType: #none
private: false
instanceVariableNames: 'familyMembers familyWills'
classInstanceVariableNames: ''
imports: ''
category: 'Finalization-Example'
Class comment:
The Executor class is a simple example of how finalization can be
achieved by using WeakArrays. After entering the code into the
system, evaluate the expression: “Executor example inspect”.
Instance Variables:
familyMembers
<WeakArray> containing the name string of each family member.
familyWills
<Array> of blocks that will print the last will and testament of the
corresponding person in the familyMembers array on the Transcript.
Instance methods for finalization:
readLastWillAndTestamentOfTheDeparted
"Read the will of each family member who has died."

familyMembers nilAllCorpsesAndDo: [:deadIndex |


(familyWills at: deadIndex) value]
Instance methods for updating:
update: anAspectSymbol with: aParameter from: aSender
"Finalize all finalizable entries of aSender."

(aSender == familyMembers and:


[anAspectSymbol = #ElementExpired])
ifTrue: [self readLastWillAndTestamentOfTheDeparted]
ifFalse: [^self]
Instance methods for accessing:
familyMembers: aWeakArray

familyMembers removeDependent: self.


familyMembers := aWeakArray.
familyMembers addDependent: self
familyWills: anArray
familyWills := anArray

286 VisualWorks
Ephemerons

Class methods for example:


example
"Executor example inspect"

| family wills familyLawyer |


family := WeakArray
with: 'cain' copy
with: 'abel' copy
with: 'eve' copy
with: 'adam' copy.
wills := Array
with: [Transcript show:
'Cain has died. Bequeaths his assets to the church.'; cr]
with: [Transcript show:
'Abel has died. Killed by Cain for his assets.'; cr]
with: [Transcript show:
'Eve has died. Bequeaths her assets to Abel.'; cr]
with: [Transcript show:
'Adam has died. Bequeaths his assets to Eve.'; cr].
familyLawyer := Executor new.
familyLawyer familyWills: wills.
familyLawyer familyMembers: family.
^familyLawyer

Ephemerons
Ephemerons are special forms of Association that are used to attach
properties to objects without preventing those objects from being garbage
collected. They are like an Association whose key is weak, but they are
more sophisticated in that references back to the key from the transitive
closure of an ephemeron's other fields do not contribute to the key being
counted as “alive” for garbage collection purposes.
Both the DependentsFields dictionary, which associates dependents with
instances that don't inherit from Model, and the EventHandlers dictionary,
which associates event handlers with all objects, are implemented to use
EphemeronDictionary to use ephemerons. The result is that adding
dependents or event handlers to an object does prevent that object from
being garbage collected, and means that application code does not need
to handle that clean-up itself.
Ephemerons also support instance-based finalization, in that simply
attaching an ephemeron to an object is a way of arranging that the
ephemeron will be notified when there exist no other references to an
object than from ephemerons.

Application Developer’s Guide 287


14
Application Delivery

When you have finished developing your application, you need to extract
it from the VisualWorks development environment and prepare it to run
as a stand-alone application. This process is called deploying an
application.
The basic activities in deploying an application are:
• Preparing the application to run stand-alone, by removing
dependencies on development environment
• Organizing code into deployment parcels
• Building the deployment image
To simplify the process of preparing an image and installing it on a
customer’s system, VisualWorks includes:
• Runtime Packager, a utility for creating a deployment image from a
development image.
• An application installation framework is provided as a goodie. This
framework was used in the VisualWorks installation program. To
examine the framework, load the VisualWorks Installer parcel.

288 VisualWorks
Choosing a Delivery Strategy

Choosing a Delivery Strategy


There are three ways to organize your application for deployment:
• As a single deployment image containing all application code
• As one or more separately-loadable parcels containing application
code, delivered with a minimal deployment image
• As a combination of an image containing part of the application code,
and parcels containing the rest
Each approach has its advantages.

Single Image File


Single image files work well for small applications. They are simple to
deploy, requiring only the object engine and image file.
However, a single image file is often too large for easy distribution and too
large-grained to provide adequately for the individual support of
subsystems or sub-applications.

Parcels
Parcels, files that contain application objects, can be rapidly loaded into
an image without the use of a compiler. This makes parcels
advantageous in large, complex applications. Parcels allow you to:
• Deliver a very small base image
• Incrementally update your application without supplying a new image
• Customize your application at run time
• Tailor the memory footprint of your running application

Combined Deployment
Even though loading parcels is fast, loading the image is faster. Loading
your entire application from parcels into a minimal image might not be
optimal for a variety of reasons.
A combined use of the image and parcels might have the core application
code saved in the image, at least up to the first window. From that
window, additional code can be loaded from parcels as needed. Seldom
used code might never be loaded by some users.

Application Developer’s Guide 289


Chapter 14 - Application Delivery

Packaging for Distribution


The files you need to distribute for your application are:
• The VisualWorks virtual machine exectutable and any required
support files.
For deployed applications, the visual or [Link] executable is
preferred. The executable may be renamed for your application.
On Windows 95/98/ME platforms, [Link] and [Link]
are needed to call 16-bit code, and should be included. For Windows
NT/2k/XP platforms, they are not used, and should be omitted.
• Your deployment image.
• Any VisualWorks product parcels (e.g., database support parcels)
that you set to load during runtime.
• Any application parcels that you set to load during runtime.
You are responsible to set up necessary directory structures and
configure your image and Runtime Packager to use them.

Deploying as a Single File


On Windows and Mac platforms you have the option of combining an
image and the virtual machine in a stand-alone executable. For Mac
platforms there is no additional software required; instructions for
packaging are provided in packaging:mac (for Mac OS 9.x) and
packaging/macx (for Mac OS X). For Windows platforms some third-
party software is required. This software and instructions are provided in
packaging\win.

VisualWorks Installer
As an aid to installation, you can use the VisualWorks installation
framework, which is provided as a goodie. Load and browse the
VWInstallerFramework and VWInstallerConfiguration parcels to use this
option.

Running a Deployed Image


You start a deployment image the same way you start a development
image, by specifying the object engine and name of the deployment
image. For the full command line syntax and options see “Running
VisualWorks” in Chapter 1, “The VisualWorks Environment.”

290 VisualWorks
Running a Deployed Image

When a virtual machine executes, the default is to read an image file with
the same name. So, if you rename the executable to myApp (or
[Link]) and the image file to [Link], you can simply execute:
> myApp
with any required options.

Loading Parcels At Start Up


Deployment images, images created using Runtime Packager, can load
parcels during startup. Parcels to load at startup are identified by
command-line options, listed either individually or in a configuration file.
When a deployed image starts up, it looks in the startup directory for a
parcel configuration file with the filename [Link], where
imagename is the same as the image file’s name. If such a file exists, the
image loads the parcel files named in the file. Parcel file names should be
listed one per line, and are resolved with respect to the working directory,
or the parcel path if one is specified in the deployed image.
You can use command-line arguments to specify additional parcels and
parcel configuration files to load:
-pcl filename
Loads the specified parcel file
-cnf filename
Loads all of the parcels listed in the specified configuration file

Opening a Runtime Application


There are several options available for opening an application upon
startup.
• Create a subclass of UserApplication (a subclass of Subsystem), and
define its main method to open the application. (Refer to
“Responding to System Events” in Chapter 8, “Application
Framework” for more information.)
• If you use Runtime Packager to create a runtime application, you
have two mechanisms for opening a runtime application:
• In the Runtime Packager, on the Basics page of the Set common
options step, you can specify a Startup Class and Startup Method. The
method is sent to the class upon image startup, after parcels
specified to load at runtime are loaded.
• If your application loads one or more parcels at launch, the
application can be opened by a parcel’s Post-load Action. For

Application Developer’s Guide 291


Chapter 14 - Application Delivery

example, to open the WalkThru example RandomNumberPicker


application, edit the parcel’s Post-load Action property to:
[:package | [Link] open]
Then republish the parcel.
• You can save the deployment image with an open application
window. This is not the preferred method, so avoid it if possible. If you
use Runtime Packager to create the deployment image, make sure
that your application code is selected as “kept,” because Runtime
Packager does not automatically keep code for open windows (see
“Specify Items to Keep and Delete” below).

Exiting a Deployed Image


An application may provide an explicit shutdown command, allowing it to
exist gracefully. For example, a graceful exit frequently involves closing
external connections to files or databases.
Applications also frequently exit when its last window is closed by the
user. This facility provided by many window managers can shortcut the
procedures invoked by an explicit shutdown command. To accommodate
this situation, Runtime Packager can invoke a shutdown block. See
“Shutdown When the Last Window Closes” below for more information.

Installing as a Service on Windows


Especially in the case of server applications being deployed on Windows
NT or 2000/XP, it is occasionally desirable to install the application as a
service. There are no specific requirements for a VisualWorks application
to be installed as a service. The procedure for installing it is entirely a
Windows procedure.
For general information on installing and running an application as a
user-defined service, refer to the Microsoft Knowledge Base article
137890, “HOWTO: Create a User-Defined Service.” You will also need to
obtain and install the appropriate Resource Kit for your operating system.
To run a VisualWorks application as a service, you need to set the
following :
1 Using the Services applet in the Windows Control Panel, edit the
properties of your service and select "Allow service to interact with
desktop."
2 Using the Registry Editor, create and set the Application and
AppDirectory values in the Parameter key for your service as follows:

292 VisualWorks
Preparing an Image for Deployment

Value Name: Application


Data Type : REG_SZ
String : <vw-path>\[Link] -noherald <my-image>
Value Name: AppDirectory
Data Type : REG_SZ
String : <vw-path>
where <vw-path> is the full drive and directory location of your
VisualWorks application files (for example,
C:\VisualWorksServer), and <my-image> is the name of your
image file (for example, [Link]). If your application requires
other command line parameters, include them in the Application
string, in the same way you would when you create a Windows
shortcut to start the application.
3 It is also a good idea to make sure your PATH= environment variable
includes the location of your VisualWorks application files.

Preparing an Image for Deployment


Before creating a deployment image for your application, there are a few
aspects of the resulting image that you may need to deal with. The
following topics can be used as a check list for preparing an image for
deployment.

Loading Application Code


Application code can be either held in the image or in parcels that are
loaded at runtime.
Code Developed in the Image
If you develop your code directly in your image and save it by saving the
image, then your application code is ready for processing by Runtime
Packager for deployment as a single image.
If you want to load some of your code as parcels, you need to create the
parcels and then proceed as for parcelled code.
Code Saved in File-outs
If you store your application code is file-out format files, simply file-in the
code to your image. Then proceed as for code developed directly in the
image.

Application Developer’s Guide 293


Chapter 14 - Application Delivery

Code Saved in Parcels


In general, you should load all parcelled code into the image before
running Runtime Packager. This allows Runtime Packager to include it in
its scan for dependencies while determining which code to keep or delete
from the image. It also provides the option of having Runtime Packager
save your parcels as runtime parcels that are optimized and saved
without source code.
For each parcel that is loaded into the image, you need to specify
whether its code is saved with the image (and so does not need the
accompanying parcel file), or will be loaded during runtime. For parcels
that are loaded at runtime, there are other options as well. These
decisions are made as part of the Set common options step, on the Parcels
page (see “Set Common Options” for more information).
You also need to plan the location of parcels. The development
environment has a complex parcel path. Your application will probably
have a simpler path, or not path at all and hold all parcels in the same
directory as the image. By default, Runtime Packager clears the parcel
path (Set common options, Details page). When you decide on a parcel
location strategy, you need to make sure you specify the necessary
information in Runtime Packager (Set common options, Parcels page).
Also, if you specify parcel paths relative to the VisualWorks home
directory, $VISUALWORKS, you need a strategy for setting that directory.
Code in a Store Database
If you develop using Store, you can either load your packages and
bundles into the image, or publish some or all of your packages and
bundles as parcels. You can, of course, combine of these options, loading
some code into the image and publishing some as loadable parcels.
Unparcelled code that is loaded into the image is included in the
deployment image. You are responsible to make sure that required code
is marked to be “kept” by the Runtime Packager.
Code that you publish as parcels should be processed just as any other
parcelled code.

Removing Source Files


By default, source files are included with a deployed image. This is not
always desirable, both for disk space and for security reasons. To detach
source files, send:
SourceFileManager default removeAllSources.

294 VisualWorks
Preparing an Image for Deployment

Alternatively, to selectively remove source files, send removeFileAt:. You


will need to know the index for the file to remove. Inspect:
files := OrderedCollection new.
SourceFileManager default fileIndicesDo:
[:index| files add: (SourceFileManager default fileAt: index) ].
^files
Then remove a file by its index, for example:
SourceFileManager removeFileAt: 2

The Transcript
The Transcript object is preserved in a deployment image, but is not
displayed as in the development image. Messages sent to Transcript
continue to process without errors but do not display themselves unless
you define a window to show the state of the Transcript.

Handling Errors
Your application is expected to catch all anticipated errors and to handle
them. Refer to Chapter 11, “Exception and Error Handling” for information
about error handling in VisualWorks.
For unhandled errors, Runtime Packager replaces calls to open a
NotifierView with calls to a RuntimeEmergencyNotifier. This simplified notifier
excludes tool support, such as the debugger, and simply notifies the user
that an unhandled exception has occurred, with a brief description of hte
error. It also writes a summary of the error and its stack to an error log file
(by default called [Link]).
Both the error handling class and the error log name are specified on the
Exceptions page of the Options step. You can create your own handling
procedures for unhandled exceptions and specify it on this page.

Registering an Interest in System Events


It is often appropriate to invoke particular behavior at system startup or
exit. Two mechanisms, one pragma based and the other message based,
are provided to register messages as dependents of system events,
which can be used for this purpose. The pragma-based mechanism is
generally preferred because it automatically registers the dependency on
parcel load, and unregisters it on unload. The message-based
mechanism is useful for exceptional cases, such as when the dependent
is an instance rather than a class.

Application Developer’s Guide 295


Chapter 14 - Application Delivery

For both mechanisms, the system event is an event sent from


ObjectMemory, and is one of the following symbols: #aboutToQuit,
#aboutToSnapshot, #earlySystemInstallation, #finishedSnapshot,
#returnFromSnapshot, or #scavengeOccurred. Both mechanisms support
registering only unary selectors.
The two events most commonly of interest are #returnFromSnapshot and
#aboutToQuit. #returnFromSnapshot is sent on system startup, after all
VisualWorks subsystems have been initialized. Use it to perform actions
such as starting your own application. #aboutToQuit is sent before shutting
down the system. Use it to do things like closing network connections
open by your application.
Pragma-based Event Dependency
The pragma-based mechanism is used by adding an annotated method
to class SystemEventInterest. The class side dependencies-pragma method
category is provided as a convenient placeholder for these methods. An
example of a pragma-based dependency is:
startMyApplication
<triggerAtSystemEvent: #returnFromSnapshot>
MyApplication open
When the system event #returnFromSnapshot is received,
SystemEventInterest sends #startMyApplication to itself, which then sends
#open to MyApplication. See the SystemEventInterest class method example
for additional examples.
This type of dependency is registered whenever a method with this
pragma is compiled or loaded into SystemEventInterest class, and
unregistered when the method is either recompiled without the pragma,
or removed or unloaded from the system.
Message-based Event Dependency
In addition to specifying the event to trigger the notification, clients using
the message-based mechanism also specify the receiver of the
notification and the selector that will be sent. The following messages
register and unregister command line processing actions when sent to
class SystemEventInterest. The first one arranges to send the message
#start to anObject when ObjectMemory triggers the event
#returnFromSnapshot.

296 VisualWorks
Preparing an Image for Deployment

SystemEventInterest
atSystemEvent: #returnFromSnapshot
send: #start
to: anObject
To unregister the dependency that will send #start to anObject upon event
#returnFromSnapshot, use this one:
SystemEventInterest
removeDependencyOnSystemEvent: #returnFromSnapshot
selector: #start
receiver: anObject
To unregister all message-based system event dependencies for anObject,
for any selector or event, send:
SystemEventInterest removeAllDependenciesFor: anObject

Shutdown When the Last Window Closes


In a development image, you must explicitly choose to exit VisualWorks to
shut down the system. In a deployed application, however, it is expected
that the application will shut down when its last window is closed.
There are frequently special functions that must be performed before the
image is shutdown. When the shutdown is initiated because there are no
more open windows, the application has no direct way of learning that the
shutdown is about to occur. To allow for special application processing at
this stage, for example, to close open database connections, the
application can register a block to be evaluated. The block is registered
by sending a message to class RuntimeManager as in:
RuntimeManager quitBlock: applicationShutdownBlock
where applicationShutdownBlock should be a block accepting zero or one
argument. If one argument is accepted, the block will be provided with
one of the following depending on the reason the image is being
shutdown:
normal
Shutdown is caused by the last window being closed or no
application windows being open after startup processing was
completed.
exception
Shutdown is caused by an unhandled exception or some other error.

Application Developer’s Guide 297


Chapter 14 - Application Delivery

Handling Command Line Options


The Smalltalk expression:
CEnvironment commandLine
returns an Array of Strings which are the command line tokens, or switches
and switch arguments, in the order they were specified. Note that a token
may include white space characters by enclosing the token in either
single or double quotation marks on the command line.
Usually, retrieving the command line is of interest only as a result of some
ObjectMemory event, such as #returnFromSnapshot. Instead of sending
commandLine directly, or as a command registered as described above in
“Registering an Interest in System Events”, forms of the event registration
mechanisms specific to command line interests are provided.
Both pragma-based and message-based versions are provided. The
pragma-based mechanism is preferred because it automatically registers
a dependency on parcel load, and unregisters it on unload. The
message-based mechanism is provided for exceptional cases, such as
when the dependent is an instance rather than a class.
For both mechanisms, the command line switch is a string, such as '-foo'.
The ObjectMemory event can be any of the system events, but the event
typically of interest for command line dependents is #returnFromSnapshot,
which is sent on system startup after all VisualWorks subsystems have
been initialized, and is the usual time to perform actions such as starting
your own application.
All command line switches for a registered event are processed from left
to right through the command line. For example, consider the following
command line:
visual [Link] -pcl ../parcels/[Link]
-hookup -port 4736
Suppose that -hookup is registered for #earlySystemInstallation, and -pcl
and -port are registered for #returnFromSnapshot. At system startup, the
-hookup action would be triggered first, because the
#earlySystemInstallation event preceeds #returnFromSnapshot. Then the
-pcl action will be triggered, because it preceeds (is to the left of) -port
on the command line. Finally, the -port action will be triggered.
The exception to this rule occurs when new command line interests are
registered during command-line processing for a particular event. For
example, say the option -port preceeded -pcl in our example

298 VisualWorks
Preparing an Image for Deployment

command line, but the dependency on -port isn't registered until


[Link] is loaded. In such cases, another pass of the command line is
made to acommodate the newly registered interest.
In the above example, the option -hookup takes no arguments, the
option -port takes one argument, and -pcl takes one or more
arguments. For zero-argument options, clients may register a unary
selector for either the message-based or pragma-based mechanism. An
example of a client method to process the -hookup option might be:
hookup
^self installInSystem
For one- or many-argument options, clients may register a single-
argument selector. When a single-argument selector is registered for
either mechanism, the argument passed will be a ReadStream on the
collection of command line tokens, positioned at the registered switch.
That is, the argument is the value of the expression:
CEnvironment commandLine readStream through: optionString; yourself
An example of a client method to process the -port option might be:
port: tokenReadStream
port := Number readFrom: tokenReadStream.
Processing an arbitrary number of arguments, such as for the -pcl
option, would be done in a loop, such as:
loadParcelsFromCommandLine: tokenReadStream
[tokenReadStream atEnd not
and: [(token := tokenReadStream next) first ~~ $- ]] whileTrue:
[self loadParcelFrom: token].
Registering multiple-argument selectors is not allowed by either
mechanism. The registered selector must expect either no arguments or
a single argument, as described above.
Pragma-based Option Processing
The pragma-based mechanism is used by adding a class method to
CommandLineInterest, in the dependencies-pragma method category. An
example of a pragma-based dependency is:
loadParcelsFromCommandLine: tokenReadStream
<triggerAtSystemEvent: #returnFromSnapshot option: '-pcl'>
Parcel loadParcelsFromCommandLine: tokenReadStream
When both conditions in the pragma are true, CommandLineInterest sends
loadParcelsFromCommandLine: to itself, which then forwards the message to
class Parcel. See the example class method in CommandLineInterest for
additional examples.

Application Developer’s Guide 299


Chapter 14 - Application Delivery

This type of dependency is registered whenever a method with this


pragma is compiled or loaded into CommandLineInterest class, and
unregistered when the method is either recompiled without the pragma,
or removed or unloaded from the system.
Message-based Option Processing
In addition to specifying the switch and the event, the message-based
mechanism also specifies a message and a receiver. The following class
methods in CommandLineInterest, register and unregister command line
processing actions:
atSystemEvent: aSymbol send: aSelector to: anObject
commandLineOption: aString
When ObjectMemory triggers event aSymbol and command line switch
aString occured in the command line, then send message aSelector to
anObject.
removeDependencyOnSystemEvent: aSymbol selector: aSelector
receiver: anObject commandLineOption: aString
Unregister the action to send aSelector to anObject upon event aSymbol
in the presence of command line switch aString.
removeAllDependenciesFor: anObject
Unregister all actions to send any message to anObject, in response
to any event or command line switch.
An example of registering interest in -hookup using the message-based
mechanism would be:
CommandLineInterest
atSystemEvent: #earlySystemInstallation
send: #hookup
to: self
commandLineOption: '-hookup'
An example of registering interest in -port using the message-based
mechanism would be:
CommandLineInterest
atSystemEvent: #returnFromSnapshot
send: #port:
to: anHTTPServer
commandLineOption: '-port'
To unregister interest in -hookup, send this message:

300 VisualWorks
Preparing an Image for Deployment

CommandLineInterest
removeDependencyOnSystemEvent: #earlySystemInstallation
selector: #hookup
receiver: self
commandLineOption: '-hookup'
To unregister interest in -port, send this message:
CommandLineInterest
removeDependencyOnSystemEvent: #returnFromSnapshot
selector: #port:
receiver: anHTTPServer
commandLineOption: '-port'
To clear all message-based dependencies for anObject, send:
CommandLineInterest removeAllDependenciesFor: anObject

Unload Tools Parcels


Even though Runtime Packager will remove development tools classes
during its processing, it is advisable to remove development tools that are
loaded from parcels, such as the UI Painter, before starting Runtime
Packager. Unloading these parcels allows the system to clean up the
image, simplifying Runtime Packager’s procedure.

Removing Undeclared Variables


The system maintains a name space for undeclared variables, which you
can access by the name Undeclared. Runtime Packager performs
operations to clear and to browse such references as part of its
procedure, but you may also wish to deal with these before starting
Runtime Packager.
An entry is appended to Undeclared when:
• A reference to a nonexistent variable is compiled during file-in (or
interactively, if you override the compiler’s warning).
• A variable is removed while references still exist.
• A class is removed (regardless of whether outside references to it
exist). This assures that any outside references that may exist will be
properly reconnected if the class is recreated.
The Undeclared name space should be empty in a deployed image.
To inspect Undeclared, enter “Undeclared” in a workspace and choose
Inspect from the <Operate> menu.

Application Developer’s Guide 301


Chapter 14 - Application Delivery

The inspector provides commands for examining variables and finding


methods that refer to a selected variable. When you are satisfied that no
references to a variable exist, use the Remove command to delete the
entry. Note that hidden references are not reported, and will have to be
found using other means.

Garbage Collecting Lingering Instances


It is possible, after a lot of experimenting and development work, to have
instances retained in the image that should have been garbage collected.
These should be released and garbage collected before deploying, to
keep the image size down. For a method for finding and releasing these
instances, see “Lingering Instances” on page 107.

Replacing Startup Splashscreen and Sound


You can change the splash screen displayed and the sound played at
startup. We ask that you not replace the splash screen for your
development image.
On Microsoft Windows platforms, simply replace either or both of the files
[Link] and [Link] in the \bin\win subdirectory.
On other systems, you need create a bitmap image in VisualWorks, and
recompile the virtual machine. The necessary C files and scripts are
provided in the release (starting in 5i.2). For instructions, see the
comment at the beginning of bin/<platform>/userprim/splash-
bits-4.h.

302 VisualWorks
Creating the Deployment Image

Creating the Deployment Image


A deployment image is a Smalltalk image that has been stripped of the
development environment, to be run as an end-user application. Once
you have done all the necessary preparation of your application, you are
ready to run Runtime Packager to create the deployment image.
development image deployed application

parcels

unparcelled code

development tools

Smalltalk base

Running Runtime Packager


To create a deployment image, you use the Runtime Packager utility.
Runtime Packager removes development tools and other unwanted
classes from an image, leaving an image file that occupies significantly
less disk space because it contains only objects required by your
application.
To use Runtime Packager:
1 Set up your application as you want it to be delivered:
2 Load the Runtime Packager parcel.
3 In the Launcher, select Tools ­ Runtime Packager, or in a Workspace
execute:
RuntimePackager open
Runtime Packager starts and displays a window that allows you to
choose what you want to remove from your development image
before saving it as a deployment image.

Application Developer’s Guide 303


Chapter 14 - Application Delivery

Runtime Packager’s UI leads you through the process, providing a


general description for each step as well as more detailed help. Some of
that information will be repeated here. For other, please read the tool’s
online help.
The basic procedure consists of the following steps. Each of these step
requires more explanation, which is provided in the following section.
1 Clean Up Image. Check the image for extraneous global objects.
2 Set Common Options. Specify parameters used in later steps.
3 Specify Items to Keep and Delete. Customize the items to be kept
for runtime.
4 Scan for Unreferenced Items. Scan the image for unreferenced
classes, methods, and globals.
5 Review Kept Items. Review the results of the previous scan.
6 Save Loadable Parcels. Save any parcels needed for the runtime
image.
7 Test the Application. Interactively detect missed references to
application classes and methods.
8 Set Runtime Memory Parameters. Set sizes for different spaces on
startup and set memory policy values.

304 VisualWorks
Creating the Deployment Image

9 Strip and Save Image. Create a stripped image for stand-alone


application execution.

A Short-cut Procedure
The basic procedure can be rather slow, and you don’t always need to
perform every step. The menu command File ­ Package Runtime Image
creates a runtime image in one operation by automatically executing the
Scan for unreferenced items, Save loadable parcels, and the final Strip and save
steps.
You still need to set options appropriately, especially specifying how to
handle parcels. But, once you understand the whole process, know what
you do and do not need to do for your image, or have a parameters file
that specifies various features, this short-cut procedure can be a great
convenience.

Examples
The following short examples use the RuntimeExample application that is
loaded as part of the Runtime Packager parcel.
Building a Stand-alone Image
For simple applications you build the deployment image with all of the
application code directly in the image.
This is the simplest procedure. The only options that need to be set are
the Startup Class and Startup Method. Runtime Packager begins with the
resulting startup message and analyzes code the image to which code
must remain and which may be deleted.
1 Load and start Runtime Packager in the usual way, into a clean
image.
2 Do the Clean up image step. Undeclared and DependentFields should both
be clean.
3 Do the Set common options step. On the Basics page set the following:
Startup Class: [Link]

Startup Method: open

Runtime Image Page Name: runtime1


Then close the Common Options window.
4 Do the Scan for unreferenced items step, to search for classes and
methods to keep and delete.

Application Developer’s Guide 305


Chapter 14 - Application Delivery

5 Do the Review kept classes and methods step.


Explore the results as you desire, but be sure to look at the following.
Select RuntimePackager in the Name Space pane, and in the Kept
Classes/Globals pane, select RuntimeExample. Notice that all of the
RuntimeExample methods are in the Kept Methods pane, except for
postLoadActionFor:. This method, which is included for use when the
example is loaded as a parcel, is not used because we have
specified a startup class and method. Runtime Packager has
discovered that fact, and excluded the method from the application.
Close the window.
6 Do to the Strip and Save Image step.
The image is saved as [Link]. Launch the image using a
command such the following.
[Link] [Link]
The image should launch and open the example application.
Building an Image Using Parcels
Building an application as a baseline image with loadable parcels is most
easily done by first loading all parcels used in the application into the
development image. The runtime image and runtime parcels are then
created from the development image.
Parcels that will load at runtime are identified as part of the Set Common
Options step, on the Parcels page. After the scan for unreferenced items, if
any, is completed, runtime versions of parcels can be created.
While the same parcels can be used for development as for runtime,
Runtime Packager permits the parcels to be analyzed for unused classes
and methods.
Classes that are to be loaded as part of a parcel should not be specified
as deleted unless they are to be removed from the runtime version of the
parcel as well.
To illustrate the process for building an image in which applications are
loaded through parcels, the following procedure describes how an image
executing the example application can be created. The method
postLoadActionFor: has been added to the RuntimeExample class to illustrate
opening the application with a post-load action.
We begin by building a parcel out of the example, and then building the
deployed application.

306 VisualWorks
Creating the Deployment Image

1 Load and start Runtime Packager in the usual way.


2 Do the Specify Common Options step, and specify:
Runtime Image Path Name on the Basics page: runtime2
Process command line on the Details page: yes (checked).
3 In the Parcels page of the Common Options window, click the New Parcel
button, and enter RuntimeExample to create a new parcel.
4 Open a Browser (select Browse ­ System in the Launcher window),
and set its navigator to display parcels (select Browser ­ Parcel).
In the browser, select both the RuntimeExample parcel and the
RuntimeExample class. Then select Class ­ Move ­ All to Parcel...,
to add the class to the parcel.
5 In the parcel’s Properties display (select the Properties tab), set the
Post-Load Action to be the following:
[:pkg | #{[Link]}
value postLoadActionFor: pkg ]
6 In the Runtime Packager Common Options window, on the Parcels page,
select the RuntimeExample parcel and set the following options:
Parcel is loaded into image at runtime: yes (checked)

Strip unreferenced items and save: yes (checked)

Path name: [Link]


Close the Common Options window.
7 Do the Scan for Unreferenced Items step.
8 Do the Save Loadable Parcels step. The file [Link]
should be written to the current directory.
9 Do to the Strip and Save Image step.
The runtime image is saved as [Link]. Launch the image using a
command such the following.
[Link] [Link] -pcl [Link]
The image should launch and open the example application.
Note that a number of other parameters can be specified on the
command line. See the description of the Process command line option in
the Options step for a complete list of the parameters supported.

Application Developer’s Guide 307


Chapter 14 - Application Delivery

Runtime Packager Process Details


The following provide details about the operations performed by Runtime
Packager in the series of steps.

Saving Runtime Packager Parameters


At any time during the procedure before doing the Strip and save image
step, you can save the parameters you have set, simplifying subsequent
runs of Runtime Packager.
To save the current parameters, select File ­ Save parameters... .
To load a parameters file, select File ­ Load parameters... .

Clean Up Image
Objects can accumulate in a development image that are not needed for
runtime execution and would occupy storage needlessly. This step scans
for global objects that commonly arise in the development process.
The scan for referenced and unreferenced items detects unreferenced
globals appearing in the system name spaces.
Unreferenced globals that are either undeclared variables or non-Model
objects that have dependents cannot be detected in the scan of
referenced items. When this step is performed, inspectors are opened on
the contents of Undeclared and DependentsFields. If no suspicious contents
are found, you will be notified and no inspectors are opened.
If entries exist in the Undeclared dictionary, you will be prompted to remove
any items that are apparently unreferenced and which are also currently
bound to nil before opening the inspector. These entries can be left
behind when classes are removed from the system, for example.
Removing these entries should normally be harmless and will greatly
simplify analyzing the Undeclared items. However, there is no provision for
restoring entries deleted by this process, so the image should first be
saved if you are not sure that the entries are extraneous.
In most cases, undeclared variables represent some type of problem in
the development process and each entry should be investigated to
ensure that no problems are lurking in the application. If no references to
an entry can be found, the entry can be eliminated. Be especially careful
when removing items from DependentsFields if you do not understand why
they are there.

308 VisualWorks
Runtime Packager Process Details

If parcels are loaded that contain facilities used only in the development
image, such as the UIPainter and Store parcels, they should be unloaded
before beginning the packaging process. If you are unable to account for
entries in Undeclared after unloading these parcels, close the Runtime
Packager window, invoke garbage collection, and open Runtime
Packager again.
This step can be skipped if you do not want to eliminate the types of
global objects detected here.

Set Common Options


Options and data entry fields used in later steps are entered here. For
simplicity of organization, the options are grouped into pages of a
notebook. Pages in the notebook are as follow.
Basics Page
This page includes the essential elements that are always needed.
These are:
Startup Class and Startup Method
Enter the fully qualified name of the Startup Class, and the Startup Method
message selector. This message provides a convenient way to open
the initial window of the application or do other application
initializations. The initial message is included in the scan of sent
messages done later. If you do not use a Startup Class and Startup
Method, you will need to specify the starting point for scanning
referenced classes and methods in the next step.
Image Path Name
When the image is finally stripped, it is saved to the file named here.
For obvious reasons, this field is required and the file named must be
writable. The same conventions for appending suffixes to the file
name are used here as are normally used for image saves (that is,
don’t include the .im suffix).
Details Page
This page includes a variety of options that are commonly selected:
Remove compiler classes
Remove classes related to the public interface to the system
compiler. In many cases the compiler will be required in the runtime
environment.

Application Developer’s Guide 309


Chapter 14 - Application Delivery

Install emergency evaluator as a dialog


The emergency evaluator is invoked by pressing Control-Shift-Y. If
this box is checked, a dialog will appear confirming that the user
wants to exit the image. If this box is not checked, Control-Shift-Y is
ignored.
Process command line options
Process the runtime command line options. The following options are
recognized:
-pcl parcelFile1 parcelFile2 ... (load parcel files)
-cnf configFile1 configFile2 ... (load config file)
-psp dir1 dir2 ... (set the parcel search path)
-err errorFile (set the path for the error log file)
-notifier notifierClass (set class for unhandled exceptions)

Config files contain a list of parcel files to be loaded and also may
contain other command line options. If a file of the form
[Link] is found in the current directory, it is processed
as a config file before other options are processed.
Build headless image
Create an image that does not access the display. Refer to
Chapter 15, “Creating an Application without a GUI” for more
information.
Clear parcel search path
Clears the list of directories to be searched when loading parcels. If
this is not selected, the Settings values are preserved in the runtime
image.
Use three-step procedure
A three-step procedure is recommended for optimal runtime images.
The procedure will be used if this box is checked. Because of the
extra time required for three saves, the default is to create a slightly
less optimal image in a single step.
The three step save process does the following:
a Do Perm Save Image As..., then exit and restart.
b Do Collect All Garbage, snapshot, exit, restart (removes transient
objects in PermSpace).
c Snapshot one more time (compacts objects in PermSpace).
Skip default scan for unreferenced items
The menu item File ­ Package Runtime Image normally performs a scan
for unreferenced items as part of the packaging process. To skip
doing the scan, select this otion.

310 VisualWorks
Runtime Packager Process Details

Action on last window close


This option selects what is to be done when the last window in the
runtime image is closed. The choices are:
• Shutdown image - shutdown the image using normal quit
procedures
• Continue - continue processing without any windows open
• Standard Behavior - allow base image behavior to determine the
action
If you select Shutdown image, and there are no windows open when the
application startup procedure completes, then the image will be
shutdown at that time.
For a headless application, these options have special meaning.
Refer to “Delivering a Headless Application” in Chapter 15, “Creating
an Application without a GUI” for more information.
Platforms Page
This page allows you to select UILooks and Operating Systems to be
supported in the runtime image. By default only the current platform and
look are supported and any others required must be selected. These
selections are used to set defaults for classes to be deleted from the
system.
Exceptions Page
This page specifies information needed to handle exceptions in the
runtime image:
Error Notifier Class
The default class, RuntimeEmergencyNotifier, notifies the user and
creates a diagnostic dump when an unhandled exception is detected.
If you want to enhance the standard behavior, you can subclass
RuntimeEmergencyNotifier and specify your class here. Other error
handler classes provided with Runtime Packager are
RuntimeDebugNotifier and RuntimeQuietEmergencyNotifier. The fully
qualified name of the class to be used must be entered in this field.
Image Dumper Class
The default class, RuntimeImageDumper, is used by
RuntimeEmergencyNotifier to create the diagnostic dump when an
unhandled exception occurs. If your emergency error handler does
not use the dumper class, the dumper class may be omitted here.
The fully qualified name of the class to be used must be entered in
this field.

Application Developer’s Guide 311


Chapter 14 - Application Delivery

Error Log Path Name


The diagnostic dump created on unhandled exceptions is written to
the file named here. This file should be writable by the runtime user
for a dump to be created. Exceptions occurring during the final
stages of the image stripping process are also reported as dumps to
this file. If you do not want dumps to be created, leave this field blank.
Parcels Page
This page allows you to provide information about parcels that will be
loaded into the runtime image. Each parcel defined in the current image
is listed, enclosed in brackets (<>). When ENVY/Developer is being used,
applications are listed as well, but without brackets. Parcels and
applications indicated as loadable are shown in the selection list as bold
items.
The following information can be provided for each parcel or application
by selecting it from the list:
Parcel is loaded into image at runtime
Select this option for each parcel that is loaded into the image during
runtime execution. If unchecked, the code contained in the parcel is
saved in the image, and the parcel does not need to be included with
the application. If checked, other parcel options can be set.
For ENVY/Developer only, if an application is selected, the
application is saved as a parcel for loading into the runtime image.
Unload before saving runtime image
Select this option to cause the parcel or application to be unloaded in
the normal way before stripping and saving the runtime image. If this
option is not selected, all classes and methods defined in the parcel
or application are deleted before saving the runtime image, but
preUnload and removing actions are not performed.
Save options
The Save Loadable Parcels step to follow allows you to save parcels that
are loaded into the runtime image. This option controls how the
selected parcel is to be handled in that step. The choices are:
• Strip unreferenced items and save

Saves only those classes and methods that are determined to be


referenced. The contents of the parcel in the image are not
changed, but the version of the parcel saved omits unreferenced
items.

312 VisualWorks
Runtime Packager Process Details

• Save full parcel


The full contents of the parcel, including unreferenced items are
saved based on the current definition of the parcel in the image.
• Do not save parcel
The parcel is not saved. You should ensure that the parcel is
saved by using the Parcel Browser before the Test step is done
and before using the runtime image.
Path name
After the scan for referenced items is completed, loadable parcels are
stored. Each parcel is stored in the file specified with this option. The
path is required for the parcels that are to be saved. If the parcel is
not to be saved, empty this field.
Parcel operations
The following operation buttons are provided for convenience. The
operations can also be performed in a Parcel browser. (When
ENVY/Developer is in use, these buttons do not apply and are not
shown.)
• New Parcel - creates a new empty parcel and adds it to the list.
• Unload Parcel - unloads the currently selected parcel. This removes
classes and extension methods defined in the parcel from the current
image.
• Discard Parcel - discards the currently selected parcel. This removes
the definition of the parcel from the image and copies source of the
parcel to ensure that the changes are recorded.
• Browse Parcels - open the Parcel Browser.

Stripping Page
This page allows specification of options that control the final stripping
step. These options are:
Remove system organization
Remove the system organization and categories. This option will
reduce the size of the runtime image, but may conflict with some
services that require categories to be present. Defined categories are
replaced with empty category objects so that functions which expect
such objects to be present can operate without raising exceptions.
Package external interfaces
Prior to creating the stripped image, evaluate each instance of
CMacroDefinition and replace it with the resulting value.

Application Developer’s Guide 313


Chapter 14 - Application Delivery

Merge method and block byte codes


The byte codes that control the operation of the virtual machine are
merged so that unique values are stored in a single instance of
ByteArray rather than being duplicated.
Use compact compiled methods
CompiledMethod objects contain pointers to source code and other
objects not used in the runtime image. This option will cause a
replacement class to be used eliminating the extra storage needed
for the pointers. This option is primarily useful for images created
under ENVY/Developer since the space savings are otherwise
minimal.
Merge literals
Multiple instances of compiler generated literals with the same value
are merged into single instances. This option should be used very
carefully since it can cause some application bugs to be manifest in
ways that could be very difficult to debug.
Merge methods
Multiple instances of methods that are equal except for the class in
which they appear are merged into a common method. In many
cases, this operation is safe, but since the identity of merged
methods cannot be determined in all cases, there are possible
exposures, especially in some exception handling logic.
Remove unreferenced globals
Unreferenced globals are set to nil and removed from their name
spaces during the final stripping step if this option is selected.
Trace Level
During the final stripping step additional information can be logged in
the progress notifier window so that hangs or crashes can be
isolated. For the Medium setting, classes and globals being removed
are shown. For the High setting, individual methods are shown as they
are stripped from the image.
Prestrip Class
Prestrip Class names a class to which a message will be sent before
the actual stripping processing commences. This message can be
used to invoke user logic for customizing some aspects of the
stripping process, for example, by becoming a dependent of
RuntimeManager and monitoring changed: messages used to inform of
progress through the different steps in the stripping process. The fully
qualified name of the class should entered in this field.

314 VisualWorks
Runtime Packager Process Details

Prestrip Method
Prestrip Method names the method to which the pre-strip message is to
be sent. This method must be one the prestrip class can respond to.
If no message is to be sent, Prestrip Method and Prestrip Class should be
blank.

Specify Items to Keep and Delete


In this step you specify globals, classes, and methods that should be kept
or deleted in the final runtime image. Items are divided into three major
categories:
• Deleted - these are always deleted in the runtime image.
• Contingent - these are deleted if no references to them can be
detected.
• Kept - these are kept in any case.
Being deleted has different meanings for different types of objects. For
globals, being deleted means that the global's name is removed from the
system, and any previous references to the global by name become
references to nil. For classes, being deleted means that the class is
replaced with a subclass of Object having no methods of its own and
having the class name removed from the system. Deleting a method
means removing it from the method dictionary in which it appears.
When a class is kept, only the definition of the class is necessarily kept in
its entirety. Methods that are unreferenced can still be deleted from kept
classes. When a method is kept, it will remain in the runtime image only if
its defining class is not otherwise deleted.
Several rules are enforced in the specification of kept and deleted items.
You cannot delete a class if subclasses of it are kept. Specifying that a
class is to be deleted implicitly specifies that its subclasses are also to be
deleted.
The name spaces selection list allows selection of the name space used
for displaying categories and classes. Other than the name space list,
which is a single entry selection list, the other selection lists appearing in
this window are multi-select lists. Items that are selected are indicated
with check marks in the normal fashion. When you select categories, all
classes in those categories are shown in the class selection boxes. When
you select classes, all methods implemented on either as instance
methods or as class methods are shown in the method selection boxes.

Application Developer’s Guide 315


Chapter 14 - Application Delivery

Name spaces do not appear with the list of classes and globals. There is
no provision for keeping or deleting a name space, and by default all
name spaces are kept.
The status of classes and methods can be changed by pressing the
buttons between the selection boxes. The meaning of the buttons is
mnemonic:
>> means move all selected items from the left to the right.

<< means move all selected items from the right to the left.
After items are moved, they become the selected items in the box to
which they are moved. Hence, you can easily undo an erroneous button
press by pressing the button for movement in the opposite direction.
Pop-up menus are provided in each selection box. These can be used to
select all items currently appearing in the box, clear all selections, look at
specific items, and scan for references. Two types of reference scan are
provided. The standard reference scan is provided by the Browser
classes and may miss some references that will be detected during the
more complete scan for referenced items in the next step. The extended
reference scan is more inclusive. It also allows you to filter out the items
that are not being kept in the runtime image, which is especially useful
after the scan for referenced items has been completed in the following
step.
When ENVY/Developer is present in the image, the top right selection list
box in the window can be toggled between VisualWorks categories,
ENVY/Developer applications, and ENVY/Developer subapplications
using the pop-up menu. When applications are shown in this selection
box, all classes in the application or its subapplications are shown below.
Otherwise, only classes immediately contained in the selected categories
or subapplications appear.
Classes that are dynamically loaded through the use of parcels should be
indicated as contingent or kept in the runtime image.
A menu option is provided for resetting classes and methods to their
default settings. Only classes and methods in currently selected
categories (or applications) will be affected. This permits a more selective
way to reset to default values than would be achieved by pressing TAKE
DEFAULTS in the main window.

316 VisualWorks
Runtime Packager Process Details

Pop-up Menus
The following pop-up menus are used to perform actions with respect to
applications, categories, classes, and methods shown in this step. These
pop-up menus can also be selected from the window's main menu.

Category and Applications Menus


• Select all - Select all categories or applications
• Clear all - Clear all category/application selections
• Find Category/Application ... - Find a category/application
• Find Class ... - Find a class by name
• Find Global … - Find a global by name
• Show Categories - Select by system categories (default when using
VisualWorks)
• Show Applications - Select by applications (ENVY/Developer only)
• Show SubApplications - Select by subapplications (ENVY/Developer
only)
• Remove Empty Categories - Remove system categories that contain no
classes.
• Reset to Defaults - Reset either all or only selected
categories/applications to defaults in keeping and deleting classes
and methods.

Classes Menu
This menu is available as a pop-up menu for Deleted, Contingent, and
Kept classes. This menu can also be selected via the Classes entry in
the window's menu bar.
• Select all - Select all classes in the related selection list
• Clear all - Clear all selections in the related selection list
• Browse - Open a browser on the selected class
• References - Use the Extended References Browser to located
references to the selected class

Application Developer’s Guide 317


Chapter 14 - Application Delivery

Methods Menu
This menu is available as a pop-up menu for Deleted, Contingent, and
Kept methods. This menu can also be selected via the Methods entry in
the window's menu bar.
• Select all - select all methods in the selection list
• Select category - select methods in a chosen category
• Clear all - clear all selections in the selection list
• Browse - open a method browser on selected methods
• Implementors - browse all implementors of the chosen selector
• References - Use the Extended References Browser to located
references to the chosen selector

Defaults
By default, some classes are kept. These are kernel classes and they are
almost certainly needed to make a runtime image. For a complete list,
see the method defaultClassesKeptVW in class RuntimeBuilderItems.
Class RuntimeManager within Runtime Packager is needed for image start-
up and is by default also a kept class. RuntimeManagerStripper is a special
subclass of RuntimeManager used to complete the stripping operation and
is required. It is eliminated in the final runtime image.
Global objects are not kept by default, but the major system globals are
referenced in numerous places will be detected as referenced.
Classes that are not generally used in the runtime image are deleted by
default. These classes come from the Tools name space and related Tools
categories. For a complete list, see the method defaultClassesDeletedVW in
class RuntimeBuilderItems.
The EtTools application and its classes are deleted by default when
ENVY/Developer is present. Similarly, subapplication
EmLibraryManagement and its classes are also deleted by default. Both
EtTools and EmLibraryManagement are removed from the image by the
standard ENVY/Developer method for removing ENVY/Developer.
Indicating that their classes are deleted prevents spurious references
during the scan for unreferenced items.
When an image starts, ObjectMemory sends update:with:from: messages to
all its dependents. By default, the classes of all dependents of
ObjectMemory are kept. If you know that a dependent is not needed in the
runtime image, you can specify the class as deleted.

318 VisualWorks
Runtime Packager Process Details

Scan for Unreferenced Items


In this step the image is scanned to detect classes, methods, and globals
that should be kept in the runtime image. Conceptually, the scan is a
straightforward process. Kept methods within kept classes are scanned
for selectors representing message sends and references to classes and
globals. As new methods, classes, and globals are detected, they are
added to the list of kept items and, in turn, scanned for references to
other items. Eventually the processes reaches the point at which no new
references can be detected and the scan ends. For details of this process
and how to modify it, see “Customizing Detected References” below.
The initial kept classes and methods are those indicated as such in the
previous step plus the application startup class and method as well as
classes named in the various Options specifications.
If a baseline image is to be built where unreferenced classes and
methods are kept for future use, the Scan for Unreferenced Items step should
be skipped. When this step is skipped, only classes and methods
explicitly indicated as deleted and those associated with runtime loadable
parcels will be removed from the current image to create the baseline.
Deleted classes are bypassed in the scan for referenced items, as are
deleted globals and methods.
The following special class methods are used to allow classes to specify
additional items to be kept:
dynamicallyReferencedClasses
Answer a collection of classes or qualified class names that are to be
kept in the runtime image.
dynamicallyReferencedSelectors
Answer a collection of symbols naming methods that are to be kept in
the runtime image.
dynamicallyReferencedGlobals
Answer a collection of qualified names of globals that are to be kept
in the runtime image.
itemsReferencedBySelector: aSymbol
Answer the collection of literals including symbols, variable bindings,
and classes referenced in the instance method named by aSymbol.
In the scan, these literals replace entirely those found in the method
itself.

Application Developer’s Guide 319


Chapter 14 - Application Delivery

itemsReferencedByClassSelector: aSymbol
Answer the collection of literals including symbols, variable bindings,
and classes referenced in the class method named by aSymbol. In
the scan, these literals replace entirely those found in the method
itself.
When these selectors are implemented as class methods, the answers
provided by them are used during the scan to include classes, methods,
and globals to be considered referenced and thus kept in the runtime
image. If an improper answer is returned by these selectors, a dialog is
used to alert you to the error.
To start scanning, click the Do This Step button. A window will open to show
you progress reports. Scanning a large image might take some time.
When the scan is complete, a dialog box opens summarizing the results
of the scan. You can see more detailed information by proceeding to the
next step.
If you choose to bypass this step, only classes, methods, and globals that
you have explicitly indicated as deleted will be removed from the runtime
image.

Review Kept Items


In this step, you review the detailed results of the previous step. The first
time through, this might not be especially meaningful, but you probably
want to make sure that your application was not somehow bypassed in
the scan and thus declared deleted. If you are pursuing an aggressive
strategy of removing all extraneous classes, you would want to check
here to see that you have eliminated exactly what you intended.
When you click Do This Step, a window with a collection of selection boxes
opens, that can be used to view categories, classes, and methods. In this
step, there is no contingent category. Everything being deleted is shown
as such.
You can select classes to see which of their methods are being deleted. If
you select a deleted class, you will be shown methods, some of which
may be kept and some deleted. All methods are removed from a deleted
class even if some appear as kept here. You can use pop-up menus to
browse or scan references for all methods shown.
There are no decisions to make in this step. If you find that something
needs to be changed, you must return to an earlier step to make the
change. In all likelihood, you will want to rerun the scan for referenced
items after your change.

320 VisualWorks
Runtime Packager Process Details

RuntimeManagerStripper is shown as kept when, in fact, it will be eliminated


in the final runtime image. This class is a special case due to the need to
discard the stripper methods after the final image has been created, but
not before. Similarly, copyright methods are preserved in the final image
but may appear as deleted here. These are also treated as special cases
to preserve copyright markings.
Press the Close button to close this window. If you want to write a report
file, press the Report button. You will be prompted for the name of the file
to which the report should be written. After the report is written, a file
browser is opened on the report file created.
Pop-up Menus
The pop-up menus used in this step function the same as those
described “Specify Items to Keep and Delete” above.

Save Loadable Parcels


Parcels identified as loadable in the runtime image are saved to files in
this step. The identification of which parcels are loadable and the file
names under which they will be saved occurred in the Set Common Options
step earlier. Changing which parcels are loadable would affect the
outcome of the previous scan step.
Parcels are saved in their entirety except for those where the Save only
referenced classes and methods option was selected. For these parcels,
only classes and methods found to be referenced in the previous scan
step will be saved. The contents of the parcel in the current image are not
changed.
Parcels are saved without source. Care should be taken that a version of
the parcel file (*.pcl) is not overwritten when a corresponding parcel
sources file (*.pst) is present. If the overwrite occurs, the source file will
not be usable and the source for the parcel may be lost.
When ENVY/Developer is being used, an application can be saved as a
parcel. In this case, a parcel is created for the purpose of saving the
contents of the application. Properties of the parcel are set from the
application. In particular, the name of the parcel is the same as the
application, the parcel’s comments are taken from the application classes
comments, and the version of the parcel is taken from the version of the
application. If the application class defines loaded or removed methods,
postLoad and preUnload actions are defined for the parcel.

Application Developer’s Guide 321


Chapter 14 - Application Delivery

To start this step, click Do This Step. The names of parcels saved are
written to the Transcript as the saves proceed. If a file is about to be
overwritten, you will be prompted for permission to overwrite the file
before proceeding.
If there are no runtime loadable parcels, this step can be skipped. If no
parcels have been defined as runtime loadable, you will be notified if this
step is attempted.

Test the Application


It would be nice to say that the process of scanning references was
foolproof and that you could rest assured that all the classes and
methods, and only the classes and methods, needed to run your
application will remain in the runtime image. This, however, is not the
case. A number of types of references can slip past unnoticed causing
the application to die an untimely death in the runtime version.
Typically the problems revolve around dynamically created names. For
example, the following code would cause an undetected reference:
pickOne: aString
self perform: (#pick, aString) asSymbol.
In general, there is no way to know what values aString might assume,
even if you could connect them together with the naming convention that
prefixes them with #pick to form a selector name. Classes and globals can
be referenced in similar dynamic fashion.
Similarly, extraneous references are very common. For example, there is
frequently a chain of references leading to Visual Launcher, which, in
turn, references most programming tools, none of which are probably
needed in the runtime image. The only way to eliminate these extraneous
references is to delete either classes or methods explicitly.
This step provides a way for you to take a broad-brush approach towards
which classes and methods are needed and find out where you were
wrong by running test cases. When you reference a deleted class or
method during a test, it is recorded as referenced and can be fed back to
the selection of kept items. If your test cases are sufficient, you will
discover all dynamic references. At a minimum, you should be able to
discover the pattern of which things are dynamically referenced and
which are not.

322 VisualWorks
Runtime Packager Process Details

Note that image startup and shutdown processing is not included within
the scope of a test. Deleting classes or methods used in startup or
shutdown is a common source of difficulty in creating a viable runtime
image. In most cases, dumps written to the error log are the best way to
debug startup or shutdown problems.
If you have extra windows open, for example a browser, you should close
or minimize them before beginning an application test. References to
classes and methods from all open windows are considered part of the
application being tested. Having extra windows open tends to result in
extraneous references and may cause you to include unneeded classes
and methods in the runtime image.
Buttons along the top of the window control activities during the
application test. These buttons are:
Save Image
In spite of all precautions to the contrary, it is possible that the image
could be corrupted during the test. If you don’t have a recently saved
image, click this button to save it now. Once the test starts, you do not
want to save the image.
Begin Test
This button begins the test process. All deleted classes and methods
are altered to allow the detection of any references to them. When
that process has been completed, the startup message, if any, is sent
and the application starts its execution in the normal way.
End Test
When you have completed application testing, pressing this button
will restore the image to its status prior to the beginning of the test. If
debugger windows are opened during the test, you might want to
press this button before debugging the problem.
Accept Dynamic References
As deleted classes and methods are referenced, they are reported in
the scrollable text area below. If you want to accept all such classes
and methods as items to be kept in the runtime image, press this
button.
Ignore Dynamic References
In some cases you may see references to items that are clearly not
part of your application. For example, you may see references to the
debugger if an error occurs. To ignore the dynamic references
appearing in the text area below, press this button. All dynamic
references displayed will be ignored. This button is active both during
and after the test. If pressed during the test, displayed dynamic

Application Developer’s Guide 323


Chapter 14 - Application Delivery

references revert to the status they had at the beginning of the test. If
pressed after the test, displayed dynamic references are simply
ignored.
References to classes and methods that would have been deleted in the
runtime image are shown in the scrollable text area as they occur. Only
the first reference to each item is shown. Once the references are
accepted or are ignored, the text area is cleared.
After the test ends, press OK to have all accepted dynamic references
included as kept items. You might want to go back and rerun the scan
step at this time to pick up other classes and methods that are now
reachable but just did not happen to get used in your test. If you press
Cancel, no changes are made to the kept items and the window is simply
closed.
Classes and methods that are potentially loadable through parcels do not
get special treatment in this step. The assumption is that loadable parcels
will be loaded into the image through command processing at start up or
through some equivalent process. Do not allow parcels to be loaded
during the test or you not be able to recover the source for methods
contained in such parcels, as would be case if runtime parcels into any
development image.

Set Runtime Memory Parameters


This step provides a way to set memory parameters for the runtime
image.
Space sizes
• Eden - initial space used for creating new object
• Survivor - space used for new objects that have graduated from Eden
• Large - space for objects larger than 1K
• Stack - space holding the portion of the stack not converted to objects
• Code Cache - cache of dynamically compiled method machine code
• Old - tenured objects that have graduated from survivor space

Policy Values
• Growth Increment - how much additional memory to allocate at a time
when growing memory
• Retry Decrement - how much less to ask for if memory is not available
when an attempt is made to grow memory

324 VisualWorks
Runtime Packager Process Details

• Growth Regime Limit - size at which reclaiming space is preferred to


expansion
• Memory Limit - maximum amount of memory allocated
Space sizes have a minimum value of 10000 bytes and a maximum value
of 1000 times the default sizes at startup. Growth Increment must be at least
twice Retry Decrement and no more than Memory Limit.
Notes
Spaces sizes are derived from values supplied by the method
ObjectMemory class>>defaultSizesAtStartup.
Memory policy values are taken from the following aspects of
MemoryPolicy:
• preferredGrowthIncrement
• growthRetryDecrement
• growthRegimeUpperBound
• memoryUpperBound
When the values specified here are recorded in the parameters file, they
are recorded as double floating point fractions of the default sizes at
image startup. It is possible that a small difference could appear between
the value entered and the value restored when loaded from the
parameters file. When moving from one platform to another, the values
will be converted proportional to the default values of the new platform.
This conversion should be reviewed whenever a parameters file created
for one platform is used on another since simple proportions may not be
the optimal settings. If you have not changed any of the values provided
here from their initial defaults, then the default values on image start-up
would always be used regardless of platforms since the fraction recorded
in the parameters file would be 1.0 in each case.

Strip and Save Image


This step is the final one. Deleted classes and methods are removed and
the final image is saved under the name provided earlier on the options
window. The final image is created with the following steps:
1. The image is checked for instances of applications that will be
deleted. Associated windows will be closed automatically if you press
Yes in the dialog box.
2. VisualLauncher instances are closed if you so authorize by pressing
Yes in the dialog box. You probably do not want the standard launcher

Application Developer’s Guide 325


Chapter 14 - Application Delivery

in your runtime image, but you might want to use instances of the
launcher in your application. If so, you should close the launchers
manually and just say No here instead of closing all launchers
automatically.
3. You will be given one last chance to change your mind.
4. Parcels that are loadable at runtime are removed from the image.
Only the definitions are removed. Classes and methods defined in
these parcels are removed in a later step of the stripping process.
5. Subclasses of ExternalInterface are packaged for the runtime
environment. C macros are fully expanded.
6. The emergency notifier is installed. If you are using the default notifier
class RuntimeEmergencyNotifier, any errors after this point will cause a
dump file to be written. If the debuggers are stripped out this could be
the only way to debug a problem in this step.
7. ENVY/Developer is removed if it is present in the image.
8. Sources are discarded. That is, the image will no longer look for
sources or create changes entries. If ENVY/Developer is present, the
connection with the manager library is terminated.
9. A new emergency evaluator is installed. The evaluator is invoked
when you press Ctrl-Shift-Y. The replacement evaluator is a dialog
confirming that you want to quit now.
10. A series of mundane clean-ups are done. One of these is clearing the
Transcript. The transcript is written to the dump file on errors, and you
might want to place application error messages there even if the
transcript is not shown to the user.
11. If you requested deletion of the compiler, the default pop-up menu for
text fields that could contain code is replaced with the menu for
straight text (the compiler is needed to evaluate anything). A few
other menus that reference the compiler are not altered. If you select
DoIt from these menus, the request is ignored.
12. System and method categories are discarded if the remove system
organization and categories option was selected.
13. Methods, classes, and globals are deleted from the image. Copyright
notices in the method named copyright are always retained.
14. If selected, literals are merged based on value.
15. If selected, multiple instances of the same byte code string are
consolidated into a single instance referenced from multiple methods.

326 VisualWorks
Debugging a Deployed Image

16. If selected, multiple instances of the same method are consolidated


into a single instance as a method in RuntimeMergedMethodOwner.
17. If the compact compiled methods option was selected the method
dictionaries are rebuilt with instances of CompiledMethod replaced by
RuntimeCompiledMethod instances.
18. The symbol table is rehashed to reclaim space from unreferenced
symbols.
19. You are informed that the image is about to be saved. This window
and the one appearing later do not have controllers. You will be given
5 seconds to read the contents and then the window is closed
automatically.
20. The garbage collector runs to remove all the deleted objects from the
image. A Perm Save is then done.
21. Another window appears letting you know that the save was done
successfully.
22. The image exits.
If you are using the three step procedure for saving the image, the image
is left in a state in which another save is done upon image startup for the
next two times the image is launched. A garbage collection is done before
the first of these saves.
You now have a stripped runtime image.

Debugging a Deployed Image


Although a deployed image does not contain development tools, such as
the debugger, there is still a limited amount of debugging facility available
for a deployed image.
The default Runtime Packager configuration opens a notifier with a brief
message describing the error. It also writes a file, by default named
[Link], which contains more diagnostic information and a message
stack. These actions are specified on the Set common options, Exceptions
page.
Given the information provided in the error log file, you should be able to
trace the source of the error in the unstripped image using the usual
methods.
You may also provide your own Emergency Notifier to enhance the
information and features of the one provided by Runtime Packager.

Application Developer’s Guide 327


Chapter 14 - Application Delivery

Customizing the Emergency Notifier


Notifiers to handle otherwise handled exceptions are provided in
RuntimeErrorNotifierFramework and its subclasses. A different class, for
example, a subclass of RuntimeEmergencyNotifier, can be specified as an
option for the Error Notifier Class. This notifier is invoked by sending the
class the message notify: anException context: aContext when an unhandled
exception occurs.
RuntimeErrorNotifierFramework provides the basic mechanics for handling
the exception, but does not include a user interface. Typically one of the
subclasses of RuntimeErrorNotifierFramework would supply the necessary
support for the user interface. The following hierarchy is supplied with
Runtime Packager:
RuntimeErrorNotifierFramework "abstract class"
RuntimeEmergencyNotifier "terminates after notifier"
RuntimeDebugNotifier "user decides whether to terminate"
RuntimeQuietEmergencyNotifier "writes error log without notification"
Class variables in RuntimeEmergencyNotifier carry the text of messages
presented to the user and permit changing the messages merely by
changing the value of the class variable prior to stripping the image. The
string values of these variables can be changed through the following
accessor functions:

dumpFailedMsg: a diagnostic dump could not be written


userInterruptMsg: control-Y was pressed (user interrupt)
errorOccurredMsg: an unhandled exception has occurred (programming
error)
emergencyAbortText: shift-control-Y was pressed (normally emergency
evaluator

RuntimeErrorNotifierFramework prevents recursive error conditions by


setting the class variable ErrorState to different values as the error
notification processing is done. If another unhandled exception occurs
during the notification process itself, processing is restarted with few
steps being attempted. Subclasses of RuntimeErrorNotifierFramework can
take advantage of this behavior by overriding only selected methods.
See RuntimeQuietEmergencyNotifier for an example.
Actions taken to record diagnostic information are controlled by the Image
Dumper Class option specification, which by default is RuntimeImageDumper.
To change this behavior, either supply your own class or use a class from
the following hierarchy:

328 VisualWorks
Customizing Detected References

RuntimeDumperFramework
RuntimeShortImageDumper
RuntimeImageDumper

Customizing Detected References


The process of scanning for unreferenced items can be customized for
the classes, methods, and globals that are discovered as referenced.
These references can be altered by explicit specification through the user
interface, but including the overrides in the application itself offers
advantages in terms of maintenance and removes some possibilities of
error.
These customizations are performed by way of class methods detected
during the scan process. When a class is found to referenced, the class
methods implemented by the class itself, as opposed to those inherited,
are examined. The following class methods are invoked if implemented
by the class:
dynamicallyReferencedClasses
Answers a collection of classes or qualified class names that are to
be kept in the runtime image. This method could be simple as a literal
containing an array of class names or much more complex.
dynamicallyReferencedSelectors
Answers a collection of symbols naming methods that are to be kept
in the runtime image. Again, this method could be a simple literal,
enforce an application naming convention, or perform a more
complex analysis of the image.
dynamicallyReferencedGlobals
Answers a collection of qualified names for globals that are to be kept
in the runtime image.
When a selector is found as referenced, all implementations of the
selector in referenced classes are examined for further references. The
following class methods can be implemented by the implementing class.
If found, these methods can supply an alternative collection of references
to replace the references that would have been inferred by the usual
algorithm used in the scan process.
itemsReferencedBySelector: aSymbol
Answer the collection of literals including symbols, variable bindings,
and classes referenced in the instance method named by aSymbol. In
the scan, these literals replace entirely those found in the method
itself.

Application Developer’s Guide 329


Chapter 14 - Application Delivery

itemsReferencedByClassSelector: aSymbol
Answer the collection of literals including symbols, variable bindings,
and classes referenced in the class method named by aSymbol. In the
scan, these literals replace entirely those found in the method itself.
If nil is answered by these methods, then the normal inferred references
are used.
Examples of the general pattern used by these customizing methods can
be found in class RuntimeManager.

Customizing Image Stripping


RuntimeManager provides for customizing the stripping process in several
ways.
The Set common options, Stripping page options allow you to name a class
and method to be invoked just prior to stripping the image in the Prestrip
Class and Prestrip Method fields. The method you supply allows you to
establish the necessary environment for further stripping, if needed.
During the stripping operations, RuntimeManager changed: messages are
sent to allow your application to monitor progress in the final stages of
stripping the image and to insert the necessary special processing
required for the particular application being stripped.
RuntimeManager class>>postStripBlock: allows you to register a block that is
evaluated after the stripped image has been created. The block is
evaluated just before the image save. The postStripBlock is a convenient
place to release any relationships used only during the stripping process.

Trouble Shooting
Workspace or Browser is Opened with the Application
Typically this is because you left the window open before stripping.
Perhaps it was minimized. Make sure all windows are closed and restrip
the image.

Parcel File not Readable


A notifier tells you that “Parcel file [Link] is not readable.”
If you are using a parcel not built by Runtime Packager, the parcel is
probably not on the deployed image’s parcel path. Try copying the parcel
file to the image directory.

330 VisualWorks
Trouble Shooting

Application Cannot Find a Parcel Source File


A notifier tells you that it “Failed to find source file [Link].”
This occurs if you are using a parcel not built by Runtime Packager, and
was saved with sources, but the source file is not in the same directory
with the .pcl file. For deployment purposes, you should rebuild the parcel
without sources.

Application Exits Immediately


The splash screen displays, but the application exits immediately.
Usually you want the application to close when there are no more
windows open (Action on last window close option on the Common Options,
Details page). If the specified action is Shutdown image, which is the default,
then your application is not opening a window for some reason.
If you are trying to build your deployment image with an application
window open, check to make sure your application code is being kept
(see the Specify classes and methods to keep step).
Otherwise, investigate to see why your Startup method and class or your
post-load action are failing to open your application window.

An Identifier has no Binding


A notifier opens saying “Unhandled exception:The identifier xxx has no binding”
This indicates that the item specified (xxx), usually a class, is not defined
in the system. On opening an application that is defined in a parcel, it
may mean that you didn’t specify the parcel on the command line. Later
in an application it may mean the same thing, that the defining parcel isn’t
loaded, or simply that the class is not defined in the system.

Application Developer’s Guide 331


15
Creating an Application without a GUI

Applications that rely on direct user interaction typically provide graphical


user interfaces for collecting input and displaying output. You can also
write batch or server applications that, by their nature, do not rely on
direct user interaction, and may run on computers that have no console
or windowing system. Such applications execute in headless
VisualWorks images—that is, images that run with the display system
deactivated (in headless mode).
This chapter describes the general steps for creating a headless image
and executing an application in it.

Key Concepts
The headlessness of an image is controlled by the sole instance of the
class HeadlessImage. This instance (HeadlessImage default) enables you to
create new images by saving them either in headless mode (with the
display system deactivated) or in “headful” mode (with an activated
display system). You typically develop your application in a headful
image, test it in a headless image, and then debug it in a headful image
that is created from the headless image. The HeadlessImage instance
records the image’s mode and can be queried for it.
The basic way to provide input to a headless image is through a startup
file. A startup file is a file that contains Smalltalk expressions in file-in
format. When a headless image is started, it reads the file and evaluates
the expressions. You typically use a startup file to start your application in
the headless image. Applications can also accept input through sockets,
file I/O, TTY interaction, and so on.
By default, output that would normally be displayed in the System
Transcript is saved to disk in a transcript file.

332 VisualWorks
Setting Up a Headless Image

Setting Up a Headless Image


To prepare to execute an application in headless mode, you start with a
standard VisualWorks image, configure it, and then create a headless
image from it, as described in the following steps:
1 In a standard VisualWorks image, parcel in [Link]. This
introduces the HeadlessImage class plus several other classes in the
category Headless-Support.
2 Write your application so that it can run in headless mode (see “Tips
for Programming a Headless Application”below). Note that the
application can send messages to the HeadlessImage instance (for
example, to test whether it is running in a headless or headful image).
3 Decide how you will want to start your application and prepare
accordingly (see “Techniques for Starting a Headless Application”
below). You may want to file out your application into a startup file, or
make certain modifications to the system. A basic technique is to
leave the application in the image and create a startup file that
contains a line such as MyApplication open!.
4 Decide whether you want the headless image to file in a startup file,
and if so, whether to use the default startup filename
([Link]).
• If you do not want to use a startup file, evaluate the following
expression:
HeadlessImage default startupFilename: nil
If you want to use a startup file with a nondefault name (for
example, [Link]), evaluate an expression such as the
following:
HeadlessImage default startupFilename: '[Link]'
The default name is returned by the defaultStartupFilename class
method.
5 Decide whether you want the headless image to append transcript
messages to the file [Link]:
• If you do not want to use any transcript file, evaluate the following
expression:
HeadlessImage default transcriptFilename: nil
• If you want to use a transcript file with a nondefault name (for
example, [Link]), evaluate an expression such as

Application Developer’s Guide 333


Chapter 15 - Creating an Application without a GUI

the following:
HeadlessImage default transcriptFilename: '[Link]'
The default name is returned by the defaultTranscriptFilename class
method.
6 Create a headless image by selecting File ­ Save Headless As... or by
evaluating an expression such as the following:
HeadlessImage default saveHeadless: 'headlessImageName'
This creates a new image named [Link] in which
HeadlessImage’s state is set to headless. Creating a headless image has
no effect on the current image.

Running an Application in Headless Mode


To run an application in headless mode, start the headless image as you
would normally start a standard VisualWorks image. The difference is in
the virtual machine executable you run, or the command line options you
use.

Starting on Unix/Linux
Most of the Unix platforms have a headless engine. These engines
exclude the GUI and window management primitives, dynamically
loading them as required from a shared library. (The all-in-one, “headful”
engines are still provided.)
The headless engines are named in the vw<platform> format, as
usual. The GUI inclusive engines are named vw<platform>gui. To
start a headless image using a headless vm, simply invoke the virtual
machine with the image as usual, for example:
vwlinux86 [Link]
plus any necessary options.

Starting on Windows
On Windows platforms, you will want to suppress the splash screen and
sound, however, so use the -noherald command line option:
visual -noherald [Link]
On Windows systems, there are two console engines available:
[Link] and [Link]. Use the appropriate engine
to launch the headless image instead of [Link].

334 VisualWorks
Running an Application in Headless Mode

When an Image Starts


When a standard VisualWorks image starts, ObjectMemory installs objects
that are fundamental to the display, thereby hooking the image up to the
host windowing system. ObjectMemory also broadcasts
#returnFromSnapshot to its various dependents, which respond with their
own startup actions.
When a headless image starts, the ObjectMemory refrains from hooking it
up to the underlying windowing system and does not install the objects
that are associated with the display. Consequently, those objects are not
available for referencing later.
The HeadlessImage is registered as a dependent of ObjectMemory. Upon
receiving #returnFromSnapshot, the HeadlessImage instance checks its state
to verify that the image is headless and replaces the normal Transcript (a
display-oriented object of class TextCollector) with a file-based surrogate
of class FileTextCollector or NullTextCollector if a nil transcript filename is
provided. The normal Transcript is retained (so it can be reinstalled in the
image if it is saved as headful), but is not accessible while headless.
Finally, the HeadlessImage instance checks whether a startup file has been
specified; if so, the start-up file is filed in and the Smalltalk expressions in
it are evaluated. Typically, these expressions start up the headless
application.

If an Application Attempts to Access a Display


If an application that is running in a headless image attempts to access
the non-existent display, the attempt is trapped, and a HeadlessImage
headlessErrorSignal exception is raised. If the exception is not caught, the
offending process is suspended and saved by the HeadlessImage instance
for debugging.
More specifically, the message #checkHeadless is sent to the HeadlessImage
instance by methods that attempt to create instances of DisplaySurface or
its subclasses (for example, ScheduledWindow or ApplicationWindow). In a
standard, headful image, #checkHeadless returns without any side effect. In
a headless image, the HeadlessImage instance responds to #checkHeadless
by sending itself #cannotSend. This, in turn, causes the HeadlessImage
instance to raise the exception, suspend and save the process, write a
context trace to the transcript file, save the image as a headful image,
and then terminate the image.

Application Developer’s Guide 335


Chapter 15 - Creating an Application without a GUI

Debugging a Suspended Process


When a process has been suspended as the result of an attempt to
display something, you can use the saved, headful image to debug a
suspended process:
1 Start the headful image that was saved by the headless image before
it terminated. By default, the image is called headless-debug,
which is returned by the defaultDebugImageName class method.
2 Inspect the suspended processes by evaluating the following
expression:
HeadlessImage default suspendedProcesses inspect
3 In the Inspector, select a process and then invoke debug from the
<Operate> menu. VisualWorks brings up a debugger on the selected
process.

Creating a Headful Copy of a Headless Image


In general, you can create a headful copy of a headless image by
including an expression such as the following in your application code or
by providing the expression in file-in format in a startup file:
HeadlessImage default saveHeadfull: 'name'
In the resulting image, the HeadlessImage instance’s state is set to headful,
which enables the display at startup. Saving a headful image from a
headless image is useful if you need to debug a failure (see “Debugging a
Suspended Process” below).
When a headful image is created from a headless one, the normal
Transcript is restored.

Tips for Programming a Headless Application


Your headless application may do whatever you wish, as long as it does
not access the display. When programming your application, you need to
consider how to start it, how users can communicate with it, how to
terminate it, and how to prevent it from accessing the display.

336 VisualWorks
Tips for Programming a Headless Application

Techniques for Starting a Headless Application


A simple technique for starting an application is to write it in a start-up file
(in file-in format). The start-up file is read and evaluated (filed in) when
the image starts. By writing your application in the start-up file, you have
the flexibility to make changes and re-execute relatively quickly. That is,
you can change your application without having to start up and save a
headful image; you can simply change the startup file and restart the
headless image.
Alternatively, you can write your application in the headful image from
which you will create the headless image. When your application resides
in the headless image, you have three options for starting it:
• Use the start-up file—for example, MyApplication open!.
• Modify HeadlessImage>returnFromSnapshot to fork off a process with
your application—for example, (MyApplication open) fork.
• Register your application as a dependent of ObjectMemory and wait for
#returnFromSnapshot to be broadcast. Look at HeadlessImage for an
example, particularly #initialize and #update: If you do this, make sure
that HeadlessImage appears before your application in the dependents
collection.

Techniques for Communicating with a Headless Application


Your application must provide some means other than a window system
for users to interact with a headless image. This can be addressed with
sockets, file I/O, or some other manner.

Terminating a Headless Application


Your application should make provisions for shutting down gracefully,
under both normal and exceptional circumstances. The last message
send should be ObjectMemory quit, which causes the image to terminate.
Failure to do so will leave the image running, but with nothing to do. Your
only recourse then is to terminate the image from the operating system
(for example, by using kill in UNIX).

Sending Output to the System Console


When running in headless mode, it is frequently necessary to send output
to the console.
In the image, write an external interface to either use write(0,...) to write
to stdout, or load the C runtime library and use printf. For example, here
are two methods:

Application Developer’s Guide 337


Chapter 15 - Creating an Application without a GUI

printf: aString
<C: int printf(void _oopref *aString)>
^self externalAccessFailedWith: _errorCode

"self new printf: 'Hello World!',


(String with: Character cr with: Character lf with: (Character value: 0))"
"self new printf: ('Hello World!',
(String with: Character cr with: Character lf)) asFixedArgument"

printfArgs: argArray
<C: int printf(...)>
^self externalAccessFailedWith: _errorCode

"self new printfArgs: (Array


with: 'Hello %d %s!', (String with: Character cr with: Character lf)
asFixedArgument
with: 1
with: 'World')"
Also, explore the ThapiExample parcel as a guide for more examples.

Preventing Access to the Display


If a headless application attempts to access the non-existent display, the
attempt is trapped, and a HeadlessImage headlessErrorSignal exception is
raised.
Your application can ignore this exception and rely on default behavior.
Alternatively your application can handle the exception as appropriate.
Note that you can still execute the default behavior if you #proceed rather
than #return in the exception handler. For example:
[...whatever your application does normally...]
on: HeadlessImage headlessErrorSignal
do: [:exception |
... special handling for the headless error...
exception resume]
If your application is to have different behavior depending on the kind of
image it is running in, you can use the following expression to determine
whether it is currently running in a headless image:
HeadlessImage default isHeadless
Similarly, you may send #checkHeadless from your application code when
you have code that should be executed only in a headful image:
HeadlessImage default checkHeadless
Note that you may modify the HeadlessImage>>cannotSend method to tailor
it for your specific needs.

338 VisualWorks
Delivering a Headless Application

Delivering a Headless Application


You deliver a headless application much the same way that you delivery
any other application. You separate your application into an image and a
(possibly empty) set of parcels. You use Runtime Packager to create a
“stripped” deployment image as described in Chapter 14, “Application
Delivery.”
When the Build headless image option (Set common options, Details page) is
selected in the Runtime Packager, the final runtime image is created for
headless operation. All functions of the application which might have
interacted with the GUI are suppressed when operating in a headless
mode. Limited functions are permitted for cursor operations, but most
other functions which explicitly or implicitly reference the user interface or
its components will create an error.
The class HeadlessImage, supplied in the Headless parcel, must be
present when the option is selected and must not be deleted by the
stripping process.
The Action on last window close option (Set common options, Details page) takes
on a special meaning in a headless image. When a runtime image starts,
the startup method is invoked. This provides convenient way to initiate
processing even in a headless image. After the startup method completes
its processing and answers back to Runtime Packager, the Action on last
window close is examined. Headless images do not differ from other
images in this regard. If no windows are open at this point, which will
certainly be true for a headless image, then the image is shutdown. In
some cases, including those in which there is no startup method to be
invoked, this may not be the desired function. If the image is to continue
operation after the startup method, if any, completes its processing, then
either Continue processing or Standard behavior should be selected as the
action.
The class RuntimeHeadlessExample provides a simple test case for creating
headless images. This class writes a line to file so that its execution can
be ascertained.

Application Developer’s Guide 339


16
Numbers, Dates, and Times

VisualWorks supports a variety of objects as subclasses of the Magnitude


class. The most important subclasses are those defining various kinds of
numbers, dates, and times.

Numbers
VisualWorks provides several number types, each defined in its own
class. The basic types are:
Integer
The Integer class is an abstract superclass with two subclasses:
SmallInteger and LargeInteger. LargeInteger further has subclasses
LargePositiveInteger and LargeNegativeInteger. A SmallInteger is any
integer in the range 229-1 (536,870,911) to -229, inclusive. Large
integers are limited only by available memory. The system coerces
integers into the proper subclass transparently, so you rarely need to
pay attention to this issue.
Floating Point
The Float class creates instances of single-precision floating point
numbers between plus and minus 1038, with eight or nine digits of
precision. The Double class creates double-precision floating point
numbers between plus and minus 10307, with 14 to 15 digits of
precision. A floating-point number has a decimal point, at least one
digit before the decimal, and at least one digit after the decimal.
Because of the imprecise way floating point numbers are represented
in computer memory, mathematically equivalent representations of
floating point numbers may not turn out to be equivalent in
comparisons. So, for comparing numbers, avoid Float, and consider
using instances of Fraction or FixedPoint instead.

340 VisualWorks
Numbers

Fraction
An instance of Fraction is a number with an integral numerator and
denominator, separated by a division slash, as in 3/4. Fractions are
always reduced to lowest terms.
Fixed Point
A fixed-point number (an instance of FixedPoint) is useful for business
applications in which a fixed number of decimal places is required.
Their literal representation appends the character $s to the number
(e.g., 5.2s).
Three related classes, Random, Date, and Time, are described later in this
chapter.

Creating a Number
Numbers are created either by a literal numerical expression or by an
arithmetic operation. The kind (or class) of a number resulting from an
arithmetical operation depends on the numbers involved and the
operation.
The following are literal expressions for numbers:

100 integer (appropriate Integer subclass)

5.3 floating point (Float)

5.5d double-precision floating point (Double)

3/5 fraction

99.95s fixed point (“s” for “scale”, giving the precision)

99.95s4 fixed point, giving precision explicitly

1.555e3 exponential notation

3.955d2 double precision exponential notation. VisualWorks


accepts q in place of d for compatibility with other
Smalltalk systems

16r1A radix notation: base, followed by “r”, followed by the


number expressed in the base notation.

The following are arithmetical expressions for numbers:

^3 + 8 integer

3 * 100.2 floating point

Application Developer’s Guide 341


Chapter 16 - Numbers, Dates, and Times

As shown above, fractions are a real class of object. An alternative


method for creating a fraction is to explicitly declare its numerator and
denominator:
y := Fraction
numerator: 3
denominator: 4.

Arithmetic Operations
Arithmetic operators are defined as messages for each class of number,
but each number class defines the standard operations and many more.
Use the system browser to examine the messages in the arithmetic
protocol for each number class for details:

+ addition

– subtraction

* multiplication

/ division

// division, discarding any remainder for an integer


result

\\ division, returning only the remainder

sqrt square root

** raise to a power (x ** 3) or taking the root (x**(1/3))

abs absolute value

reciprocal reciprocal value

Comparing Numbers
Numeric comparison operators are defined as messages for each class
of number, but each number class defines the standard operations and
many more. These tests all return a Boolean value:

= equality

== identity. Identity works only for SmallInteger, so in


general test for equality instead.

~= inequality

~~ non-identity

< less than

342 VisualWorks
Numbers

> greater than

<= less than or equal to

>= greater than or equal to

min: returns the smaller of two numbers

max: returns the larger of two numbers

Note that, when comparing floating point numbers (class Float), certain
comparisons may give incorrect results. For example, equality and
identity (= and == ) may fail between two representations that are
mathematically equal. This is due to the way floating points are
represented by computers, and has nothing specific to do with Smalltak
or VisualWorks. For such comparisons, consider representing these
numbers as Fraction or FixedPoint numbers instead.

Testing Numbers for Properties


Because variables have no declared type in VisualWorks, it is sometimes
necessary to test a variable that is expected to hold a number. If it does
hold a number, you can safely send arithmetic and other number
messages to it.
To test whether a variable holds a number, send it a respondsToArithmetic
message. If the object is a number, it responds true.
|x|
x := 55.
^x respondsToArithmetic
More specific tests are also available, such as isInteger and isReal.
A large variety of messages are available for testing for specific
properties of numbers:

isInteger tests for integers

isReal tests, in effect, for members of subclasses of


Number

even tests for even numbers

odd tests for odd numbers

isZero tests for zero

positive tests for zero or greater

Application Developer’s Guide 343


Chapter 16 - Numbers, Dates, and Times

strictlyPositive test for greater than zero

negative tests for less than zero

Converting Object Type


A number of type conversion messages are available. Refer to the
method definitions for details of their behavior.

asFixedPoint: returns a fixed point number with the specified


number of decimal places

asFloat returns a floating point number

asDouble returns a double-precision floating point number

asRational returns an integer or a fraction

asCharacter returns the character represented by the number

printString returns a String representation of the number

printStringRadix: returns a String representation of the number with


the specified radix (base)

Mathematical Functions
VisualWorks number classes support a large number of advanced
mathematical functions. Browse the number classes for details about
available functions.
Factoring
Three messages are defined for Integer, providing factoring operations:

gcd: greatest common denominator

lcm: least common multiple

factorial factorial

Trigonometric Functions
Trigonmetrical functions are defined to either operate on or return the
value for an angle expressed in radians.
To convert an angle expressed in degrees to radians, send the
degreesToRadians message to the number:

344 VisualWorks
Numbers

|x|
x := 45 degreesToRadians.
^x sin
Conversely, to convert a result angle expressed in radians to degrees,
send the radiansToDegrees message:
|xy|
x := 45 degreesToRadians sin.
y := x arcSin radiansToDegrees.
^y
The functions supported are:

sin sine

cos cosine

tan tangent

arcSin ArcSine

arcCos ArcCosine

arcTan ArcTangent

Logarithmic Functions
Send the following unary messages to a number to perform logarithmic
functions:

log Return the base 10 logarithm

log: base Return the logarithm for the specified base

ln Return the natural logarithm (lowercase l)

exp Return the exponential

Numeric Constants
There are three numeric constants defined in VisualWorks: zero, unity,
and pi. All three are returned by class methods for various numeric
classes.
Zero
The zero message is defined for all numeric classes, and returns the
appropriate value to ensure additive identity. The type of the zero value
varies; for example, Float returns 0.0 and Integer returns 0.

Application Developer’s Guide 345


Chapter 16 - Numbers, Dates, and Times

To get a zero of the same class as an existing number, first get the class
of that number by sending a class message to it and then send zero to the
resulting object.
|xyz|
x := Float zero.
y := Integer zero.
z := x class zero.
^x + y + z
Unity
The unity message is defined for all numeric classes, and returns the
appropriate value to ensure multiplicative identity. The type of one
returned varies; for example, Float returns 1.0 and Integer returns 1.
To get a one of the same class as an existing number, first get the class
of that number and then send unity to the resulting object.
|xyz|
x := Float unity.
y := Integer unity.
z := x class unity.
^x + y + z
Pi
The pi message is defined for Float or Double. Float returns a single-
precision version while Double returns a double-precision version.
To get a pi of the same class as an existing number, first get the class of
that number and then send pi to the resulting object.
|xyz|
x := Float pi.
y := Double pi.
z := x class pi.
^x + y + z

346 VisualWorks
Random Numbers

Random Numbers
A pseudo-random number can be generated by an instance of Random.
This object is a kind of stream, so the next message gets the next number
in the sequence.
A random stream returns a Double value, generally between 0 and 1 but
dependent on the seed value.
| randomStream x |
randomStream := Random new.
x := randomStream next.
^x
The seed: message changes the seed value, allowing you to force a
specific sequence. This message is sent to an instance of Random, and
restarts the sequence:
| randomStream x |
randomStream := Random new seed: 4.
x := randomStream next.
^x
The new message invokes the DefaultRandomGenerator, which is set to
MinimumStandardRandom by default. For backward compatibility, the
previous creation protocol and behavior is supported by FastRandom. You
can easily subclass Random to implement your own generator, and make
it the default if you wish.

Dates
Dates are supported in VisualWorks as instances of the class Date.

Creating a Date
There are a variety of messages for creating a date. Browse the class
methods defined instance creation protocol of Date for the complete list. We
will describe a few methods here.
To create a date for today’s date, send a today message to the Date class.
| date |
date := Date today.
^date
It is often useful to create a date from a string, which can be done by
sending a readFromString: message to Date. The argument is a string
containing the month, day, and year in any of several formats. The year is

Application Developer’s Guide 347


Chapter 16 - Numbers, Dates, and Times

always last. The month can be either a number (1 through 12) or the
unique first letters of the name (case is irrelevant). The month, day, and
year can be separated by a space, comma, hyphen, slash, period, or
nothing:
Date readFromString: 'January 31, 1994'
Date readFromString: '31 January 1994'
Date readFromString: '1/31/94'
Date readFromString: '1.31.1994'
Date readFromString: '1-31-1994'
Date readFromString: '31JAN94'
You can create a date by specifying the day, month and year. To specify
each by a number, send a newDay:monthNumber:year: message to the Date
class. Alternatively, specify the month by name, send a
newDay:month:year: message to Date. The month argument is the unique
first letters of a month name expressed as a Symbol:
| date1 date2 |

date1 := Date
newDay: 31
monthNumber: 1
year: 1994.

date2 := Date
newDay: 31
month: #Jan
year: 1994.

^date1 = date2
Note that if a two-digit year is specified, the year is given in the current
century, so
Date newDay: 2 month: 'jan' year: 52
Returns 1952 before the year 2000, and 2057 after 2000. To create a Date
for a year prior to 1000, use newDay:year:, for example:
Date newDay: 136 year: 52
in which the number of days is specified from the start of the year.

348 VisualWorks
Dates

Getting Information about a Day


Several messages retrieve information about a date. Browse the Date
class for a complete set of messages:

weekday returns the name of the week day as a Symbol,


such as #Friday

dayOfMonth returns the day number within the month

day returns the day number within the year

asDays returns the day number since January 1, 1901

monthName returns the month name as a Symbol, as in #January

monthIndex returns the number of the month

daysInMonth returns the number of days in the month

year returns the year number

daysInYear returns the number of days in the year

Adding and Subtracting with Dates


Doing arithmetic with dates is supported by a number of messages.
To add a number of days to a date, send an addDays: message to the
date. The argument can be a negative number:
| date daysToAdd |
date := Date today.
daysToAdd := 60.
^date addDays: daysToAdd
Similarly, you can send a subtractDays: message to the date.
To get the number of days between to dates, send a subtractDate:
message to a date with the date to be subtracted as argument:
| date1 date2 |
date1 := Date today.
date2 := Date readFromString: '31 December 1999'.
^date2 subtractDate: date1

Comparing Dates
The usual numerical comparison operations can be performed on dates:

= equality

~= inequality

Application Developer’s Guide 349


Chapter 16 - Numbers, Dates, and Times

< earlier than

<= earlier than or equal to

> later than

>= later than or equal to

Formatting a Date
A date can describe itself in a string having a variety of formats. The
printFormat: message takes as its argument an array containing six
elements. The six elements are interpreted as follows:
• Day’s position in the string (1, 2, or 3)
• Month’s position in the string (1, 2, or 3)
• Year’s position in the string (1, 2, or 3)
• The separator character
• Month’s format: 1 (numeric), 2 (abbreviation), or 3 (full name)
• Year’s format: 1 (with century) or 2 (without century)
To format a date string, send a printFormat: message to the date with a
six-element array as argument specifying the formats:
| date |
date := Date today.
^date printFormat: #(2 1 3 $- 3 1)

350 VisualWorks
Times

Times
VisualWorks provides the class Time to represent times. A Time consists
of some number of hours, minutes, and seconds, specified relative to
midnight. Time calculation is based on a microsecond clock in the virtual
machine.

Note: On windows, the default time access function, while very fast
and high-resolution, tends to drift from wall time over a period of
hours. For an application that must run for long periods, and is very
time sensitive, this can cause problems. In this situation, start
VisualWorks using the -walltime command line option (refer to
“Virtual Machine Command Line Options” for a description).

Creating a Time
There are several methods for creating instances of Time. Browse the
class methods in the Time instance creation protocol for details and the
complete set.
To create a time to represent the current time, send a now message to the
Time class:
| time |
time := Time now.
^time
You can create a time from a string representation by sending a
readFromString: message to Time. The argument is a string containing the
hours, minutes, and seconds, separated by colons. The minutes and/or
seconds can be omitted. The “am/pm” designation can be omitted (“am”
is the default) and can be in upper- or lowercase.
| times |
times := OrderedCollection new.

times
add: (Time readFromString: '3:47:26 pm');
add: (Time readFromString: '03:47');
add: (Time readFromString: '::26 PM').
^times
In computations involving times on different dates, it is sometimes useful
to represent each time as a number of seconds since midnight. At the
end of the computation, you can convert the number of seconds back into

Application Developer’s Guide 351


Chapter 16 - Numbers, Dates, and Times

an instance of Time. To convert seconds back to a time, send a


fromSeconds: message to Time. The argument is the number of seconds
that have elapsed since midnight:
| time |
time := Time fromSeconds: (60 * 60 * 4).
^time

Getting the Seconds, Minutes, and Hours


Time includes protocol for retrieving its number of seconds, minutes, and
hours individually. Send a seconds message to the time.
| time scnds mins hrs|
time := Time now.
scnds := time seconds.
mins := time minutes.
hrs := time hours

Adding and Subtracting Times


Times can be added and subtracted.
To add times, send an addTime: message to a time. To subtract times,
send a subtractTime: message to the time. The argument is either a time
or a date:
| time1 time2 |
time1 := Time readFromString: '5'.
time2 := Time readFromString: '8:51:39 am'.
^time1 addTime: time2

Creating a Time Stamp


When an application needs to record the date and time that an event
occurred, there two primary options for providing it.
• The Time class provides a timeAndDateNow method, which returns an
Array containing two elements: the current date and the present time.
• The Timestamp class provides a now method, which returns an
instance of Timestamp containing numeric representations of the day,
month, year, hour, second, and millisecond.
Timestamp has other instance creation methods, too, as well as
conversion and arithmetic methods similar to those provided for Date and
Time.

352 VisualWorks
Times

Setting the Time Zone


The virtual machine microsecond clock reports time in UTC (coordinated
universal time, formerly known as Greenwich Mean Time, GMT) on all
platforms. The Time class converts GMT to local time with the aid of
another class, TimeZone.
A TimeZone stores an offset from UTC for local time, including settings for
daylight savings time. In some parts of the world, this offset from UTC is
an integral number of hours, while in other places it is not; both kinds of
offset are handled by TimeZone.
Two instance creation messages are provided. The more general form is:
timeDifference: hours DST: amount start: startHour end: endHour
from: startDate to: endDate startDay: startDaySymbol
where:
• hours is the difference from GMT (e.g., -5 for Eastern time).
• amount is the amount of time change for Daylight Savings Time
(usually one hour).
• startHour is the hour at which the change takes effect.
• endHour is the hour at which the change ends.
• startDate is the integer number of the latest day DST starts.
• endDate is the integer number of the latest day DST ends.
• startDaySymbol is the name of the day, as a Symbol, of the week when
the change takes effect, prior to startDate and endDate.
This form is necessary in Europe where start and end times are
referenced relative to UTC, and so are an hour different. If DST starts and
ends at the same hour, as in the U.S.A., you can use the slightly shorter
form:
timeDifference: hours DST: amount at: startHour from: startDate to: endDate
startDay: startDaySymbol
where startHour is the hour DST begins and ends.
To set the time zone in VisualWorks, send a setDefaultTimeZone: message
to the TimeZone class, with a TimeZone instance:

Application Developer’s Guide 353


Chapter 16 - Numbers, Dates, and Times

TimeZone setDefaultTimeZone:
(TimeZone timeDifference: -5
DST: 1
start: 2
end: 2
from: 97 "on April 7"
to: 304 "until October 31"
startDay: #Sunday).
By default, the time zone is set for the Pacific time zone with daylight
savings time. You need to set these to appropriate values for your
location. The Time Zones page of the System Settings dialog (System ­
Settings) provides a set of sample expressions for various regions.

When properly set, the reference time zone returns the actual time zone,
and so should be used by application code that needs to know the time
zone:
timeZone := TimeZone reference.
For backwards compatibility, TimeZone keeps both a default time zone and
a reference time zone in the class variables DefaultTimeZone and
ReferenceTimeZone, respectively. There is no longer a distinction between
these.

354 VisualWorks
17
Collections

VisualWorks provides a wide variety of classes for operations involving


collections of objects. In addition to the conventional arrays, there are
bags, dictionaries, sets, linked lists, and more. Operations involving these
classes are discussed in this chapter.
The first section describes several collection classes. The variety of
collections is far richer than is covered here, however. Use a System
Browser to explore the collection classes when you need a special kind of
collection. The following decision tree provides a quick reference when
making such a choice. .

Keyed
Y N

Duplicates
Integer key allowed
Y N Y N

Dictionary Bag Set


Adds allowed
Y N

Sorted
Array
Y N

OrderedCollection
LinkedList

SortedCollection

Collection Class Decision Tree

Application Developer’s Guide 355


Chapter 17 - Collections

Iterative operations involving collections are discussed in detail in


Chapter 5, “Control Structures.” A string of characters is also a collection
and shares much of the behavior of other collections. It is discussed as a
special case in Chapter 19, “Working with Text.”

Choosing the Appropriate Class


There are nine main kinds of collections. Three of them have specialized
variations. A brief description of each collection class follows, proceeding
from the simplest to the more complex. As a rule of thumb, choose the
simplest class that suits your purpose.

Collection class Distinguishing features


Set Discards duplicate elements
Bag Tallies duplicates
Array Integer index (and fastest access)
Interval Integer elements in progression
OrderedCollection Integer index; preserves the order in which
elements are added
SortedCollection Integer index; elements are sorted by user-
defined algorithm (ascending order is
default)
LinkedList Each element points to the next element,
for maximum efficiency of dynamic lists
Dictionary Noninteger index; each element consists of
a key-value pair for dictionary-like lookups

Set
A Set is about as close to a generic collection as you can get. No index.
No sorting. It does discard duplicates, which is often useful. The fact that
an instance of Set has only one special capability should not distract you
from the fact that the generic behavior it inherits, as described in later
sections of this chapter, includes powerful mechanisms for manipulating
elements of a data set.
An IdentitySet is identical in all respects, except that it uses == for
comparisons instead of =.

356 VisualWorks
Choosing the Appropriate Class

Bag
An instance of Bag is like a Set, except that it counts the duplicate. For
each element in a Bag there is also a tally of the occurrences of that
object. If each character in the word collection were an element in a Bag,
for example, the tally for the element $c would be 2. Bag does not create a
new element for a duplicate, but increments the counter the item.

Array
Array allows you to maintain relative positions of elements, via an integer
index. In our collection example, $e can be identified by its external key,
the integer 5. (In a Set or a Bag, by contrast, the position of $e is
unpredictable.) As another example, if a customer name were to be
stored as a collection of three elements—first, middle, and last names—
it would make sense to use an Array rather than a Set because the
relative positions of the elements must be preserved.
A RunArray provides efficient storage for situations in which a value is
repeated consecutively over long stretches of an array. For example, the
font information for a block of text is a likely candidate—a roman font
would be used for many sequences of elements in the array (letters in the
text), with occasional bursts of italic, bold, etc. Although RunArray
responds to the same messages as Array, its internal representation
avoids waste by storing an element only if it differs from the preceding
element, along with a tally of that element’s repetitions.
A ByteArray provides space-efficient storage for bytes. Its elements are
restricted to the set of SmallIntegers from 0 to 255. WordArray is for
manipulating 16-bit words; its elements can be integers from 0 to 65535.

Interval
An Interval is a finite arithmetic progression, such as the series 2 4 6 8. It is
typically used to control an iterative loop, as described in Chapter 5,
“Control Structures.”

OrderedCollection
An OrderedCollection, like an Array, has an integer index and accepts any
object as an element. Unlike Array, however, an OrderedCollection permits
elements to be added and removed freely. It is frequently used as a stack
(the last element in is the first one removed) or a queue (first in, first out).
However, its uses extend farther because there are so many situations in
which ordering must be preserved as an arbitrary number of elements
are added.

Application Developer’s Guide 357


Chapter 17 - Collections

SortedCollection
When elements are not added in the desired order, sorting is required.
SortedCollection provides that extra capability. By default, elements are
sorted in ascending order. You can override this default by specifying an
alternative sort algorithm enclosed in a block. For example, the
expression:
SortedCollection sortBlock: [:x :y | x >= y]
creates a new collection whose elements will be sorted in descending
order.

LinkedList
As its name suggests, a LinkedList is a collection in which each element
points to the next element. An OrderedCollection can accomplish the same
thing, but is less efficient in circumstances involving large numbers of
additions and deletions. For example, the ProcessorScheduler class makes
use of LinkedList to track the highly dynamic list of processes. LinkedList
achieves its efficiency in a way that prohibits its elements from belonging
to other collections at the same time.

Dictionary
The Dictionary class, instead of imposing an integer index on each
element, permits any object to be the external key. The result, as in the
familiar Webster’s dictionary, is a collection of key-value pairs. For
example, an element might consist of the word ‘object’ with the associated
definition ‘something solid that can be seen or touched’. Thus, each
element in a Dictionary is typically an instance of Association, which is a
key-value pair. The nil object is specifically excluded as a valid element.
An IdentityDictionary is similar, except that it uses == for comparisons
instead of =. That is, the values in an IdentityDictionary are expected to be
literals or other unique objects that can be compared with the more
efficient identity operator ( == )

358 VisualWorks
Creating a Collection

Creating a Collection
Typically, you create an empty collection, and then add elements to it. All
collections respond to the new message, as shown here for List.
| list |
list := List new.

list add: 'Leonardo';


add: 'Michelangelo';
add: 'Donatello';
add: 'Raphael'.
^list.
Note that add: returns the new element. Consequently, you do not want to
cascade the add: messages directly from the new message, as you might
be inclined to do. Or, if you do, conclude the cascade with yourself.
For an Array, which cannot add elements, it is necessary to specify the
size of the array. Each element is nil until replaced with another object.
| array |
array := Array new: 4.

array at: 1 put: 'Leonardo';


at: 2 put: 'Michelangelo';
at: 3 put: 'Donatello';
at: 4 put: 'Raphael'.
^array.
Other collections can be created with an initial size as well.
To create a collection filled with a filler object, send a new:withAll:
message to the desired collection class:
^Array new: 16 withAll: 0.
You can also create a collection by specifying up to four elements. This
approach is typically used to create a small array. Variations of the with:
message, for up to four elements, are provided in VisualWorks:
| array |
array := Array
with: 'Leonardo'
with: 'Michelangelo'
with: 'Donatello'
with: 'Raphael'.
When an array contains only literal elements, such as numbers and
strings, you can also create the array using its literal form:

Application Developer’s Guide 359


Chapter 17 - Collections

| array1 array2 |
array1 := #( 'Leonardo' 'Michelangelo' 'Donatello' 'Raphael' ).
array2 := #( 1 2 3 4 )
Notice the use of # to indicate that a literal is being created.
Sometimes a new collection needs to be created from an existing
collection. For example, a nongrowing array might need to be expanded
to accommodate more elements. Or a dictionary’s keys might be placed
in a list for sorting.
Send a withAll: message to the desired collection class, with an
expression yielding the elements of the old collection, for example:
List withAll: Smalltalk keys

Adding Elements
Different kinds of collections add elements in different ways. Most
collections will add an element when sent an add: message with an
element to add. Arrays are the exception, since they are restricted to the
number of elements with which they are created. A Dictionary always
adds a key-value pair.
Because the elements of a Set are each unique, adding an element that
already exists in the set results in no change; duplicates are omitted. A
Bag, on the other hand, adds duplicates without limit.
By default, a List adds new elements to the end of the collection. You can
also position the additional element at the beginning of the collection,
before a particular element, or before a particular index. (A Set and a
Dictionary do not keep their elements in an externally visible order, so the
notion of inserting a new element does not apply.)

Adding an Element to a Collection


You can add an element to most collections by sending an add: message
to the collection with an object as the argument. For ordered collections,
such as List, the default is to add the object at the end of the ordering. For
classes such as Set, there is no meaningful position.

360 VisualWorks
Adding Elements

| list |
list := List new.

list add: 'Leonardo';


add: 'Michelangelo';
add: 'Donatello';
add: 'Raphael'.
^list
To add an element to a Dictionary, send an at:put: message to the
dictionary. The first argument is the lookup key (typically but not
necessarily a Symbol). The second argument is the object to be
associated with the key.
| dict |
dict := Dictionary new.

dict at: #Leader put: 'Leonardo';


at: #Member1 put: 'Michelangelo';
at: #Member2 put: 'Donatello';
at: #Member3 put: 'Raphael'.
^dict

Inserting an Element at a Specific Location


Collection classes which preserve order, such as OrderedCollection and
List, support protocol for inserting elements at specific positions.
The default position, where an object is added using the add: message, is
the end of the collection. It is sometimes helpful to make this position
explicit, in which case you can use the addLast: message.
To insert an element at the beginning of an ordered collection, send an
addFirst: message, which the new element as the argument.
To insert an element before or after a specific element already in the
collection, send an add:before: message or add:after: message to the
collection. The first argument is the element to be inserted. The second
argument is the element relative to which the insertion is to take place.
To insert an element at a numbered position, send an add:beforeIndex:
message to the collection. The first argument is the element to be
inserted. The second argument is the index of the element before which
the insertion is to take place.

Application Developer’s Guide 361


Chapter 17 - Collections

| list |
list := List new.

list add: 'Raphael';


addFirst: 'Leonardo';
add: 'Michelangelo' before: 'Raphael';
add: 'Donatello' beforeIndex: 3.
^list

Adding a Collection of Elements


When a collection is used to accumulate the contents of other collections,
additions can be made in batches by adding an entire collection. For
ordered collections, each batch can be inserted at a specific location.
To add all members of a collection to a collection, send an addAll:
message to the collection, with the collection of elements to be added as
argument. The receiving collection will determine any specific behavior.
For example, a Set will discard duplicate elements, and a List will add all
elements to the end of the list.
For an ordered collection, the addAllFirst: message inserts all members of
the argument collection at the start of the list. Similarly, the
addAll:beforeIndex: message inserts the collection before the position
specified by the second argument.
| sizes totalElements |
sizes := List new: 10000.

sizes addAll: (List allInstances collect: [ :list | list size]).


sizes addAllFirst: (Dictionary allInstances collect: [ :dict | dict size]).
sizes
addAll: (Array allInstances collect: [ :array | array size])
beforeIndex: 2.

totalElements := 0.
sizes do: [ :sz | totalElements := totalElements + sz].
^totalElements

Expanding an Array
Although an Array can contain only the number of elements with which it
was created, you can expand an array by creating a copy that has a new
element appended to it. The copy can then be substituted for the original.
To create the copy, send a copyWith: message to the Array. The argument
is the object that is to be appended to the end of the new array.

362 VisualWorks
Removing Elements

| array copy |
array := #( 1 2 3 4 5 6 7 8 9 ).

copy := array copyWith: 10.


array := copy.
^array

Removing Elements
The basic method for removing an object from a collection is to send a
remove: message to the collection, with the object to be removed as
argument:
| list |
list := List withAll: ColorValue constantNames.

list remove: #red.


^list
If the specified object is not an element in the collection, an error results.
To supply an alternative action (including doing nothing) when the object
is not found, send a remove:ifAbsent: message to the collection. The first
argument is the object to be removed. The second argument is a block
containing the action or actions. An empty block is an effective means of
taking no action, so the process can continue without an error message
or other action.
| list |
list := List withAll: ColorValue constantNames.

list remove: #brickRed


ifAbsent: [Dialog warn: 'You must be kidding -- brickRed?'].

list remove: #moonbeam


ifAbsent: [ ].
^list

Removing a Subcollection
The removeAll: message allows you to remove all memebers of one
collection from a target collection. Send removeAll: to the collection from
which you want elements removed. The argument is a collection
containing the elements to be removed.

Application Developer’s Guide 363


Chapter 17 - Collections

| list |
list := List withAll: ColorValue constantNames.

list removeAll: #( #red #green #blue ).


^list
If an element is not found, an error is reported.
Because removeAll: is defined in Collection, it can be used with any
collections as receiver and argument.

Removing an Element or Range of Elements by Index


Ordered collections provide several messages for removing a single
element at a specified position or a range of elements:

removeFirst Removes the first element in the collection.


removeFirst: Removes the number of elements specified by the
argument from the beginning of the list.
removeLast Removes the last element.
removeLast: Removes the number of elements specified by the
argument from the end of the list.
removeFrom:to: Returns an Array containing only elements
removed from the collection, from the starting index
(first argument) to the ending index (second
argument).
removeFrom:to: Same as removeFrom:to:, except that if the third
returnElements: argument is false, nil is returned. This is used for
efficiency if the array is not needed.

| list |
list := List new: 25.
1 to: 25 do: [ :i | list add: i].

list removeFirst. "Removes 1"


list removeFirst: 5. "Removes 2 3 4 5 6"
list removeLast. "Removes 25"
list removeLast: 5. "Removes 20 21 22 23 24"
list removeFrom: 8 to: 12. "Removes 14 15 16 17 18"
^list

Removing All Elements That Pass a Test


You can remove elements from any ordered collection based on a test, by
sending a removeAllSuchThat: message to the collection. The argument is
a block containing the test. The block must declare one argument variable
for the element to be tested.

364 VisualWorks
Removing Elements

| list |
list := List withAll: ColorValue constantNames.

list removeAllSuchThat: [ :name | name first == $r].


^list

Removing an Association from a Dictionary


To remove elements from a Dictionary, you remove the entire association
by sending a removeKey: message to the dictionary. The argument is the
key of the association that you want to remove. The removed value is
returned.
| dict |
dict := Dictionary new.
dict at: #Leader put: 'Leonardo';
at: #Member1 put: 'Michelangelo';
at: #Member2 put: 'Donatello';
at: #Member3 put: 'Raphael'.

dict removeKey: #Member2.

dict removeKey: #Villain ifAbsent: [ ].


^dict
If the key is not found, an error results. To provide an alternative response
to the key-not-found condition, send a removeKey:ifAbsent: message to the
dictionary, with a block that specifies the action to take if the key is not
found. An empty block causes no action, which is the same as silently
ignoring the condition.

Removing an Element from an Array


To remove occurrences of an object from an array, you create a copy of
the array, omitting each occurrence of a specified object. Send a
copyWithout: message to the Array. The argument is the object to be
removed. The copy can then be substituted for the original array.
The copyWithout: message works for all ordered collections as well as
arrays.
| array copy |
array := #( 1 8 3 4 5 6 7 8 9 ).

copy := array copyWithout: 8.


array := copy.
^array

Application Developer’s Guide 365


Chapter 17 - Collections

Replacing Elements
Replacing elements in a collection is useful when the collection has
sufficient structure so that its elements have a position. Indexed
collections, such as List and Array, have the right structure, as do keyed
collections, such as Dictionary. Unordered collections, such as a Set, do
not support replacing of elements, because there is no corresponding
notion of a location at which to make the replacement.

Replacing Individual Elements


Both keyed and indexed collections support an at:put: message for
replacing elements. For keyed collections, such as Dictionary, the first
argument is the lookup key. For indexed collections, such as List and
Array, the first argument is the index of the element to be replaced. For
both kinds of collection, the second argument is the object that is to
replace the old element.
| list dict |
dict := Dictionary new.
dict at: #Leader put: 'Leonardo';
at: #Member1 put: 'Michelangelo';
at: #Member2 put: 'Donatello';
at: #Member3 put: 'Raphael'.
list := List withAll: dict values.
list sort.

dict at: #Leader put: 'Rembrandt'.


list at: 1 put: 'Rembrandt'.

Replacing All Elements


Sequenced collections, such as List, Array, and OrderedCollection, allow you
to replace all elements with a single object by sending an atAllPut:
message to the collection. The argument is the object that is to replace all
existing elements. This is useful, for example, in reinitializing the
collection.
| list |
list := List new.
1 to: 10 do: [ :number | list add: number ].

list atAllPut: 0.
^list

366 VisualWorks
Replacing Elements

Replacing Specified Elements


Sequenced collections, such as List, Array, and OrderedCollection, allow
replacing several specified elements with a single object by sending an
atAll:put: message to the collection. The first argument is a collection
containing the index numbers of the elements to be replaced. The second
argument is the object to be placed in those slots.
| list |
list := List new.
list
add: 'red';
add: 'ghoulishGreen';
add: 'red';
add: 'blackAndBlue'.

list atAll: #( 1 3) put: 'bloodRed'.


^list

Replacing All Occurrences of an Object


Sequenced collections, such as List, Array, and OrderedCollection, allow
replacing of all occurences of a specified object with another object by
sending a replaceAll:with: message to the collection. The first argument is
the object whose occurrences you want to replace. The second argument
is the replacement object.
| list |
list := List new.
list
add: 'red';
add: 'ghoulishGreen';
add: 'red';
add: 'blackAndBlue'.

list replaceAll: 'red' with: 'bloodRed'.


^list

Replacing a Subcollection
Sequenced collections, such as List, Array, and OrderedCollection, allow
replacing an interval of objects with objects from another sequenced
collection by sending a replaceFrom:to:with:startingAt: message to the
collection. The first and second arguments are index numbers identifying
the replacement range. The with: argument is a collection containing the
new elements. The startingAt: argument is the index number in the new
collection at which to begin copying the replacement elements.

Application Developer’s Guide 367


Chapter 17 - Collections

| mainList replacements |
mainList := #( 1 2 3 4 5 6 7 8 9 ).
replacements := #( 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 ).

mainList
replaceFrom: 1
to: mainList size
with: replacements
startingAt: 7.

^mainList

Copying Elements
A collection, like any other object, can provide a copy of itself in response
to being sent a copy message. The result is a new object which is a
complete copy of the original.
| dict1 dict2 |
dict1 := Dictionary new.
dict1 at: #Leader put: 'Leonardo';
at: #Member1 put: 'Michelangelo';
at: #Member2 put: 'Donatello';
at: #Member3 put: 'Raphael'.

dict2 := dict1 copy.


You can then modify the copy without affecting the original.
Note, however, that the effect of making changes to the elements of the
collections, rather than to the collections themselves, is different for literal
and non-literal elements. Literal elements, such as numbers and strings,
can be modified in one collection without affecting the other.
For a nonliteral element, however, the collections hold the same object,
not copies. Any changes to the object in one collection are reflected in
the other as well. If you do not want this effect of the copy, you can
replace each element with a copy of itself. Since this is a change to the
collection itself, the change will not affect the copy.

Copying a Subcollection
For sequenced collections, such as List, Array, and OrderedCollection, send
a copyFrom:to: message to copy a segment of the collection. The first
argument is the starting index of the range you want to copy, and the
second argument is the ending index.

368 VisualWorks
Testing Collections

| list copy |
list := List new.
1 to: 10 do: [ :number | list add: number ].

copy := list copyFrom: 1 to: 3.


^copy

Concatenating Two Collections


Like strings, sequenced collections, such as List, Array, and
OrderedCollection, can be concatenated using the , (comma) message.
The argument is another sequenced collection. A new collection is
returned, of the same type as the first collection, containing the elements
of both collections.
| list array combinedList |
list := List withAll: ColorValue constantNames.
array := #( #bloodRed #ghoulishGreen #blackAndBlue ).

combinedList := list, array.


^combinedList

Subtracting One Set from Another


Instances of Set (and its subclasses) understand subtraction. Send a –
(minus) message to the set with another set as the argument. A similar
type of collection is returned, containing the elements that occur in the
first set but not the second.
| set1 set2 |
set1 := Set withAll: ColorValue constantNames.
set2 := set1 select: [ :name |
(name indexOfSubCollection: 'light' startingAt: 1) > 0].

^set1 - set2

Testing Collections
It is useful to be able to test collections for a variety of properties. The
following sections describe a number of useful tests. For others, browse
the collection classes.

Equality and Identity


One collection is equal (=) to another collection if it is the same type of
collection, has the same number of elements, and all of the elements are
equal.

Application Developer’s Guide 369


Chapter 17 - Collections

This example shows that a copy is equal, but a copy with one changed
element is not equal.
| list1 list2 test1 test2 |
list1 := List withAll: ColorValue constantNames.
list2 := list1 copy.

test1 := list1 = list2. "true"

list2 at: 1 put: #burntOrange.


test2 := list1 = list2. "false"
Testing for identity (==) determines whether two collections are the same
object. While this is a very fast test, it is seldom used since two distinct
collections will fail the test even if they are of the same type, have the
same number of elements, and all of their elements are the same.

Getting the Number of Elements


To get the number of elements in an collection, send a size message to
the collection. The return value is an integer.
| array |
array := ColorValue constantNames.
^array size

Getting the Capacity


Each position in which an element can be stored is known as a slot. A
collection often has more slots than elements to avoid having to expand
the collection each time a new element is added. To get the number of
slots in a collection, send a capacity message to the collection. The return
value is an integer.
| set |
set := Set withAll: ColorValue constantNames.
^set capacity

Testing for Emptiness


Frequently, it is useful to know whether a collection is empty of elements.
To test for emptiness, send an isEmpty message to the collection. The
response is true when the collection has no elements and false otherwise.
| list |
list := List allInstances.

list isEmpty
ifFalse: [^list first]

370 VisualWorks
Retrieving Elements

Testing for the Presence of an Object


Any collection will answer whether it includes a specific object in
response to the includes: message. It will answer true if it includes the
object, and false otherwise. A Dictionary will respond to the more specific
includesKey: and includesAssociation: messages.
A collection will also answer the number of instances of an object in
response to an occurrencesOf: message. The returned value is an integer,
zero if the object is not found.
| list found1 found2 |
list := List withAll: #( #red #green #blue #red #yellow #blue).

found1 := list includes: #red.


found2 := list occurrencesOf: #red.

^Array with: found1 with: found2

Retrieving Elements
Indexed and keyed collections are useful for storing objects that can then
be retrieved by index or key. The following sections describe methods for
retrieving objects from a collection.

Getting the Element at an Index


Indexed collections, such as List or Array, return the object stored at an
indexed position in response to the at: message. The argument is an
index number. If the object is not found, zero is returned.
| list |
list := List withAll: Smalltalk classNames.

^list at: 1

Finding the Index of an Object


The reverse operation, finding an index at which a known value is stored,
is sometimes useful. To find the index of an object, send a indexOf:
message to the collection. The first argument is the object whose index is
to be found.
To search a subset of a List or Array, send a nextIndexOf:from:to: message.
The second and third arguments are indexes that define the search
range. The returned index is relative to the beginning of the collection.

Application Developer’s Guide 371


Chapter 17 - Collections

To search backward from the end, send a lastIndexOf: message. The index
of the last occurrence is returned, or zero if none exists. The returned
index is relative to the beginning of the collection.
| list found1 found2 found3 |
list := List withAll: #( #red #green #blue #red #yellow #blue).

found1 := list indexOf: #red.


found2 := list nextIndexOf: #red from: 2 to: 6.
found3 := list lastIndexOf: #red.

Finding a Subcollection by Index


To find the starting index of a collection within a sequenced collection,
send an indexOfSubCollection:startingAt: message to the collection. The first
argument is the subcollection to be found, which need not be the same
type of collection. The second argument is the index number at which the
search is to begin. The returned index number is relative to the beginning
of the collection. If the subset is not found, zero is returned.
| list subset found |
list := List withAll: #( #red #green #blue #red #yellow #blue).
subset := #( #red #yellow #blue).

found := list indexOfSubCollection: subset startingAt: 1.


^found

Getting the Value at a Key


A Dictionary returns the value for a specified lookup key in response to the
at: message. The argument is the key.
By default, an error results if the key does not exist. To specify an
alternative action, send the send an at:ifAbsent: message. The second
argument is a block containing actions to be taken if the key does not
exist.
| dict found1 found2 |
dict := Smalltalk.

found1 := dict at: #List.


found2 := dict at: #UnlikelyClassName ifAbsent: [nil].

^Array with: found1 with: found2


The reverse operation, finding a key at which a known value is stored, is
sometimes useful. To find the index of an object, send a keyAtValue:
message to the collection. The first argument is the object whose index is
to be found.

372 VisualWorks
Retrieving Elements

Retrieving an Object by Relative Position


A sequenced collection can find the element that is either before or after
a specified object, as well as the first and last objects in a collection,
using these methods.

before: Returns the object before the specified object. An


error occurs if the reference element is the first
element.
after: Returns the object after the specified object. An
error occurs if the only occurrence of the reference
element is the last element.
first Returns the first object in the collection.
last Returns the last object in the collection.

| list first last found1 found2 |


list := List withAll: #( #red #green #blue #red #yellow #blue).

first := list first.


last := list last.
found1 := list before: #blue.
found2 := list after: #yellow.

Finding Elements That Pass or Fail a Test


Three methods are available for generating a collection based on
success or failure of a test condition. The result is a collection containing
just those elements that satisfy the test. The test is a block declaring one
argument which takes elements from the source collection for testing.

select: Returns a collection of elements that pass the test.


reject: Returns a collection of elements that fail the test.
detect:ifNone: Returns the first element that passes the test. The
second argument is a no-argument block
containing the action to perform if no element
passes the test.

Application Developer’s Guide 373


Chapter 17 - Collections

| list found1 found2 found3 |


list := List withAll: Smalltalk classNames.

"Select classes with 'Example' in their names."


found1 := list
select: [ :nextElement |
(nextElement indexOfSubCollection: 'Example'
startingAt: 1) > 0].

"Reject classes with 'Example' in their names."


found2 := list
reject: [ :nextElement |
(nextElement indexOfSubCollection: 'Example'
startingAt: 1) > 0].

"Detect the first class beginning with 'R'."


found3 := list
detect: [ :nextElement | nextElement first == $R]
ifNone: [0].

^Array with: found1 with: found2 with: found3

Sorting a Collection
Sorted collections can rearrange themselves either in ascending order or
according to a specified sort criterion. A List has a simplified form of the
sorting messages.
The sort messages assume that the elements respond to < and =
messages, which are used to compare elements during the sorting.
Sort criteria are specified in a block containing the test for determining
whether one element comes before another. The block is given two
elements to compare, and is expected to answer true when the first
element should precede the second element.
Arbitrary collections are sorted by first being converted to an instance of
SortedCollection.

asSortedCollection Returns a new collection as an instance of


SortedCollection, with the collection’s elements in
ascending order.
asSortedCollection: Returns a new collection as an instance of
SortedCollection, with the collection’s elements
sorted according to the specified sort criteria.

374 VisualWorks
Converting Collection Types

sort Defined for List, and returns a list with the


elements sorted into ascending order.
sortWith: Defined for List, and returns a new list with the
elements sorted according to the specified sort
criteria.
reverse Returns a new collection of the same kind, but
with the elements in reversed order.

| array1 sort1 array2 sort2 |

array1 := #( 'Leonardo' 'Michelangelo' 'Donatello' 'Raphael').


sort1 := array1 asSortedCollection.

array2 := #( 'Leonardo' 'Michelangelo' 'Donatello' 'Raphael').


sort2 := array2 asSortedCollection: [ :name1 :name2 | name1 > name2].

^Array with: sort1 with: sort2.

Converting Collection Types


The Collection class defines several methods for creating a specific kind of
collection from any other kind of collection. The result is a new collection
of the specified kind. The original collection remains unchanged. Since
these conversion methods are defined in Collection, they work for all
collection types.
| array list |
array := ColorValue constantNames.

list := array asList.


^list.
When converting an unordered collection, such as a Set or Dictionary, to
an ordered collection, an order is imposed. One practical implication of
this is that a later conversion of the same collection may return a
collection with the elements in a different order, making it unequal to the
first conversion.
When a Dictionary is converted, its keys are ignored and the new
collection contains only its values.

Application Developer’s Guide 375


Chapter 17 - Collections

The following are a few of the conversion methods. Browse the Collection
class converting protocol for additional methods.

asArray returns an Array


asBag returns a Bag
asList returns a List
asOrderedCollection returns an OrderedCollection
asSet returns a Set

Looping through the Elements (Iterating)


It is common for an application to perform a set of actions for each
element in a collection. For example, a sales processing application might
want to generate a packing slip for each element in a list of sales orders.
To create a loop that repeats a series of steps for each element in a
collection, send a do: message to a collection. The argument is a block
that performs a series of operations on an element. The block declares
one argument variable to hold the element being processed.
| list color |
list := List withAll: ColorValue constantNames.
list sort.

list do: [ :colorName |


Transcript show: colorName asString; cr.
color := ColorValue perform: colorName.
Transcript
show: color red printString;
tab;
show: color green printString;
tab;
show: color blue printString;
cr; cr].
Occasionally the elements in a collection need to be processed in reverse
order, starting with the final element and proceeding toward the first
element. To do this, use the reverseDo: message instead of do:

Looping by Index or Key


For indexed collections (such as List and Array) and keyed collections
(Dictionary), it is common to loop on the index or key instead of the values.
This is especially useful with dictionaries, whose values are sometimes
meaningless without the associated keys.

376 VisualWorks
Looping through the Elements (Iterating)

To loop on the index or key, send a keysDo: message to the collection. The
argument is a block that performs a series of operations on each
element. The block is expected to declare one argument variable to hold
the element to be processed.
To loop on the collection and process using both the key or index and the
value, send a keysAndValuesDo: message to the collection. The argument
is a two-argument block that performs a series of operations on the key
and associated value for each element.
| dict randomGenerator gc randomX randomY colorValue |
randomGenerator := Random new.
gc := (ExamplesBrowser prepareScratchWindowOfSize: 300@400)
graphicsContext.

dict := Dictionary new.


ColorValue constantNames do: [ :colorName |
colorValue := ColorValue perform: colorName.
dict at: colorName put: colorValue].

dict keysDo: [ :colorName |


randomX := randomGenerator next * 300.
randomY := randomGenerator next * 300.
colorName displayOn: gc at: (randomX @ randomY)].

dict keysAndValuesDo: [ :colorName :color |


randomX := randomGenerator next * 300.
randomY := randomGenerator next * 300.
gc paint: color.
colorName displayOn: gc at: (randomX @ randomY)].

Collecting the Results of the Processing


Frequently the results of iterating on a collection create related objects
that need to be collected in a new collection. The collect: message is a
shorthand way of doing this. The effect is the same as iterating with do:
and explicitly creating the new collection.
| list capitalizedName initial |
list := List withAll: ColorValue constantNames.
list sort.

list collect: [ :colorName |


capitalizedName := colorName asString.
initial := (capitalizedName at: 1) asUppercase.
capitalizedName at: 1 put: initial.
capitalizedName].

Application Developer’s Guide 377


Chapter 17 - Collections

Looping through Two Parallel Collections


Often two collections need to be processed in tandem. The with:do
message passes corresponding elements from two ordered collections
into a two-argument block. The first argument is a second ordered
collection. The second argument is a two-argument block that performs a
series of operations on a pair of elements, one from each of the two
collections. (The example creates key-value pairs for a dictionary, taking
the keys from one array and the associated values from a second array.)
| array1 array2 dict |
array1 := #( #Leader #Member1 #Member2 #Member3).
array2 := #( 'Leonardo' 'Michelangelo' 'Donatello' 'Raphael' ).
dict := Dictionary new.

array1 with: array2 do: [ :array1Element :array2Element |


dict at: array1Element put: array2Element].

^dict

378 VisualWorks
18
Working With Graphics and Colors

Graphics are used primarily to provide illustrative feedback to the user, or


simply to make the user interface more visually appealing. Graphics are
also the foundation of a graphical user interface. Graphics are used
extensively in GUI widgets.
This section discusses how to create and display graphical objects in
VisualWorks.

Examples Browser
Many examples in this chapter use the Examples Browser, which is
provided in the ExamplesBrowser parcel in the examples/ directory. It is
also loaded with the Help system.

The VisualWorks Graphics Environment


VisualWorks provides a rich framework for creating graphical objects and
displaying them in the UI of an application.
Graphics are typically composite objects consisting of geometric shapes,
images, and colors that interact with a display object. Control over the
appearance of a graphic is often shared by the graphic itself and its
display surface.

Coordinate System
Graphics in VisualWorks are represented as points in a two-dimensional
rectangular coordinate system, with x coordinates increasing from left to
right on the graphic plane and y coordinates increasing from top to
bottom. Numbering starts from zero.

Application Developer’s Guide 379


Chapter 18 - Working With Graphics and Colors

0 1 2 3 4
x
1
2
3

y
Coordinate system

All graphic operations accept nonintegral coordinates, but such


coordinates are rounded to the nearest integer. Coordinate values must
be in the range from -32768 through 32767.
Graphic objects are typically displayed in a window, and the window can
be moved around by the user. For that reason, the origin of the window is
used in most graphic operations rather than the origin of the screen. If the
window has subviews, each subview maintains its own origin, and graphic
operations use that origin. As a result, you rarely need to be concerned
with translating coordinates when a window is moved or resized.
Some windowing systems (such as the Macintosh’s) place pixels
between grid points, as shown in the above figure, while other window
systems (X and MS-Windows) place pixels on grid points. VisualWorks
takes its lead from the window manager. This difference rarely matters,
but it can cause a one-pixel misalignment in some circumstances, and a
“difference of opinion” about whether the border of an object such as a
polygon is to be repainted when that object is filled.

Points
An x-y coordinate pair is represented as an instance of Point. The
@ message creates a Point, as in this example which creates a point with
an x-value of 100 and a y-value of 250. The spaces before and after the
binary selector (@) are optional:
100 @ 250
You can also create a point by specifying polar coordinates. The following
example creates a Point whose coordinates lie on a circle of radius 100 at
45 degrees:
Point r: 100 theta: 45 degreesToRadians
Two constants are available: Point zero returns 0@0, and Point unity returns
1@1.

380 VisualWorks
The VisualWorks Graphics Environment

A Point can perform comparison and arithmetic functions. So, you can
test for equality, and for less than and greater than relations. You can add
two points, and add (or subtract) a scalar value to a Point, to increase
(decrease) both x and y by scalar amount. For other operations, browse
the Point class using the System Browser.

Graphical Objects
Graphical objects are drawn and positioned by specifying points in the
coordinate system. VisualWorks uses several generic geometric shapes
as well as bitmap images for displaying graphics.
The graphic objects supported by the imaging model are texts, lines,
polylines, splines, Bezier curves, arcs, circles, rectangles and graphical
images. Display surfaces themselves are graphic objects, so you can
display one window in another.
Text Objects
Texts are treated as graphical objects in many contexts, particularly
instances of Text and ComposedText. These are described in detail in
Chapter 19, “Working with Text.”
Lines, Polygons, and Curves
A line segment connects two points, named start and end. A polyline
connects three or more points (its collection of vertices) as a series of line
segments, and is closed between the start and end points. A polygon is a
polyline that is filled rather than stroked. (A point is inside the polyline if
an infinite ray originating from the point crosses the polyline an odd
number of times. If the polyline is not closed, it is implicitly closed before
the even/odd rule is applied.) Rectangles are treated specially.
An arc is a curved line defined by three elements of information:
• The smallest rectangle that can contain the ellipse of which the arc is
a segment (adjusted for line width).
• The angle at which the arc begins, measured in degrees clockwise
from the 3 o’clock position (or counterclockwise for negative values).
• The angle traversed by the arc, known as the sweep angle. The
sweep angle is measured from the starting angle (not necessarily the
3 o’clock position) and proceeds clockwise for positive values and
counterclockwise for negative values.

Application Developer’s Guide 381


Chapter 18 - Working With Graphics and Colors

Bitmap Images
An Image is a graphic object composed of a rectangular array of pixels.
Image employs a bitmap to represent its pixel colors or coverages.
A very simple Image can be constructed by manipulating the bits in the
map directly, but this is unwieldy for complicated pictures. More typically,
a scanner or a drawing tool is used to create the desired arrangement of
pixels. An Image is then captured from the on-screen representation or
from the bitmap.
An Image is stored in Smalltalk memory, so it is saved with the Smalltalk
image. For that reason, a graphical image can be used as a storage
device for display objects like Pixmaps and Masks.
An Image can be either color-based or coverage-based, depending on its
palette.
Common uses of images in an application are for cursors and icons.

Display Surfaces
Graphic operations in Smalltalk display graphics on two-dimensional
graphic media. All current graphic media are subclasses of the abstract
class DisplaySurface, which represents host graphic media related to video
display screens.
There are three types of display surface: Window, Pixmap, and Mask. While
a Window is used to display graphic objects on-screen, Pixmaps and Masks
are used for manipulating graphics. All three types of display surface
employ a GraphicsContext as an intermediary between the surface and the
objects to be displayed.
Graphics Context
Every display surface uses an instance of GraphicsContext to manage
graphic parameters such as line width, tiling phase, and default font.
Displaying operations are performed not by the display surface directly,
but by its GraphicsContext.
Similarly, messages for modifying graphic parameters such as line width
must be addressed to the appropriate GraphicsContext. That object applies
the relevant parameters and then displays the object on the surface.
A display surface does not store a graphics context, so it cannot be
accessed by an accessor. Instead, you need to get a display surface’s
graphic context any time a change is made. To get the graphics context,
send the message graphicsContext to the display surface. This is done
repeatedly in the examples.

382 VisualWorks
The VisualWorks Graphics Environment

Since many unrelated graphic operations can modify a display surface’s


graphic context, each graphic operation is responsible for setting up its
own graphic context. For this reason, you should never store a
GraphicsContext in an instance variable or a class variable, or if you must
assign it to a variable, use a temporary variable so the changes remain
local within a method.
Windows
A VisualWorks Window corresponds to the window supplied by the host
platform’s window manager. It is a Macintosh window on the Macintosh,
an X window on machines running X, and so on. For that reason, a
Window’s border decorations and label bar take on the host window
manager’s look and feel.
ScheduledWindow, a subclass of Window, has a controller that permits the
user to move, resize and close the window. To create a ScheduledWindow
and then open it on the screen, execute the following:
ScheduledWindow new open.
By itself, a ScheduledWindow is not very useful. Try opening one and typing
characters into it—as you will see, it does not provide application
capabilities such as text editing. (To close the window, select close in its
<Window> menu.) A ScheduledWindow handles the details of window
resizing, raising and lowering, etc. It holds onto a VisualComponent, which
is frequently a View. The view itself may contain subviews, and so on.
Thus, ScheduledWindow is commonly described as being at the top of the
view hierarchy.
Pixmaps
A Pixmap is the off-screen equivalent of a window. It is a rectangular
surface, capable of storing an encoded color at each pixel location just as
a window does. Unlike a window, a Pixmap retains its contents until they
are explicitly overwritten. For this reason, a Pixmap is said to be a retained
medium.
Masks
A Mask is used to trim unwanted parts of a picture. The mask can take
any shape, such as a circle, a rectangle, or an irregular polygon.
Advanced graphic effects can be created by merging images using
masks.
For example, Cursor employs a mask to trim away “white” portions of the
rectangular image, leaving only the desired shape (such as an arrow, or
cross-hairs). Without a mask, the cursor would obscure a rectangular
region of the display no matter what shape the cursor image was.

Application Developer’s Guide 383


Chapter 18 - Working With Graphics and Colors

Cursors
The displayed cursor is controlled by class Cursor. Use this class to create
a new cursor from an image and a mask, and to substitute cursors.
Cursors can be bitmaps up to 32x32 bits (except of Mac OS 9.x which is
restricted to 16x16).
For example, the example class CursorExample defines images to create a
town crier cursor. To create the cursor, evaluate:
townCrier := Cursor image: CursorExample townCrierForCursor
mask: CursorExample townCrier
hotSpot: 1@1 name: #myCursor.
To get the current cursor, send a currentCursor message to Cursor. To set
the cursor, send currentCursor: with the cursor as argument. Typically you
change the cursor while some process is occuring, and then set it back:
Cursor currentCursor: newCursor.
(Delay forSeconds: 10 ) wait.
Cursor currentCursor: heldCursor
For a better example, see CustomViewExample.
On Windows platforms, the VM substitutes platform cursors for the origin,
top left, bottom right, corner, execute, and wait cursors if not others, even
if your application cursors are more appropriate. You can turn off this
substituion by evaluating:
ScheduledControllers useHostCursors: false.
To restore substitution of host cursors, set this to true.

Colors and Patterns


VisualWorks uses Colors and Patterns to draw lines and fill shapes.

Displaying a Geometric Object


There are two ways of displaying geometric objects in VisualWorks. The
approach you select depends on whether the shape is displayed once,
without needing to do any other operations on it, or whether the shape
needs to be operated on and displayed or refreshed repeatedly.

Drawing a Transient Shape


To draw a shape only once, GraphicsContext supports a variety of
messages, one for each kind of shape. For instance, to draw a line, you
can send the displayLineFrom:to: message to a graphics context. Similar

384 VisualWorks
Displaying a Geometric Object

messages are available for other shapes, such as arcs, polygons, and
rectangles. Browse the GraphicsContext class displaying protocol instance
methods to see the complete set.
| gc |
gc := ([Link]
prepareScratchWindow) graphicsContext.

5 to: 400 by: 5 do: [ :i |


gc displayLineFrom: 0@i to: i@400].
No geometric object is actually created by these messages, so no
transformations or other operations can be performed.

Drawing a Geometric Object


Geometric objects in VisualWorks, such as Bezier curves, circles,
polygons, lines, and rectangles, support a pair of messages for directly
displaying themselves on display surfaces: displayFilledOn: and
displayStrokedOn:. These messages provide a convenient method for
displaying graphical objects, as long as the object is displayed either
always filled or always stroked (as an outline).
A more flexible mechanism is to display such objects using a wrapper
object, either in a StrokingWrapper or in a FillingWrapper object, to
determine the drawing style. Both wrapper objects use a single message
to display themselves: displayOn:. Using the wrapper technique allows
VisualWorks to provide a uniform display interface for all geometric
objects.
The choice of Wrapping object depends on whether the drawing should
be a line drawing (StrokingWrapper), or should be filled with a color or
pattern (FillingWrapper). Some objects, such as lines, cannot be wrapped
in a filling wrapper since that would clearly be inappropriate.
To display any geometric object, create the object and perform any
needed transformations on it. Then create a wrapper for the geometric
object by sending the message asStroker or asFiller to the geometric
object. To display the wrapper object, send the displayOn: message to it,
with the target graphic context as its argument.
For example, the following expression creates a line, performs some
operations on it, wraps the line, and displays it in an examples browser:

Application Developer’s Guide 385


Chapter 18 - Working With Graphics and Colors

| gc line scaleFactor |
gc := ([Link]
prepareScratchWindow) graphicsContext.
scaleFactor := 10@1.

5 to: 400 by: 5 do: [ :i |


line := LineSegment from: 0@i to: i@400.
line := line scaledBy: scaleFactor.
line asStroker displayOn: gc].
When displaying a filled object, you must also specify the color for the
filler:
| gc rect1 rect2 border |
gc := ([Link]
prepareScratchWindow) graphicsContext.

"Black rectangle"
rect1 := 100@100 extent: 200@200.
rect1 asFiller displayOn: gc.

"Gray rectangle"
border := 3.
rect2 := (rect1 origin + border) corner: (rect1 corner - border).
rect2 asFiller displayOn: (gc paint: ColorValue gray).

Shifting Graphics
Sometimes it’s convenient to draw a polyline, polygon or rectangle as if its
origin point were at 0@0, then position the object elsewhere relative to
the display surface. For that reason, a variant of the displaying messages
allows you to specify the point at which the object’s origin is to be
positioned.
The variants are as follows:
gc displayPolyline: pointCollection at: aPoint
gc displayRectangle: aRectangle at: aPoint
gc displayRectangularBorder: aRectangle at: aPoint
gc displayPolygon: pointCollection at: aPoint

386 VisualWorks
Displaying a Geometric Object

Integrating Graphics into an Application

Online example: CustomViewExample, Sketch and SketchView


Displaying graphic objects directly onto a window, as is done in most of
the examples in this chapter, is useful for testing. You will have noticed,
however, that the image is damaged if you move another window over the
graphic window. In a live application you need the window to redraw itself
when this kind of damage occurs.
The VisualWorks graphics framework includes a damage repair
mechanism that sends a displayOn: message to a view whenever its
containing window perceives that the view’s display has been damaged.
Using this mechanism in an application is quite simple.
For dynamic graphics that change when the model changes, the
application model triggers the displaying method whenever necessary. A
view gets display requests from two sources: the window-repair
mechanism and the application. Requests of the first kind happen
automatically; you arrange for the second in your application, as shown in
the basic steps.
The SketchView example updates its display when any of three changes
occur in its model (a Sketch): a point is added to the current stroke, the
sketch is erased, or a new sketch is selected in the list of sketches. Each
of these three events demonstrates a variant in the basic mechanism for
keeping the displayed graphic up to date.
1 Create a displayOn: method in the view that is responsible for
displaying the graphic (SketchView).
This method is responsible for creating and displaying the graphic
objects based on data from the model.

Application Developer’s Guide 387


Chapter 18 - Working With Graphics and Colors

displayOn: aGraphicsContext
self model isNil ifTrue: [^self].

self model strokes do: [ :stroke |


aGraphicsContext displayPolyline: stroke].
2 In any method in the domain model that changes the graphic, send a
changed:with: message to self.
The first argument is a symbol identifying the nature of the change
(#stroke, which is used by update:with: to determine the update action).
The second argument is a data or control parameter needed by the
view to display the graphic (in the example, a line segment is sent).
add: aPoint
"Add aPoint to the current stroke."

self strokes last add: aPoint.


self changed: #stroke with: self currentLineSegment.
The changed:with: message sends update:with: to all dependents of the
model, with the same parameters. The view is the primary and often
the only dependent.
If the change in the model is such that the view needs no data or
control parameter, use nil as the second argument in the changed:with:
message. In the example, Sketch specifies #erase as the first
argument in the changed:with: message and nil as the second
argument to erase the sketch, because the view must display the new
sketch entirely.
eraseAll
"Erase my contents."

self strokes removeAll: self strokes copy.


self changed: #erase with: nil.
3 In the view, create an update:with: method.
This method is invoked by the model whenever it changes. The
message is responsible for updating its display based on the aspect
of the model that changed. In the example, it displays a new line
when the #stroke aspect is changed. When the #erased aspect is
indicated, the update:with: method sends invalidate to the view. This
inherited method causes a displayOn: message to be sent to the view
with the appropriate graphics context.

388 VisualWorks
Displaying a Geometric Object

update: anAspect with: anObject


"When a point is added to the model..."
anAspect == #stroke
ifTrue: [anObject asStroker displayOn: self graphicsContext].

"When the model erases its contents..."


anAspect == #erase
ifTrue: [self invalidate].
4 Create a model: method for changing the model object.
When an entirely new model is given to the view using its model:
method, the view sends invalidate to itself, again causing a displayOn:
message to be sent to the view with the appropriate graphics context.
Because model: overrides an inherited method with that name, begin
the method by invoking the inherited version by sending a model:
message to super.
model: aModel
super model: aModel.
self invalidate.

"Tell the controller where to send menu messages."


self controller performer: aModel.

Storing Graphic Attributes


It is frequently necessary to store color information with the graphic
object. To do this, wrap the geometric object in a
GraphicsAttributesWrapper.
1 Wrap the geometric object in a stroking or filling wrapper by sending
asStroker or asFiller to it.
2 Wrap the stroking or filling wrapper in a GraphicsAttributesWrapper by
sending an on: message to that class, with the wrapper from the
basic step as the argument.
3 Create a new GraphicsAttributes and send a paint: message to it. The
argument is a color or pattern.
4 Install the graphics attributes in the GraphicsAttributesWrapper by
sending an attributes: message with the attributes as the argument.
5 Display the graphics attributes wrapper by sending a displayOn:at:
message to it. The first argument is the graphics context of the
display surface. The second argument is the origin point at which the
geometric object is to be displayed.

Application Developer’s Guide 389


Chapter 18 - Working With Graphics and Colors

| gc circle wrapper1 wrapper2 random pt attributes1 attributes2 |


gc := ([Link]
prepareScratchWindow) graphicsContext.
circle := Circle center: 0@0 radius: 50.

wrapper1 := GraphicsAttributesWrapper on: circle asFiller.


attributes1 := GraphicsAttributes new paint: ColorValue red.
wrapper1 attributes: attributes1.
wrapper2 := GraphicsAttributesWrapper on: circle asFiller.
attributes2 := GraphicsAttributes new paint: ColorValue blue.
wrapper2 attributes: attributes2.

random := Random new.


100 timesRepeat: [
pt := random next * 300 + 50 @ (random next * 300 + 50).
wrapper1 displayOn: gc at: pt.
pt := random next * 300 + 50 @ (random next * 300 + 50).
wrapper2 displayOn: gc at: pt]

Drawing Objects
A line segment connects two points, named start and end. A polyline
connects three or more points (its collection of vertices) as a series of line
segments, and is closed between the start and end points. A polygon is a
polyline that is filled rather than stroked. (A point is inside the polyline if
an infinite ray originating from the point crosses the polyline an odd
number of times. If the polyline is not closed, it is implicitly closed before
the even/odd rule is applied.) Rectangles are treated specially.

390 VisualWorks
Drawing Objects

Lines

You can draw a straight line directly on a display surface, by sending a


displayLineFrom:to: message to the graphics context. The first argument is
the starting point of the line and the second argument is the endpoint.
| gc |
gc := ([Link]
prepareScratchWindow) graphicsContext.

5 to: 400 by: 5 do: [ :i |


gc displayLineFrom: 0@i to: i@400].
It is often useful to create a line as an object separately from its display.
To do this, create an instance of LineSegment and display it. Creating a
LineSegment is allows your application to perform operations on the line,
such as determining its length or scaling the line.
To create a line segment, send a from:to: message to the LineSegment
class. The first argument is the starting point of the line and the second
argument is the endpoint. You can perform various operations on the
resulting LineSegment.

Application Developer’s Guide 391


Chapter 18 - Working With Graphics and Colors

| gc line scaleFactor |
gc := ([Link]
prepareScratchWindow) graphicsContext.
scaleFactor := 10@1.

5 to: 400 by: 5 do: [ :i |


line := LineSegment from: 0@i to: i@400.
line := line scaledBy: scaleFactor.
line asStroker displayOn: gc].

Polylines and Polygons


A jointed line, or polyline, can also be drawn directly or instantiated as a
Polyline. A polygon is a filled Polyline.
To display a (stroked) polyline directly, send a displayPolyline: message to
the graphics context. To display a (filled) polygon directly, send a
displayPolygon: message to the graphics context of the display surface.
The argument in both cases is a collection of points, each point
representing one vertex of the polyline or polygon.
To create and display a polyline object, create a Polyline by sending a
vertices: message to the Polyline class, with a collection of points (vertices)
as the argument. Then wrap the polyline in a stroking wrapper (using
asStroker) and display it on the graphics context (using displayOn:).
| gc points x y radians polyline |
gc := ([Link]
prepareScratchWindow) graphicsContext.
points := OrderedCollection new.
0 to: 360 by: 30 do: [ :angle |
radians := angle degreesToRadians.
x := 200 - (200 * radians cos).
y := 200 - (200 * radians sin).
points add: x@y].
gc displayPolyline: points.

polyline := Polyline vertices: points.


0.9 to: 0.1 by: -0.1 do: [ :scale |
polyline := polyline scaledBy: scale.
polyline asStroker displayOn: gc].
To create and display a polygon object, you again create an instance of
Polyline by sending a vertices: message to the Polyline class, with the
vertex points as the argument. But to fill the polyline, making a polygon,
wrap the polyline in a filling wrapper (using asFiller) and display the
wrapped polygon by sending displayOn: to the wrapper with the graphics
context as argument.

392 VisualWorks
Drawing Objects

A variant of displayOn: is used in the following example to specify the


origin, the upper-left corner of the rectangle containing the polygon.
| gc points x y radians polyline origin |
gc := ([Link]
prepareScratchWindow) graphicsContext.
points := OrderedCollection new.
0 to: 360 by: 30 do: [ :angle |
radians := angle degreesToRadians.
x := 200 - (200 * radians cos).
y := 200 - (200 * radians sin).
points add: x@y].

polyline := Polyline vertices: points.


origin := 0@0.
polyline asFiller displayOn: gc at: origin.

Rectangles
Rectangles are used in a variety of graphic operations, from setting the
size of a window to specifying the bounding box of an ellipse, as well as
simply to create a rectangular graphic.
Creating a Rectangle
There are several ways to create a rectangle, accommodating a variety of
contexts. The most common methods are to send an extent: or corner:
message to an origin (top left) point. Both of the following expressions
create a rectangle 100 pixels wide, 250 pixels high, with its origin at
50@50:
50@50 extent: 100@250
50@50 corner: 150@300
The extent: message specifies the rectangle by its size, setting the x and y
distance from the starting point. The corner: message, on the other hand,
specifies the absolute corner position.

Application Developer’s Guide 393


Chapter 18 - Working With Graphics and Colors

100
50@50

250

150@300

Creating a rectangle

When it is inconvenient to assemble the coordinates into Points, you can


also create a Rectangle from the component x- and y-values:
Rectangle left: 50 right: 300 top: 50 bottom: 150
And when your application prefers not to distinguish between the origin
and the corner point, you can let Rectangle do the comparison and create
an instance:
Rectangle vertex: 300@150 vertex: 50@50
A suite of creation messages supports user-defined placement and sizing
of rectangles, with or without grids.
Getting a Rectangle’s Dimensions
Once created, a Rectangle can tell you a number of things about its
dimensions and its contents.

origin width area


corner height contains: aRectangle
left leftCenter containsPoint: aPoint
right rightCenter intersects: aRectangle
top topCenter
bottom bottomCenter

394 VisualWorks
Drawing Objects

Scaling and Transforming Rectangles


Rectangles also handle a variety of scaling, merging and translating
tasks.

scaledBy: aScalarOrPoint
expandedBy: aScalarPointOrRectangle
insetBy: aScalarPointOrRectangle
insetOriginBy: point1 cornerBy: point2
intersect: aRectangle
merge: aRectangle
areasOutside: aRectangle
moveBy: aPoint
moveTo: aPoint
translatedBy: aScalarOrPoint
align: point1 with: point2
amountToTranslateWithin: aRectangle

Arcs, Ellipses, and Circles

An arc is a curved line defined by three elements of information:

Application Developer’s Guide 395


Chapter 18 - Working With Graphics and Colors

• The smallest rectangle that can contain the ellipse of which the arc is
a segment (adjusted for line width).
• The angle at which the arc begins, measured in degrees clockwise
from the 3 o’clock position (or counterclockwise for negative values).
• The angle traversed by the arc, known as the sweep angle. The
sweep angle is measured from the starting angle (not necessarily the
3 o’clock position) and proceeds clockwise for positive values and
counterclockwise for negative values.
A complete ellipse is an arc with a sweep angle of 360 degrees. A circle
is an arc with a square bounding box and a sweep angle of 360 degrees.
A wedge is a filled arc (or circle or ellipse). If the arc does not describe a
closed ellipse, the ends of the arc are connected to the center of the
ellipse to define the filled region. The common case of a filled circle is
referred to as a dot, and is defined by a diameter.
Drawing Circles
A circle is created by specifying its center point and radius.
| gc circle |
gc := ([Link]
prepareScratchWindow) graphicsContext.

"Blue filled circle"


circle := Circle center: 200@200 radius: 100.
circle asFiller displayOn: (gc paint: ColorValue blue).

"Black stroked circle"


gc paint: ColorValue black; lineWidth: 2.
circle asStroker displayOn: gc.
Graphics contexts understand a displayDotOfDiameter:at: message, which
displays a filled circle with the specified diameter and center point. This
can be used, for example, to display points, which aren’t otherwise
displayable objects:

396 VisualWorks
Drawing Objects

| gc random points |
gc := ([Link]
prepareScratchWindow) graphicsContext.
random := Random new.
points := OrderedCollection new.

"Create 1000 random points in a 100-pixel square."


1000 timesRepeat: [
points add: ((random next * 100) @ (random next * 100))].

"Display each random point."


points do: [ :pt |
gc displayDotOfDiameter: 2 at: pt]
Drawing Ellipses
An ellipse is created by specifying the rectangle that encloses it, as well
as the beginning angle and the number of degrees traversed (the sweep
angle) from that starting angle. For a complete ellipse, the angles are 0
and 360.
For a stroked ellipse, send a displayArcBoundedBy:startAngle:sweepAngle:
message to the graphics context. The first argument is the rectangle that
encloses the ellipse. The second argument is 0 and the third argument is
360.
For a filled ellipse, send a displayWedgeBoundedBy:startAngle:sweepAngle:
message to the graphics context, with the same arguments as above.
To use the wrapper approach, create an instance of EllipticalArc by
sending a boundingBox:startAngle:sweepAngle: message to that class. The
arguments are the same as above. Then wrap the ellipse in a stroking or
filling wrapper and display it on the graphics context.

Application Developer’s Guide 397


Chapter 18 - Working With Graphics and Colors

| gc ellipse |
gc := ([Link]
prepareScratchWindow) graphicsContext.

"Black stroked ellipse"


gc displayArcBoundedBy: (150@100 extent: 100@200)
startAngle: 0
sweepAngle: 360.

"Black filled ellipse"


gc displayWedgeBoundedBy: (160@110 extent: 80@180)
startAngle: 0
sweepAngle: 360.

"Red ellipse"
ellipse := EllipticalArc
boundingBox: (150@175 extent: 100@50)
startAngle: 0
sweepAngle: 360.
ellipse asFiller displayOn: (gc paint: ColorValue red)
Drawing Arcs
An arc is created in the same way as a full ellipse, except that the
beginning and sweep angles specify only a portion of the full 360
degrees.
Use the same technique as for displaying a full ellipse, but the startAngle
argument is the angle at which the arc or wedge begins, measured in
degrees clockwise from the 3 o’clock position. The sweepAngle argument
is the number of degrees spanned by the arc, measured clockwise from
the starting angle.
| gc arc box |
gc := ([Link]
prepareScratchWindow) graphicsContext.
box := 150@100 extent: 100@200.

"Black stroked arc"


gc displayArcBoundedBy: box
startAngle: 0
sweepAngle: 180.

398 VisualWorks
Drawing Objects

"Black filled arc"


gc displayWedgeBoundedBy: box
startAngle: 180
sweepAngle: 90.

"Red arc"
arc := EllipticalArc
boundingBox: box
startAngle: 270
sweepAngle: 90.
arc asFiller displayOn: (gc paint: ColorValue red)

Curved Lines

spline Bezier curve

Besides circular and elliptical arcs, VisualWorks provides two kinds of


smooth curve: Spline and Bezier.
A Spline is similar to a polyline in that it connects a collection of vertices;
the difference is that it smooths the corners. A Bezier curve has a start, an
end and two control points which affect the curve angle.

Application Developer’s Guide 399


Chapter 18 - Working With Graphics and Colors

| gc points spline random x y |


gc := ([Link]
prepareScratchWindow) graphicsContext.
points := OrderedCollection new.
random := Random new.

"Collect 10 random points."


10 timesRepeat: [
x := random next * 400.
y := random next * 400.
points add: x@y.
gc displayDotOfDiameter: 8 at: points last].

spline := Spline controlPoints: points.


spline asStroker displayOn: gc.
A Bezier curve is also available. A Bezier curve has a start, an end, and two
control points. Each control point causes the line to curve toward it, as if
exerting gravity on the line.
| gc points bezier random x y |
gc := ([Link]
prepareScratchWindow) graphicsContext.
points := OrderedCollection new.
random := Random new.

"Collect 10 random points."


4 timesRepeat: [
x := random next * 400.
y := random next * 400.
points add: x@y.
gc displayDotOfDiameter: 8 at: points last].

bezier := Bezier
start: (points at: 1)
end: (points at: 2)
controlPoint1: (points at: 3)
controlPoint2: (points at: 4).
bezier asStroker displayOn: gc.
Splines and Bezier curves support comparison, intersection testing,
scaling, and transforming. A Spline can also be asked whether it folds
back on itself (isCyclic).

400 VisualWorks
Drawing Objects

Line Properties
Line properties, such as thickness and endcaps, are controlled by the
graphics context rather than by the graphical object itself.
Changing the Line Thickness

By default, lines, arcs, and polygons are drawn with a one-pixel line width.
You can increase the thickness of a line by setting the thickness in pixels.
Extra thickness is spread evenly on both sides of the actual line, so a
horizontal line that is 20 pixels thick has 10 pixels above the line and 10
pixels below.
To set the line width, send a lineWidth: message to the graphics context of
the display surface. The argument is an integer indicating the number of
pixels of thickness.
| gc rect |
gc := ([Link]
prepareScratchWindow) graphicsContext.
rect := 10@10 extent: 30@30.

2 to: 20 by: 2 do: [ :width |


gc lineWidth: width.
rect moveBy: 30@30.
rect asStroker displayOn: gc].

Application Developer’s Guide 401


Chapter 18 - Working With Graphics and Colors

Changing the Line Cap Style

capButt

capProjecting

capRound

By default, lines and arcs are drawn with butt ends, which means each
end stops abruptly at the specified endpoint. When two thick lines share
an endpoint, butt ends produce a notched joint. Changing the cap style to
projecting fixes this by extending each end of the line by half of its
thickness. Another solution is to use round ends, which extend the ends
in a semicircle.
To change the endcap, send a capStyle: message to the graphics context.
The argument is derived by sending a capButt, capProjecting, or capRound
message to the GraphicsContext class.
| gc |
gc := ([Link]
prepareScratchWindow) graphicsContext.
gc lineWidth: 20.

"Butt line caps -- the default"


gc capStyle: GraphicsContext capButt.
gc displayLineFrom: 100@100 to: 300@100.
gc displayLineFrom: 300@100 to: 300@300.

"Projecting line caps"


gc capStyle: GraphicsContext capProjecting.
gc displayLineFrom: 100@150 to: 250@150.
gc displayLineFrom: 250@150 to: 250@300.

"Round line caps"


gc capStyle: GraphicsContext capRound.
gc displayLineFrom: 100@200 to: 200@200.
gc displayLineFrom: 200@200 to: 200@300.

402 VisualWorks
Graphical Images

Changing the Line Join Style

joinMiter

joinBevel

joinRound

By default, a polyline or polygon is drawn with mitered joints. In some


situations, a beveled or rounded joint is preferable. To change the line join
style, send a joinStyle: message to the graphics context of the display
surface. The argument is derived by sending a joinMiter, joinBevel, or
joinRound message to the GraphicsContext class.
| gc |
gc := ([Link]
prepareScratchWindow) graphicsContext.
gc lineWidth: 30.

"Miter joins -- the default"


gc joinStyle: GraphicsContext joinMiter.
gc displayPolyline: (Array with: 100@200 with: 200@50 with: 300@200).

"Bevel joins"
gc joinStyle: GraphicsContext joinBevel.
gc displayPolyline: (Array with: 100@300 with: 200@150 with: 300@300).

"Round joins"
gc joinStyle: GraphicsContext joinRound.
gc displayPolyline: (Array with: 100@400 with: 200@250 with: 300@400).

Graphical Images
An Image is a graphic object composed of a rectangular array of pixels. It
is similar to a Pixmap and a Mask in many respects, the main differences
being:
• An Image is stored in Smalltalk memory, so it is saved with the
Smalltalk image. For that reason, a graphical image can be used as a
storage device for Pixmaps and Masks.
• An Image is not a display surface, so you can’t display other graphic
objects on it as a means of assembling the desired picture.

Application Developer’s Guide 403


Chapter 18 - Working With Graphics and Colors

• An Image can be either color-based or coverage-based, depending on


its palette.
Common uses of images in an application are for cursors and icons, and
increasingly as decoration for an application GUI.
VisualWorks includes support for BMP, JPEG, GIF, and XBM formats in
the base. Support for PNG can be loaded from the PNGImageReader
parcel.

Pixels and Bitmaps


A graphic image is displayed on a computer screen as tiny dots of color.
Each dot is known as a picture element, or pixel.

101
000
101
bitmap pixels

Bits in memory represent picture elements on the screen

On a monochrome display, each pixel is either on (black) or off (white).


Each pixel is represented in memory by a single bit, and the entire screen
is represented as a two-dimensional array of bits. The array provides a
map of the screen, so it’s called a bitmap.
On a full color display, each pixel requires several bits to represent its
color. The number of colors that can be represented is determined by the
number of bits used to represent a pixel. Though the “bitmap” is no longer
a one-to-one mapping from bits in memory to pixels on the screen, it is
still referred to as a bitmap.

Color Depth and Images


Class Image is an abstract class providing the general protocol for
images. Its concrete subclasses provide specific representations for
images of different color depths (or bits per pixel) of 1, 2, 4, 8, 16, 24, or
32.
For each pixel, an Image stores the value of the picture at that position,
which is either the color value or the coverage value of the pixel.
An Image’s palette can be either color-based or coverage-based (see
page 416). The type of palette determines what kind of display surface
the image can be displayed on and copied to. A coverage-based Image

404 VisualWorks
Graphical Images

can be displayed on any surface a Mask can, while a color-based Image


can be displayed on a Window or a Pixmap. When copying a region from
an Image to a display surface, however, the two objects must have similar
palettes.
To create a display surface bearing an Image’s contents, send
asRetainedMedium to the Image. A Pixmap is returned when the Image has a
color-based palette, and a Mask is returned when the palette is coverage-
based. This operation is equivalent to creating a new Pixmap or Mask and
then displaying the Image on it.

Creating a Graphic Image


A graphic image is a rectangular painting made up of colored pixels
arranged in rows. Complex graphics that involve non-geometric elements
are typically graphic images.
Using the Image Editor
VisualWorks includes an Image Editor that you can use to paint an image
pixel by pixel, and then store it in a compilable resource method. Because
of the size of the encoded image, the Image Editor is best suited for
producing small images, such as for cursor shapes or icons.
To open an Image Editor, choose Tools­Image Editor from the VisualWorks
main window.

Paint the desired image in the scrollable pixel grid. The controls are pretty
standard for simple paint programs.

Application Developer’s Guide 405


Chapter 18 - Working With Graphics and Colors

To make the graphic available to your application, click the Install button,
then specify your application class as the class into which to install the
graphic, and a method name for the graphic. This installs the graphic as a
resource, which you can access in the resources browser. The method is
installed as a class method in a resource protocol of a selected class.
Reading an Image from a File
To creating an Image from an external source, such as a file, send a
fromFile: message to the ImageReader class, with the name of the file as a
String. The result is an instance of the ImageReader subclass appropriate
for the image format, such as GIFImageReader. To get the image from the
image reader, send an image message to it. For example:
image := ( ImageReader fromFile: '..\bin\win\[Link]' ) image
This returns an Image instance.
It is often useful to store the image in a resource method. To do so, send
an imageFromFile:toClass:selector: message, with the file name, the target
class name, and the resource selector name as arguments:
ImageReader
imageFromFile: '[Link]'
toClass: DummyTree
selector: #herald
Capturing an Image from the Screen
You can also capture a graphic image from the screen, whether the
image is in a VisualWorks window or another program’s window.
The Image Editor allows you to select a relatively small area of the
screen. To use its capability, open an Image Editor and choose the
Image­Capture command. The cursor changes to a cross-hair. Move the
cursor to the top left of the selection area, press the mouse button, drag
the cursor to the lower-right corner, and release the mouse button. You
can then edit or install the resulting image.
To capture a larger area, or to invoke the screen capture capability from
your application, send a fromUser message to the Image class. The cursor
changes to a cross-hair, and you can select the area as above. You will
need to capture the image in a variable and process it as needed. This
example simply displays it in a scratch window:
| gc capturedImage |
gc := ([Link]
prepareScratchWindow) graphicsContext.
capturedImage := Image fromUser.
capturedImage displayOn: gc.

406 VisualWorks
Graphical Images

Creating a Bitmap Manually


You can create an Image manually by directly editing its bits. Except for
very simple graphics, this is seldom done directly. In general, you would
create a tool to do this, as is provided by the Image Editor.
An Image is stored in rows that have been padded to multiples of 32 bits,
called packed rows.
To manually edit an Image, you can create an intermediate ByteArray
containing one byte for each pixel. In intensive applications, this
wastefulness can become noticeably slow.
An alternate set of bitmap accessors operate on the packed row format
directly:
packedRowAt: rowIndex
packedRowAt: rowIndex into: anArray
packedRowAt: rowIndex into: anArray startingAt: destinationIndex
packedRowAt: rowIndex putAll: anArray
packedRowAt: rowIndex putAll: anArray startingAt: sourceIndex
Use these accessors to manipulate the bit values of one packed row at a
time.

Displaying an Image
As with other visual objects, an image can display itself on a graphics
context. The image’s palette must match that of the graphics context:
coverage-based to display a Mask, color-based to display on a Window or
Pixmap.
To display an image positioned at the origin (0@0), send a displayOn:
message to the image with the graphics context as argument. To specify
a display position other than the default 0@0, send a displayOn:at:
message to the image with a Point as the second argument:
| gc logo |
gc := ([Link]
prepareScratchWindow) graphicsContext.
logo := LogoExample logo.
logo convertForGraphicsDevice: Screen default.

logo displayOn: gc.


logo displayOn: gc at: 50@50.
The convertForGraphicsDevice: message is necessary to ensure that the
image displays properly, by making sure that the color depth and bits per
pixel are correct. While it is not always required, it is strongly
recommended, especially for images that are read from files.

Application Developer’s Guide 407


Chapter 18 - Working With Graphics and Colors

Creating a Display Surface Bearing an Image


A common situation requires creating a hidden display surface (a Mask or
Pixmap) of the same size as an image, and then displaying the image on
it. The asRetainedMedium message returns a Pixmap if the image has a
color-based palette, and a Mask if the image has a coverage-based
palette:
| image pixmap |
image := LogoExample logo.

pixmap := image asRetainedMedium.


^pixmap

Caching an Image
A display surface such as a Pixmap usually can be displayed on another
display surface (such as a window) more quickly than an equivalent
Image. However, an Image has greater longevity because it does not
require a resource from the operating system, so it can survive when you
quit and restart VisualWorks.
A CachedImage combines the longevity of an Image with the displaying
speed of a display surface. Whenever its display surface is unavailable,
as when it has been destroyed by a save-and-restart operation, it is
recreated from the image automatically. This relieves your application
from having to recreate such display surfaces manually.
A CachedImage must be treated like a display surface, not an image. For
example, you cannot rotate a CachedImage.
Create a CachedImage by sending an on: message to the CachedImage
class, with the image as argument:
| gc logo |
gc := ([Link]
prepareScratchWindow) graphicsContext.

logo := CachedImage on: LogoExample logo.


logo displayOn: gc.

Coloring Pixels in an Image


Individual pixel colors can be changed by changing the color value at a
point. The colors that you substitute, however, must exist in the image’s
palette.

408 VisualWorks
Graphical Images

Changing Color by Color Value


To get the current color of a pixel, send a valueAtPoint: message to the
image, with a Point as argument indicating the coordinates of the pixel in
the image. To set the color of a pixel, send a valueAtPoint:put: message to
the image. The first argument is the location of the pixel, and the second
is a color that exists in the image’s palette.
| gc logo oldColor newColor white black |
gc := ([Link]
prepareScratchWindow) graphicsContext.
logo := LogoExample logo.
white := ColorValue white.
black := ColorValue black.

"Change each black pixel to white, and vice versa."


0 to: logo height -1 do: [ :y |
0 to: logo width - 1 do: [ :x |
oldColor := logo valueAtPoint: x@y.
oldColor = white
ifTrue: [newColor := black]
ifFalse: [newColor := white].

logo valueAtPoint: x@y put: newColor]].

logo displayOn: gc
Changing Color by Numeric Value
To get the current color number of a pixel, send an atPoint: message to
the image. The argument is a Point indicating the coordinates of the pixel
in the image. The number that identifies the pixel color in the image’s
palette is returned.
To change the color of a pixel, send an atPoint:put: message to the image.
The first argument is the location of the pixel and the second argument is
a color number that exists in the image’s palette.

Application Developer’s Guide 409


Chapter 18 - Working With Graphics and Colors

| gc logo oldColor newColor |


gc := ([Link]
prepareScratchWindow) graphicsContext.
logo := LogoExample logo.

"Change each black pixel to white, and vice versa."


0 to: logo height -1 do: [ :y |
0 to: logo width - 1 do: [ :x |
oldColor := logo atPoint: x@y.
oldColor = 1
ifTrue: [newColor := 0]
ifFalse: [newColor := 1].

logo atPoint: x@y put: newColor]].

logo displayOn: gc

Masking an Image
You can mask out
a rectangular portion
of an image . . .

. . . or any other shape

Sometimes an image contains extraneous material that needs to be


removed. In the simplest case, you can mask off a rectangular area. For
more complex shapes, a Mask graphical object is used.
Creating a Mask
The simplest way to create a mask is using the Image Editor. Select Image
­ Store B&W Mask, so this selection is checked. Then draw the mask
shape and install it as a resource in your application. The areas you draw
in black will allow the image to show through, and the areas in white will
be transparent, allowing the background to show through.
For regular geometric shapes, you can create a mask by sending
messages to the Mask class. Send an extent: message to the Mask class,
with a Point as argument specifying the size of the mask. You can display
the desired shape or shapes on the Mask as with a window or other
display surface. In the example, a solid oval is drawn. The shapes on the
mask define the visible regions of the image:

410 VisualWorks
Graphical Images

| ovalMask |
ovalMask := Mask extent: 66@66.
ovalMask graphicsContext
displayWedgeBoundedBy: ovalMask bounds
startAngle: 0
sweepAngle: 360.
^ ovalMask
You can also create a mask from an image by changing the palette of the
image to a coverage palette. Send a convertToCoverageWithOpaquePixel:
message to the image. The argument is an integer specifying the position
in the image palette of the color to make opaque, to allow the image to
show through.
Masking a Rectangular Area
For masking an image to a rectangular area, you do not need to create a
mask. Instead, you can simply specify the rectangle in a
completeContentsOfArea: message that you send to the display surface.
1 Create a display surface (Pixmap) containing the image by sending an
asRetainedMedium message to the image.
2 Send a completeContentsOfArea: message to the display surface, with a
rectangle as argument.
The copied portion is returned as an image, which can then be
displayed on the graphics context.
| gc logo subImage pixmap copyRect |
gc := ([Link]
prepareScratchWindow) graphicsContext.
logo := LogoExample logo magnifiedBy: 2@2.

pixmap := logo asRetainedMedium.


copyRect := 0@0 extent:
(logo width @ logo height / 2) rounded.

subImage := pixmap completeContentsOfArea: copyRect.


subImage displayOn: gc at: 10@10.
Masking a Nonrectangular Area
When the desired portion of an image is not rectangular, you can either
create a Mask of the desired geometric shape, or specify a mask
resource. The mask is then used as a stencil through which the image is
displayed.
1 Create a display surface (Pixmap) for the image by sending
asRetainedMedium to the image.

Application Developer’s Guide 411


Chapter 18 - Working With Graphics and Colors

2 Create the desired mask, if necessary.


The mask may be created in a resource method built by the Image
Editor, in another method, or on the fly in the displaying message.
3 Send a copyArea:from:sourceOffset:destinationOffset: message to the
graphics context of the destination display surface.
The copyArea argument is the mask. The from argument is the
graphics context of the source display surface. The sourceOffset
argument is a Point indicating the origin of the mask when placed
over the source display surface. The destinationOffset argument is the
origin of the subimage when displayed on the destination display
surface.
| gc logo pixmap ovalMask |
gc := ([Link]
prepareScratchWindow) graphicsContext.
logo := LogoExample logo magnifiedBy: 2@2.
pixmap := logo asRetainedMedium.

ovalMask := Mask extent: 66@66.


ovalMask graphicsContext
displayWedgeBoundedBy: ovalMask bounds
startAngle: 0
sweepAngle: 360.

gc copyArea: ovalMask
from: pixmap graphicsContext
sourceOffset: 0@0
destinationOffset: 10@10.

Modifying an Image
There are a variety of modifications you can make to images using
facilities provided in VisualWorks, such as rotating and expanding.
Expanding or Shrinking an Image
You can get a copy of an image that has been magnified or shrunken in
either the x dimension, the y dimension, or both.
To get an expanded copy of an image, send a magnifiedBy: message to
the image. The argument is a Point whose x value is multiplied by the
width of the image to derive the width of the expanded version; similarly,
the y value controls the height of the expanded version.

412 VisualWorks
Graphical Images

To shrink an image, send a shrunkenBy: message to the image. The


argument is a point that is used as a divisor to reduce the width and
height in the shrunken version.
| gc logo bigLogo tinyLogo |
gc := ([Link]
prepareScratchWindow) graphicsContext.
logo := LogoExample logo.

bigLogo := logo magnifiedBy: 1@2.


tinyLogo := logo shrunkenBy: 1@2.

logo displayOn: gc.


bigLogo displayOn: gc at: logo extent.
tinyLogo displayOn: gc at: logo extent + bigLogo extent.
Flopping an Image
Sometimes you need a mirror copy of an image. The basic steps show
how to get a reflected copy in which the imaginary mirror is aligned with
the x axis, the y axis, or both. This process of rotating an image about the
x axis or the y axis is known as flopping an image, from the photographic
process in which a negative is flopped onto its backside to produce a
mirror image.
To flop an image about the x axis, send a reflectedInX message to the
image. To flop an image about the y axis, send a reflectedInY message. To
flop an image about both axes, send a reflectedInX message followed by a
reflectedInY message.
| gc helpImage |
gc := ([Link]
prepareScratchWindow) graphicsContext.
helpImage := ToolbarIconLibrary help20x20 image.

helpImage
displayOn: gc at: 10@10.
helpImage reflectedInX
displayOn: gc at: 60@10.
helpImage reflectedInY
displayOn: gc at: 10@60.
helpImage reflectedInX reflectedInY
displayOn: gc at: 60@60.

Application Developer’s Guide 413


Chapter 18 - Working With Graphics and Colors

Rotating an Image
You can rotate an image about the z axis in 90-degree increments by
sending a rotatedByQuadrants: message to the image. The argument is an
integer indicating how many 90-degree rotations you want. A rotated copy
of the image is returned.
| gc helpImage rotatedImage |
gc := ([Link]
prepareScratchWindow) graphicsContext.
helpImage := VisualLauncher helpIcon image.

rotatedImage := helpImage rotatedByQuadrants: 1.

helpImage
displayOn: gc at: 10@10.
rotatedImage
displayOn: gc at: 60@10.
Each rotated copy uses time and memory resources. For a series of
rotations, you can reduce the resources required by reusing the same
scratch image for each subsequent copy, as shown in the variant. The
scratch image must be of the same size as the unrotated image, so this
technique works only when all images in the series are the same size.
Create a scratch image the same size as the image that is to be rotated
by sending a copyEmpty message to the original image. Then send a
rotateByQuadrants:to: message to the image to be copied. The first
argument is the number of quadrants to rotate the image. The second
argument is the scratch image.
| gc helpImage scratchImage |
gc := ([Link]
prepareScratchWindow) graphicsContext.
helpImage := ToolbarIconLibrary help20x20 image.

scratchImage := helpImage copyEmpty.

1 to: 4 do: [ :quads |


helpImage rotateByQuadrants: quads to: scratchImage.
scratchImage displayOn: gc at: (60 * quads) @ 10]

414 VisualWorks
Graphical Images

Overlaying Images

You can achieve a variety of layering effects by combining two images


and applying a filtering algorithm to the overlapping portions.
VisualWorks provides 16 built-in algorithms, called combination rules.
The rules are numbered 0 through 15, and the more commonly used
rules have names. Thus, sending an erase message to the RasterOp class
returns the combination rule for erasing shared pixels from the combined
image. Combining two images involves copying a region from one image
(the source) onto the other image (the destination), applying the
combination rule.
Raster operations work correctly only on monochrome screens that have
the most commonly used polarity characteristics. On color screens and
on monochrome screens of the opposite polarity, the effects will be
unpredictable. Because of this, only the RasterOp over rule is portable
across screen types.
To preserve the destination image in its unchanged state, first make a
copy on which to merge the source image, by sending a copy message to
the image (in the example, triangle).
Next, send a copy:from:in:rule: message to the copy. The copy argument is
a rectangle identifying the region in the destination image to be merged
with the source image (the lower part of the triangle). The from argument
is the origin of the rectangle within the source image (the origin of the
circle, because we want to copy the entire circle). The in argument is the
source image. The rule argument is an integer identifying a combination
rule (which can be derived by sending and, over, erase, reverse, under, or
reverseUnder to the RasterOp class).

Application Developer’s Guide 415


Chapter 18 - Working With Graphics and Colors

| gc triangle circle scratch |


gc := ([Link]
prepareScratchWindow) graphicsContext.

triangle := Pixmap extent: 50@100.


triangle graphicsContext
displayPolygon: (Array
with: 0@0
with: 0@50
with: 50@50).
triangle := triangle asImage.

circle := Pixmap extent: 50@50.


circle graphicsContext
displayDotOfDiameter: 50
at: 25@25.
circle := circle asImage.

0 to: 15 do: [ :rule |


scratch := triangle copy.
scratch
copy: (0@20 extent: 50@50)
from: 0@0
in: circle
rule: rule.

scratch displayOn: gc at: (50 * rule \\ 400) @ (50 * rule // 400 *


100)]

Colors and Patterns


VisualWorks uses Colors and Patterns to draw lines and fill shapes.
Colors as represented as instances of ColorValue. VisualWorks stores
colors as red, green, and blue (RGB) components, but allows colors to be
specified by constant names, by RGB values, or by hue, saturation, and
brightness (HSB) values.
A Pattern is an arrangement of pixels created by replicating a tile
throughout a painted region. For example, the gray background used by
many window managers is created by employing a four-pixel tile. The tile
can be an Image, a Pixmap, or a Mask.

416 VisualWorks
Colors and Patterns

Pixel Coverage
A CoverageValue identifies the fraction of a pixel that is covered. Since a
pixel, by its nature, must be displayed in its entirety, only the values 0 and
1 are typically used. Fractional coverages can be specified, however, as
explained in the discussion of coverage palettes on page 416.
CoverageValue is the paint basis for Masks. An Image can also be coverage-
based, typically when it is used as a storage medium for a Mask, which
does not survive after the system is shut down.
A CoverageValue can be created by name or by value:
CoverageValue transparent
CoverageValue coverage: 0
CoverageValue opaque
CoverageValue coverage: 1

Creating a Color
ColorValue class methods provide simple protocol for creating instances
by either color constant name, RGB values, or HSB values.
Create by Color Name
Several color constants are defined by class method selectors for each
color name. To create a color, send the appropriate color message to
ColorValue class. For example, to create an instance of cyan, send the
cyan message to the ColorValue class:
| gc color |
gc := ([Link]
prepareScratchWindow) graphicsContext.

color := ColorValue cyan.

gc paint: color.
gc displayDotOfDiameter: 400 at: 200@200.
The following example displays all the predefined colors in a ray chart.

Application Developer’s Guide 417


Chapter 18 - Working With Graphics and Colors

| gc endPoint colors |
gc := ([Link]
prepareScratchWindow) graphicsContext.
gc lineWidth: 7.
endPoint := 350@0.

colors := ColorValue constantNames.

colors do: [ :c |
endPoint := endPoint + (-10@12).
gc paint: (ColorValue perform: c).
gc displayLineFrom: 0@0 to: endPoint.
gc paint: ColorValue black.
c asString displayOn: gc at: endPoint + (0@8)]

Create by Red, Green, and Blue Values


Send a red:green:blue: message to the ColorValue class. All arguments are
numbers between zero and one, representing the intensity of their
respective colors. In the example, the intensity of green is varied while the
red and blue intensities remain at zero.
| gc origin |
gc := ([Link]
prepareScratchWindow) graphicsContext.
origin := 0@0.

1 to: 0 by: -0.01 do: [ :grn |


gc paint: (ColorValue red: 0.0 green: grn blue: 0.0).
origin := origin + 4.
gc displayRectangle: (origin extent: 400 - origin)]

418 VisualWorks
Colors and Patterns

Create by Hue, Saturation, and Brightness Values


Send a hue:saturation:brightness: message to the ColorValue class. The hue
argument is a number from 0 to 1, where 0 is red, 0.333 is green, 0.667 is
blue, and 1 is red again. The saturation argument is a number from 0 to 1,
representing minimum vividness (white) to full color; a more saturated
color makes an object appear closer to the viewer. The brightness
argument is a number from 0 to 1, representing minimum brightness
(black) to full color; varying the brightness is useful for representing
shadows.

| gc r x y |
gc := ([Link]
prepareScratchWindow) graphicsContext.
r := 50.
gc lineWidth: 2.

gc translation: 150@150.
0 to: 1 by: 0.005 do: [ :i |
x := (i * Float pi) cos * r.
y := (i * Float pi) sin * r / 2.
gc paint: (ColorValue hue: 0.0 saturation: 0.5 brightness: i).
gc displayLineFrom: x@y to: 0@-100 ].

Application Developer’s Guide 419


Chapter 18 - Working With Graphics and Colors

gc translation: 200@200.
0 to: 1 by: 0.005 do: [ :i |
x := (i * Float pi) cos * r.
y := (i * Float pi) sin * r / 2.
gc paint: (ColorValue hue: 0.0 saturation: 0.75 brightness: i).
gc displayLineFrom: x@y to: 0@-100 ].

gc translation: 250@250.
0 to: 1 by: 0.005 do: [ :i |
x := (i * Float pi) cos * r.
y := (i * Float pi) sin * r / 2.
gc paint: (ColorValue hue: 0.0 saturation: 1.0 brightness: i).
gc displayLineFrom: x@y to: 0@-100 ]
Coloring a Graphical Object

By default, a color-based display surface (ApplicationWindow or Pixmap)


displays geometric objects in black. To change the color of an object, set
the color for the graphic context before drawing the object. To set the
color, send a paint: message to the graphics context of the display surface
with the color as argument:

420 VisualWorks
Colors and Patterns

| gc circle colors |
gc := ([Link]
prepareScratchWindow) graphicsContext.
circle := Circle center: 200@200 radius: 200.
colors := ColorValue constantNames.

colors do: [ :colorName |


gc paint: (ColorValue perform: colorName).
circle := circle scaledBy: 0.9.
circle asFiller displayOn: gc]

Creating a Pattern
A Pattern is created by filling a space with a single graphic image that is
repeated in tiles. A pattern can be used in any situation that you can use
a solid color.
To create a pattern, send an asPattern message to the graphic image to
serve as the tile:
| gc tile |
gc := ([Link]
prepareScratchWindow) graphicsContext.

tile := Image CincomLogo shrunkenBy: 4@4.


tile := tile asPattern.

gc paint: tile.
gc displayRectangle: (50@50 extent: 300@300).
The graphic image is typically an Image subclass instance, but can also
be a window, Pixmap, or Mask.
Applying a Pattern
Patterns are applied in the same way as colors. Send a paint: message to
the graphics context of the display surface on which the object is to be
displayed. The argument is a pattern, or in the case of a Mask, a
coverage.

Application Developer’s Guide 421


Chapter 18 - Working With Graphics and Colors

| gc tile |
tile := Pixmap extent: 10@10.
gc := tile graphicsContext.

"Tile background"
gc paint: ColorValue chartreuse.
gc displayRectangle: (0@0 extent: 10@10).

"Tile foreground"
gc paint: ColorValue red.
gc displayDotOfDiameter: 10 at: 4@4.

"Patterned circle"
gc := ([Link]
prepareScratchWindow) graphicsContext.
gc paint: tile asPattern.
gc displayDotOfDiameter: 400 at: 200@200.
Adjusting a Pattern’s Tile Phase
For some patterns, the placement of that first tile can be critical to the
pattern. By default, the first tile is placed with its upper left corner at the
origin of the display surface’s GraphicsContext.
To adjust the start location, send a tilePhase: message to the graphics
context of the display surface on which the patterned object is to be
displayed. The argument is a point that defines the origin of the first tile in
the pattern.
In the example, the tile phase is the same as the origin of the painted
object, which aligns the tiles with the top and left edges of the object.
| gc tile |
gc := ([Link]
prepareScratchWindow) graphicsContext.
tile := Image CincomLogo shrunkenBy: 4@4.
tile := tile asPattern.
gc paint: tile.

gc tilePhase: 50@50.

gc displayRectangle: (50@50 extent: 300@300).

422 VisualWorks
Image Color Palettes

Image Color Palettes


A Palette represents the collection of colors available for coloring pixels.
For colored objects, such as images, the color of each pixel is stored as a
numeric value. A palette is needed to translate those numeric values to
instances of ColorValue or CoverageValue.

Coverage Palettes
A CoveragePalette is used by Masks and masking images, to specify levels
of transparency. It has a maxPixelValue, which determines the number of
levels of transparency. Usually, maxPixelValue is set to 1, because a pixel
can only be fully transparent (pixel value 0) or fully opaque (1).
However, you may want to allow for intermediate levels of translucence.
By specifying the maxPixelValue, you can create an image having any
number of coverage levels (currently, masks are restricted to two levels).

Color Palettes
A color palette can have either of two representations: fixed or mapped. A
FixedPalette breaks a pixel value into red, green, and blue fields, each of
which controls the intensity of that primary color. A MappedPalette stores a
table of colors, so each numeric pixel value can be associated with an
arbitrary color. A MonoMappedPalette is a MappedPalette that is specialized
for the case in which the palette contains only black and white.
Mapped palettes are most appropriate for images on color-mapped
display screens and for images that use a small number of colors. Fixed
palettes support true-color display screens that don’t use a hardware
color map. Such true-color screens typically support a large number of
colors. A mapped palette for a typical true-color screen, which has a
depth of 24, requires a color mapping table with more than 16 million
elements.
Creating a Color Palette
Different types of palettes are created in different ways.
To create a mapped palette, send a withColors: message to MappedPalette,
specifying an array of colors used to initialize the palette.
A fixed palette uses RGB values. Depending on the depth of the image,
one set of RGB values might occupy 8 bits, 24 bits, or 32 bits (or even
something in between). When you create a fixed palette, you must arm it
with the means to locate the red bits, the green bits, and the blue bits.
You do so by indicating the number of the bit that begins each RGB

Application Developer’s Guide 423


Chapter 18 - Working With Graphics and Colors

component as well as the maximum value for that component. In the


creation message, the starting bit is called the shift value and the
maximum value is called the mask value.
Eight-bit Color Palettes
Fixed palettes for 8-bit pixel values are structured in which the high three
bits specify the red component, the next three bits the green component,
and the low two bits the blue component.

0 0 1 0 1 0 1 1

red green blue

8-bit color palette

Image Display Performance


The composition of an image’s palette greatly affects the amount of time
required to display the image. An image can be displayed quickly in either
of two circumstances:
• Its palette is the same as that of the display surface
• Its palette contains only two colors, which can be rendered without
halftoning.
Otherwise, displaying the image requires creating a temporary image,
which can take a substantial amount of time. To avoid generating a
temporary image, convert the image to the native palette and then display
the converted image. For example, to convert an image to the color
palette of the default screen (and therefore also of all windows and
pixmaps on the default screen), perform:
anImage convertToPalette: Screen default colorPalette
By default, the convertToPalette: operation employs a NearestPaint renderer.

Device Color Map


The window manager’s color map is not accessible from within Smalltalk.
The screen’s colorPalette is assembled based on that color map, as
indicated in the following table. In the Comment column, “Fully populated”
means the VisualWorks palette is the same as the device color map.
“Partially populated” means VisualWorks uses only a portion of the color

424 VisualWorks
Image Color Palettes

map, leaving enough unused cells so neighboring applications will have a


chance to allocate their colors, too. When the platform provides a hint as
to the default set of colors to be shared by applications, we use that set.

Screen depth Window system Palette type Comment


1 All Mapped Fully populated
2 All Mapped Fully populated
4 All Mapped Fully populated
8 X Mapped* Partially populated
8 MS-Windows Mapped Partially populated
8 Macintosh Mapped Fully populated
15 MS-Windows Fixed RGB values
16 All Fixed RGB values
24 All Fixed RGB values
32 All Fixed RGB values
* Using X, an 8-bit color map can be made fixed instead of mapped.

Applying a Palette to an Image


In a graphic image, each pixel is associated with a color in the image’s
palette of colors. You can effectively change one or more colors in an
image by creating a new palette with the desired colors at the old colors
positions, and then install the new palette. The new palette must have the
same number of color entries as the old palette.
1 Create an array representing the old color palette.
To create the array, send a palette message to the image, and then
send a colors message to the resulting palette.
2 Modify the palette by replacing colors in the array as desired.
3 Create a new palette by sending a withColors: message to the
MappedPalette class, with the new array as argument.
4 Install the new palette by sending a palette: message to the image,
with the new palette as argument.
In this example, every white pixel is converted to yellow.

Application Developer’s Guide 425


Chapter 18 - Working With Graphics and Colors

| gc palette image colors whiteIndex |


gc := ([Link]
prepareScratchWindow) graphicsContext.
image := InputFieldSpec paletteIcon asImage.

colors := image palette colors.


whiteIndex := colors indexOf: ColorValue white.
colors at: whiteIndex put: ColorValue yellow.

palette := MappedPalette withColors: colors.

image := image palette: palette.


image displayOn: gc at: 10@10.

Converting an Image to Use the Default Palette


When a color palette differs from the palette used by the display surface,
a temporary image is created so VisualWorks can simulate the desired
colors when necessary. This step can take a significant amount of time.
To display an image quickly, convert it to use the default palette that is
used by display surfaces.
To convert the palette for an image, send a convertToPalette: message to
the image. The argument is the default color palette, which can be
accessed by sending a default message to the Screen class and then
sending a colorPalette message to the resulting screen.
| gc image |
gc := ([Link]
prepareScratchWindow) graphicsContext.
image := Image CincomLogo magnifiedBy: 2@2.

image := image convertToPalette: Screen default colorPalette.


image displayOn: gc at: 10@10.
For a coverage-based image, send a coveragePalette message instead of
colorPalette.

Color Rendering Policies


When an image makes liberal use of the color turquoise, what should a
black-and-white window do when asked to display that alien color? How
about a color window that doesn’t happen to have just the right shade of
turquoise in its palette?

426 VisualWorks
Color Rendering Policies

VisualWorks provides three common techniques for rendering unknown


colors, represented by the classes: NearestPaint, OrderedDither and
ErrorDiffusion.
Any of the three can be used to render an image, but only NearestPaint
and OrderedDither are appropriate for rendering paints. A PaintPolicy object
holds both a paintRenderer and an imageRenderer, which may be the same.
The default renderers are determined as follows:

NearestPaint Used by Pixmaps and Windows on color systems


OrderedDither Used by Masks on all types of screens
OrderedDither Used by Pixmaps and Windows on monochrome or
gray-scale systems

NearestPaint
NearestPaint simply chooses the nearest available paint from the screen’s
palette. On color screens, NearestPaint usually produces satisfactory
results and always gives the best performance of the three renderers.
On a limited palette, such as on a monochrome screen, the results can
be disappointing. For example, a magenta image on a chartreuse
background will result in a white rectangle, because both colors are
luminous enough to be converted to white by NearestPaint.

OrderedDither
OrderedDither employs a threshold array to synthesize unrecognized
colors by blending neighboring colors from the screen’s palette. This has
the effect of smoothing the transition from one palette color to the next in
a continuous tone. While the result is often more pleasing than with
NearestPaint, you pay a price in performance.

ErrorDiffusion
An ErrorDiffusion uses a more sophisticated blending algorithm. When it
makes a choice from the screen’s palette, it keeps track of how far off that
choice was from the requested color. When this error accumulates
sufficiently, the renderer uses the color on the other side of the threshold.
For example, suppose that a region of the image uses a red-brown color,
but the screen’s palette has only red and brown. An ErrorDiffusion may
supply red at first, but keeps track of the numeric difference between red

Application Developer’s Guide 427


Chapter 18 - Working With Graphics and Colors

and the red-brown. When that remainder accumulates to a breakpoint, a


brown pixel is displayed instead. In this way, red and brown pixels are
blended to give a red-brown effect.

Applying a Renderer to an Image


If an image is to be displayed repeatedly, there is a performance
advantage to converting it to use the screen’s renderer, rather than
leaving it to the display surface to perform the conversion each time the
original image is displayed on it.
To convert an image, send a convertForGraphicsDevice:renderedBy: message
to the image. The first argument is typically Screen default. The second
argument is the renderer to use.
| gc r g b im |
gc := ([Link]
prepareScratchWindow) graphicsContext.
im := Image
extent: 60@60
depth: 15
palette: (FixedPalette
redShift: 10 redMask: 31
greenShift: 5 greenMask: 31
blueShift: 0 blueMask: 31).
0 to: 59 do: [:x |
0 to: 59 do: [:y |
r := 1 - ((x@y - (10@10)) r / 30) max: 0.
g := 1 - ((x@y - (20@50)) r / 30) max: 0.
b := 1 - ((x@y - (50@30)) r / 30) max: 0.
im atPoint: x@y put: (im palette
indexOfPaintNearest:(ColorValue red: r green:g blue: b))]].

(im convertForGraphicsDevice: Screen default


renderedBy: NearestPaint new)
displayOn: gc at: 10@10.

(im convertForGraphicsDevice: Screen default


renderedBy: OrderedDither new)
displayOn: gc at: 80@10.

(im convertForGraphicsDevice: Screen default


renderedBy: ErrorDiffusion new)
displayOn: gc at: 150@10.

428 VisualWorks
Color Rendering Policies

Converting an Image to a Specific Palette


The image can be converted to a palette other than the screen’s palette.
This is useful for showing what the image would look like on a screen that
has a limited palette.
Send a convertToPalette:renderedBy: message to the image, where the first
argument is the desired palette (in the example, a monochrome palette),
and the second argument is the desired renderer.
| gc r g b im |
gc := ([Link]
prepareScratchWindow) graphicsContext.
im := Image
extent: 60@60
depth: 15 palette: (FixedPalette
redShift: 10 redMask: 31
greenShift: 5 greenMask: 31
blueShift: 0 blueMask: 31).
0 to: 59 do: [:x |
0 to: 59 do: [:y |
r := 1 - ((x@y - (10@10)) r / 30) max: 0.
g := 1 - ((x@y - (20@50)) r / 30) max: 0.
b := 1 - ((x@y - (50@30)) r / 30) max: 0.
im atPoint: x@y put: (im palette
indexOfPaintNearest:
(ColorValue brightness: 1-((1-r)*(1-g)*(1-b))))]].

(im convertToPalette: MappedPalette whiteBlack


renderedBy: NearestPaint new)
displayOn: gc at: 10@10.

(im convertToPalette: MappedPalette whiteBlack


renderedBy: OrderedDither new)
displayOn: gc at: 80@10.

(im convertToPalette: MappedPalette whiteBlack


renderedBy: ErrorDiffusion new)
displayOn: gc at: 150@10.
Setting the Rendering Policy for Nonimage Graphics
Graphic objects other than images do not have their own color, so the
rendering is performed by the graphics context of the display surface. To
change the renderer, you install the desired renderer in the graphics
context.

Application Developer’s Guide 429


Chapter 18 - Working With Graphics and Colors

Install a paint policy in the graphics context of the display surface by


sending a paintPolicy: message to the graphics context. The argument is a
PaintPolicy, typically a new instance. Then, set the rendering algorithm by
sending a paintRenderer: message to the paint policy with either a
NearestPaint or an OrderedDither as argument. (ErrorDiffusion is only used
with images).
| gc |
gc := ([Link]
prepareScratchWindow) graphicsContext.

gc paintPolicy: (PaintPolicy new imageRenderer: OrderedDither new).

gc paintPolicy paintRenderer: NearestPaint new.


0 to: 60 by: 4 do: [:i |
0 to: 60 by: 4 do: [:j |
gc paint: (ColorValue red: i/60 green: j/60 blue: 0).
gc displayRectangle: (i@j+(10@10) extent: 4@4)]].

gc paintPolicy paintRenderer: (OrderedDither order: 1).


0 to: 60 by: 4 do: [:i |
0 to: 60 by: 4 do: [:j |
gc paint: (ColorValue red: i/60 green: j/60 blue: 0).
gc displayRectangle: (i@j+(80@10) extent: 4@4)]].

gc paintPolicy paintRenderer: (OrderedDither order: 6).


0 to: 60 by: 4 do: [:i |
0 to: 60 by: 4 do: [:j |
gc paint: (ColorValue red: i/60 green: j/60 blue: 0).
gc displayRectangle: (i@j+(150@10) extent: 4@4)]].
By default, a new OrderedDither has an order of 6, which means it
synthesizes 65 (2 to the sixth, plus 1) intermediate color values between
each pair of neighboring colors in the palette. You can set the order by
sending an order: message to the OrderedDither class to create an
instance; the argument is the desired order number.

430 VisualWorks
19
Working with Text

Characters and strings are primarily managed by the two classes


Character and String. This chapter discusses operations at the character
level first, followed by string operations.
The final section places Character and String in the context of their abstract
superclasses and, in the case of String its concrete subclasses.
As a collection of characters, a string responds to the messages
described in Chapter 17, “Collections.” The more pertinent behavior is
reviewed in this chapter.

Characters
Character objects are instances of the class Character. As with all objects
in Smalltalk, and unlike many languages, characters are full blooded
objects, not primitive data types.

Note: Any application that manipulates characters should be


prepared to encounter any character value from 0 to 65535.

Creating Characters
Many characters can be represented by printable keyboard characters.
Instances of these characters can be created by preceding the desired
character with a dollar sign:
char := $C

Application Developer’s Guide 431


Chapter 19 - Working with Text

Certain characters cannot be created as keyboard literals, such as


<Delete> and <Return>. Smalltalk provides class messages for creating
many of these characters. Send one of the following messages to the
Character class to create the corresponding character: backspace, cr, del,
esc, leftArrow, lf, newPage, space, tab. For example:
char := Character cr
A character can also be created from its Unicode numeric equivalent.
Send a value: message to the Character class, with the numeric Unicode
representation for the character:
char := Character value: 67
The numeric value is displayed in a character’s print string.
A composed character is a character consisting of base character plus a
diacritical mark. To create a composed character, send a
composeDiacritical: message to a character. The argument is a diacritical
character, which can be obtained by sending diacriticalNamed: to the
Character class. The argument is a symbol naming a diacritical character.
A list of valid diacritical characater names is included in the method
comment.
| baseChar diacrit composedChar |
baseChar := $a.
diacrit := Character diacriticalNamed: #grave.
composedChar := baseChar composeDiacritical: diacrit

Testing Character Types


Because the extended character set contains so many subsets, Character
provides a variety of tests to help you characterize an instance:
Character Tests
Method Returns true if the character is...
isLowercase a-z or a lowercase special character
isUppercase A-Z or an uppercase special character
isAlphabetic a-z, A-Z, or a special character
isVowel in the set: AEIOUaeiou (with or without
diacritical marks)
isDigit 0-9
isAlphaNumeric a-z, A-Z, 0-9, or a special character
isSeparator space, cr, tab, line feed, form feed, or null

432 VisualWorks
Strings

Character Tests (Continued)


Method Returns true if the character is...
isDiacritical a diacritical mark (has a value in the range
16rC1 to 16rCF)
isComposed composed of base and diacritical parts (has a
value of 16rF100 or higher)
isLetter English alphabet or extended character

Comparing Characters
Characters can be compared using the usual binary comparison
operators defined for numbers: =, ==, <, >, ~=, and so on. Comparison is
performed based on the integer values of the characters, so
$C < $D
evaluates as True, but
$c < $D
evaluates as False.

Strings
A String in Smalltalk is a collection, or more specifically an array, of
characters. While protocol is defined at the level of the String class, a
string is actually represented as a platform-specific subclass of String.
Strings are the foundation of all text operations in VisualWorks, including
the text formatting and display operations described later in this chapter.

Creating a String
Most frequently a string is created by enclosing the desired characters in
single quotes:
| string |
string := 'This is a string.'.
^string
You can create an empty string by sending a new message to the String
class. This is equivalent to enclosing nothing between single quotes.
| emptyString |
emptyString := String new.
^emptyString

Application Developer’s Guide 433


Chapter 19 - Working with Text

Although a string will grow to accommodate added characters, it is more


memory efficient to create a string of the appropriate size and then
change its characters. To do this, send a new: message to the String class
with the length specified:
| newString |
newString := String new: 15.
^newString
By default, the string is filled with null characters, but you can specify the
default character by using the new:withAll: message. The first argument is
the number of characters, and the second argument is the character to fill
the string:
| filledString |
filledString := String new: 10 withAll: $x.
^filledString
It is frequently necessary to represent a character by a string. There are
several ways to create such a string, using obvious variations of the
methods already shown. Another way is to send a with: message to the
String class, with the character that is to be the sole element of the string
as the argument. This is especially useful, and often necessary, when the
character is a non-printing or white-space character:
| oneCharString |
oneCharString := String with: Character tab.
^oneCharString

Changing the Case in a String


Applications that manipulate strings sometimes need to convert one or
more lowercase letters to uppercase, or vice versa. You can change the
case of an entire string or of a selected letter.

Note: Do not use case-changing protocol with strings whose


characters are caseless (for example, Japanese Katakana
characters).

To convert a string to all lowercase letters, send an asLowercase message


to the string. Similarly, send asUppercase to convert the entire string to
uppercase letters:
| string |
string := 'North American Fertilizer Company'.
^string asUppercase

434 VisualWorks
Strings

To change the case of individual characters in a string, you identify the


character by its index (place in the string), use the asUppercase or
asLowercase message to the character, then put the converted character
back in the string at the same location. The following example uses the
keysAndValuesDo: message to cycle through the string, and set all
characters to lowercase except the first and those preceded by a
separator character:
| string prevCharIsSeparator newChar |
string := 'NORTH AMERICAN FERTILIZER COMPANY'.
prevCharIsSeparator := true.

string keysAndValuesDo: [ :index :char |


prevCharIsSeparator
ifTrue: [newChar := char asUppercase]
ifFalse: [newChar := char asLowercase].
string at: index put: newChar.
prevCharIsSeparator := char isSeparator].

^string
Some character sets contain single lowercase characters that become
multiple characters in their uppercase form. If you are working with such a
character set, your code should handle the results of asUppercase
accordingly.

Getting a String’s Length and Width


A String is a kind of Collection with characters as its elements. Counting
the characters in a string is accomplished by sending a size message to
the string:
| string |
string := '123456789'.
^string size
The width of a string changes depending on the font and point size that is
used to display it. Because the font choice is controlled by the graphics
context of the display surface, that object can compute the width of a
string in pixels. Send a widthOfString: message to the graphics context of
the display surface on which the string will be displayed. The argument is
the string. The width in pixels is returned.

Application Developer’s Guide 435


Chapter 19 - Working with Text

| window string width |


window := ScheduledWindow new.
string := 'Hello, world'.

width := window graphicsContext


widthOfString: string.
^width

Combining Strings
There are a variety of ways in which two or more strings can be combined
to form longer strings, or to perform replacements within a string.
The simplest operation is concatenation, which is performed by putting a
comma between the two string expressions, for example:
| firstName lastName fullName space |
firstName := 'Bill'.
lastName := 'Clinton'.
space := String with: Character space.

fullName := firstName, space, lastName.


^fullName
salutation := 'Dear ', addresseeName.
The result is a new string, without changing either of the original strings.
Another useful approach, especially for constructing strings of
dynamically generated data, like reports, is to use a WriteStream. Create
a stream by sending an on: message to the WriteStream class. The
argument is typically an empty string, but it could be any string, such as a
preassembled report heading. Then append each string in the series to
the stream by sending a nextPutAll: message to the stream, with the string
as argument. Get the stream contents in the form of a string by sending a
contents message to the stream.
| classNames formalList |
classNames := Smalltalk classNames.
formalList := WriteStream on: String new.

classNames do: [ :name |


formalList nextPutAll: 'Class: ';
nextPutAll: name;
cr].

^formalList contents

436 VisualWorks
Strings

Comparing Strings
Unlike characters, strings are not compared by numerical value of their
characters. When comparing strings, case is ignored and alphabetical
order is used, unless the two strings have exactly the same letters in the
same order. In this latter case, numerical values are used to differentiate
uppercase and lowercase letters.
Testing for Equality and Identity
Two strings are equal when both have the same number of characters,
and both have the same characters in the same order.
To test for equality or inequality, send an = or ~= (not equal) message to
one string with another string as argument:
| str1 str2 |
str1 := 'abc'.
str2 := 'ABC'.
^str1 = str2
To compare based on identity, send an == or ~~ (not identical) message to
the object. Two different strings cannot be identical, though two variables
that refer to the same string are identical.
| str1 str2 str3 |
str1 := 'Excellent'.
str2 := 'Excellent'.
str3 := str1.

^Array
with: (str1 == str2)
with: (str1 == str3)
The sameAs: message compares the equality of strings while ignoring
case:
| str1 str2 str3 |
str1 := 'north'.
str2 := 'North'.
str3 := 'northwest'.

^Array
with: (str1 sameAs: str2)
with: (str1 sameAs: str3)
with: (str2 sameAs: str3)

Application Developer’s Guide 437


Chapter 19 - Working with Text

Comparing by Sorting Order


The usual comparison operators, in addition to equality and identity, can
be used to compare strings:

< less than


<= less than or equal to
> greater than
>= greater than or equal to

Comparison is by alphabetical order in most cases, rather than numerical


value of the characters. So,
'BCD' < 'bcde'
evaluates to true.
If two strings have exactly the same letters in the same order, the integer
values of the characters is used to differentiate them. So,
'bcD' > 'bcd'
evaluates to false, because uppercase letters have lower integer values
than lowercase letters.
Rating the Similarity of Two Strings
Two messages return a similarity rating of strings.
A sameCharacters: message returns an integer indicating how many
characters are the same (including case) up to the first mismatch. So,
'bcDe' sameCharacters: 'bcde'
returns 2.
A spellAgainst: returns an integer from 1 (entirely different) through 100
(equal) is returned, giving a percentage of match to mismatch. So,
'bcDe' spellAgainst: 'bcde'
returns 75.

Searching
The ability to find a specific character or substring is essential in
applications that parse strings. Often a special character or series of
characters identifies a field within a string, especially when the string
represents the contents of a structured text file.

438 VisualWorks
Strings

By default, searching is case-sensitive, but there are methods which


ignore case during a search.
Searches can also use wildcard characters. A pound sign (#) takes the
place of any single character, and an asterisk (*) takes the place of zero
or more characters.
Get the Index of a Character in a String
To get the index of a character, send an indexOf: message to the string.
The argument is the search character. If it is not found, zero is returned.
To find the starting index of a substring, send a
findString:startingAt:ifAbsent: message to the string. The first argument is
the substring to be found. The second argument is the character position
at which the search is to begin. The third argument is a block containing
actions to be taken if the substring is not found (often an empty block, to
avoid the default error).
| classComment searchChar searchString index1 index2 |
classComment := String comment.
searchChar := $<.
searchString := 'Class Variables:'.

index1 := classComment indexOf: searchChar.


index2 := classComment
findString: searchString
startingAt: 1
ifAbsent: [ ].

^Array with: index1 with: index2


Ignoring Case in a Search
Send a findString:ignoreCase:useWildcards: message to the string. The
findString argument is the substring to be found. The ignoreCase argument
is true when case difference is to be ignored.
The useWildcards argument is true when the pound sign and asterisk are
to be interpreted as wildcard characters rather than literal characters.
Because the presence of an asterisk wildcard affects the endpoint of the
found string, this variant returns an Interval identifying the index range of
the found string. A zero interval is returned when the search string is not
found.

Application Developer’s Guide 439


Chapter 19 - Working with Text

| classComment searchString interval |


classComment := String comment.
searchString := 'Var*:'.

interval := classComment
findString: searchString
startingAt: 1
ignoreCase: true
useWildcards: true.

^classComment
copyFrom: interval first
to: interval last

Substring Operations
When a string contains two or more parts, getting the parts as separate
strings is a common requirement. For example, you might need to extract
the first and last names from a string containing a full name. You can
copy a portion of a string, using the starting and stopping character
locations.
In certain situations, the only part of a string that you need is a prefix that
ends at a specific character. You can copy the characters that precede a
specific endpoint character.
Copying a Substring
Send a copyFrom:to: message to the string. The first argument is the
starting index and the second argument is the ending index of the desired
substring.
| fullName firstName lastName spaceIndex |
fullName := 'Mahatma Gandhi'.
spaceIndex := fullName indexOf: Character space.

firstName := fullName
copyFrom: 1
to: spaceIndex - 1.
lastName := fullName
copyFrom: spaceIndex + 1
to: fullName size.

^Array with: firstName with: lastName

440 VisualWorks
Strings

Copying a Prefix
Send a copyUpTo: message to the string. The argument is the character
that marks the end of the prefix (but is not included in it).
| fullName firstName |
fullName := 'Boris Yeltsin'.

firstName := fullName copyUpTo: Character space.


^firstName
Removing or Replacing a Substring
A string can be quite long and complicated, representing an entire report
or the contents of a lengthy text file. In long strings especially, replacing a
portion of the string with a new substring is frequently useful.
Removing characters is accomplished by creating a copy in which the
unwanted characters have been replaced by an empty string.
When a string contains multiple occurrences of a substring, you can
replace all occurrences.
Replacing a Substring
To replace characters in a string with another string, send a
copyReplaceFrom:to:with: message to the string. This returns a copy of the
original string with the replacement made. The first and second
arguments are the index locations of the starting and stopping characters
in the substring that is to be replaced. The with: argument is the
substitution string.
You can also use this method to insert a substring without removing any
characters in the existing string, by making the ending index one less
than the starting index.
To remove characters, replace them with an empty string.
For details of the operation of this method, refer to the method comment.

Application Developer’s Guide 441


Chapter 19 - Working with Text

| aString anotherString newString |


aString := 'abcd'.
anotherString := 'efgh'.

" Replacement, returns 'aefghd' "


newString := aString copyReplaceFrom: 2 to: 3 with: anotherString.

" Insertion, returns 'aefghbcd' "


newString := aString copyReplaceFrom: 2 to: 1with: anotherString.

" Prefixing, returns 'efghabcd' "


newString := aString copyReplaceFrom: 1 to: 0 with: anotherString.
^newString
Replacing All Occurrences of a Substring
Send a copyReplaceAll:with: message to the string. The first argument is
the substring that is to be replaced. The second argument is the
replacement substring.
| colorNames |
colorNames := String new.
ColorValue constantNames do: [ :name |
colorNames := colorNames, name asString, ' '].

colorNames := colorNames"Variant Step"


copyReplaceAll: 'Gray'
with: 'Grey'.
^colorNames

String Substitution Parameters


Strings can include formal parameters, enclosed in the angle brackets
< >. The parameters are expanded by sending a version of the
expandMacros: message to the string.
Simple parameters are <n> and <t>, which specify substitution of CR and
Tab, respectively. For example, the String 'This is a <n><t>test' can be
expanded:
'This is a <n><t>test.' expandMacros
to print:
This is a
test.
Positional substitution parameters are also allows. Immediately following
the opening bracket there may be an integer that specifies which of the
expansion arguments to substitute for this parameter. This allows for

442 VisualWorks
Strings

substitutions in the string to appear in a different order than that in which


the arguments are passed in, and for the same argument to be
substituted more than once.
Following this parameter index is a character that identifies how the
substitution is to be performed. The characters and the substitution they
indicate are:
p
Substitute the printString value of the argument. For example:
'This is a <1p> test.' expandMacrosWith: 'substitution'
expands to
'This is a ''substitution'' test.'
s
Substitute the argument itself, which must be a CharacterArray. For
example:
'This is a <1s> test.' expandMacrosWith: 'substitution'
expands to
'This is a substitution test.'
?
Requires two arguments in the parameter, and a Boolean expression
argument. The first is substituted if the expression argument is true,
and the second if the argument is false. For example:
'One is greater than <1?zero:two>.' expandMacrosWith: true
expands to
'One is greater than zero.'
#
Requires two arguments in the parameter, and a numeric expression
argument. The first is substituted if the expression argument is equal
to 1; otherwise, the second is substituted. For example:
'The book "<1#War:Peace> and <2#War:Peace>" is a must read.'
expandMacrosWith: 1 with: 2.
expands to
'The book "War and Peace" is a must read.'
The versions of expandMacros in these examples take one and two
positional substitution arguments. For up to four arguments, there are
also expandMacrosWith:with:with:, and expandMacrosWith:with:with:with:. For
more than four arguments, use exapndMacrosWithArguments:, with an Array
of arguments. All of these expand <n> and <t> as well.

Application Developer’s Guide 443


Chapter 19 - Working with Text

The character $% acts as the escape character. Unless otherwise


specified, any character following the quote character is itself, and is not
treated specially. For example, '%<' becomes '<', which, because it is
preceded by the escape character, is not treated as the beginning of a
formal parameter. So,
'This is %<1s%> test' expandMacrosWith: 'a'
expands to
'This is <1s> test'

Abbreviating a String
Abbreviations are rarely as comprehensible as the full form of a string,
and automatically derived abbreviations tend to be even less readable. In
some situations, however, an abbreviation is useful, and VisualWorks
provides a few useful abbreviation messages. Here are two methods.
Browse the String class and its superclasses for others.
Contracting a String
Send a contractTo: message to the string. The argument is the number of
characters in the abbreviation, including three for the ellipsis. Half of the
abbreviation will be taken from the beginning of the string and the other
half from the end.
| string contractedString |
string := 'North American Free Trade Agreement'.

contractedString := string contractTo: 15.


^contractedString
Removing Vowels
Send a dropFinalVowels message to the string. An abbreviated string is
returned in which only the leading vowel (if any) remains.
| string noVowelString |
string := 'North American Free Trade Agreement'.
noVowelString := string dropFinalVowels.
^noVowelString

Inserting Line-End Characters


In Smalltalk methods, certain conventions of indentation and line
wrapping make the code more readable. Sometimes a string disrupts the
readability of the code because it contains embedded carriage returns.

444 VisualWorks
Formatted Text and Fonts

Rather than embed returns in a string, you can substitute a backslash


character (\). Then, when you print the string, send a withCRs message to
the string to convert the backslashes back to carriage returns.
Dialog
request: 'This string\has 3 lines\when displayed.' withCRs
initialAnswer: 'No response needed'.

Note: This technique is not recommended for cross-cultural


applications, because it interferes with text lookup in message
catalogs. Instead, use separate strings and recombine them with
literal line-end characters.

Formatted Text and Fonts


A ComposedText object is the displayable counterpart of a String. A
ComposedText consists of a string plus a set of attributes that control the
appearance of that string, such as boldness and font. Typically, a
composed text is created when you want to customize the appearance of
the text that is displayed in a textual widget such as a text editor or a
label.
A Text object is an intermediate text object between a string and a
composed text. It holds a string plus an array of emphasis values that
apply to the string. Because the emphasis values can be interpreted only
by a composed text, a Text is used during operations that involve applying
boldness or other emphasis values to a composed text.

Creating a Formattable Text Object


The basic approach to creating a ComposedText object is to send an
asComposedText message to a string:
| string txt gc |
gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
string := ComposedText comment. "Read class comment"

txt := string asComposedText.

txt displayOn: gc at: 5@5.


The resulting composed text has no interesting format, however. To
format the text, we need to assign text attributes.

Application Developer’s Guide 445


Chapter 19 - Working with Text

Composed text display attributes are controlled by an instance of


TextAttributes. You can either create a new TextAttributes, or use one that is
already defined in the TextAttributes class by sending a styleNamed:
message to TextAttributes.
To create a ComposedText with attributes, we also need to use the
intermediate Text object. This object is created by sending the asText
message to a string.
Now we can create the composed text by sending a withText:style:
message to the ComposedText class:
| txt gc textStyle |
gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
txt := ComposedText comment asText.
textStyle := TextAttributes styleNamed: #large.

txt := ComposedText
withText: txt
style: textStyle.

txt displayOn: gc at: 5@5.


More interesting formatting options are discussed in the following
sections.

Displaying a Text Object


Because a ComposedText is a visual component, you can display it on a
window or other display surface.
This example gets the graphics context from the display surface by
sending a graphicsContext message. It then send a displayOn: message to
the composed text, with the graphics context of the display surface as
argument:
| txt gc |
txt := ComposedText comment asComposedText.
gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
txt displayOn: gc at: 5@5.
In practice, you seldom display text directly on a window. VisualWorks
provides a variety of textual widgets that are more appropriate targets for
textual objects. For example, the TextEditor widget available in the UI
Painter makes an appropriate target for composed text.

Controlling Line Length


By default, composed text word-wraps long sentences onto multiple lines,
to avoid running off the right edge of the display area.

446 VisualWorks
Formatted Text and Fonts

Setting Line Length


Line length is determined by the composition width of the composed text.
Normally, the composition width is adjusted automatically when a
composed text is displayed in a text widget, so setting the line length is
unnecessary.
If you are writing a displaying method (displayOn:) for a new text widget,
you need to handle and set line widths. In this example, a scratch window
is used as the display surface, illustrating a technique for controlling line
length.
The key is to send a compositionWidth: message to the composed text:
| txt gc |
gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
txt := VisualComponent comment asComposedText.

txt compositionWidth: 380.

txt displayOn: gc at: 5@5.


Variations of the ComposedText creation methods include forms for
specifying the width.
To get the current line length, send a compositionWidth message to the
text.
Changing the composition width has no effect when word wrapping has
been disabled in the text.
Controlling Word Wrap
It is sometimes desirable to disable the default word-wrapping feature for
composed text, for instance to display columnar material or other text that
would be disrupted by wrapping.
Word wrapping is controlled by the text widget (ComposedText), not the
text itself, because frequently a string is the “text” of a widget and a string
has no notion of wrappability. To turn off word wrapping, you turn it off in
the text widget itself.
If you turn off word wrapping, however, be sure to provide a horizontal
scroll bar on the text widget, or fix the size of the widget to ensure that it
is wide enough.
Send a wordWrap: message to the composed text. The argument is false
to disable wrapping, and true to turn it on.

Application Developer’s Guide 447


Chapter 19 - Working with Text

| txt gc |
gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
txt := ComposedText comment asComposedText.
txt compositionWidth: 380.

txt wordWrap: false.

txt displayOn: gc at: 5@5.

Controlling Line Format


Setting Alignment
By default, a composed text is left-aligned, starting each new line flush
against the left margin. Other alignments are preferable in some
situations. Composed text allows you to set the alignment.
ComposedText provides four messages to set the alignment: leftFlush,
rightFlush, centered, and justified.
| txt gc |
gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
txt := VisualComponent comment asComposedText.
txt compositionWidth: 380.

txt rightFlush.

txt displayOn: gc at: 5@5.


Setting Indents
With a composed text, you can set two indents, one indent for the first line
and another for all subsequent lines in the same paragraph. Indents are
measured in pixels.
To set the first line indent, send a firstIndent: message to the composed
text. The argument is the width, in pixels, of the first line’s indentation
from the left edge.
To set the indent for later lines, send a restIndent: message to the
composed text. The argument is the width of the indentation from the left
edge for all lines after the first line.

448 VisualWorks
Formatted Text and Fonts

| txt gc |
gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
txt := 'Line 1\Line 2\Line 3\Line 4'
withCRs asComposedText.
txt compositionWidth: 380.

txt firstIndent: 50.


txt restIndent: 100.

txt displayOn: gc at: 5@5.


Setting Tab Stops
You can set any number of tab stops for composed text. Tab settings are
controlled by the TextAttributes object that is held by the composed text.
Tab stops are measured in pixels.
When changing a composed text object’s attributes, first make a copy of
the attributes object, which you retrieve by sending a textStyle message.
You need a copy because the default text style for any composed text is a
systemwide object which, if changed, affects all texts that do not already
have custom attributes.
To define the tab stops, send a useTabs: message to the text style. The
argument is an array of integers specifying one or more tab settings.
Each setting indicates the number of pixels that tab stop is from the
restIndent setting.
If the array contains a single integer, that value is used as an increment,
and each tab is set as that distance from its predecessor.
Send a textStyle: message to the text with the style as the argument to set
the tab stops.

Application Developer’s Guide 449


Chapter 19 - Working with Text

| txt gc style tab |


gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
tab := String with: Character tab.
txt := ('Line 1\Line 2\Line 3\',
tab, '1 Tab\',
tab, tab, '2 Tabs\',
tab, tab, tab, '3 Tabs').
txt := txt withCRs asComposedText.
txt compositionWidth: 380.

txt firstIndent: 50.


txt restIndent: 100.

style := txt textStyle copy.


style useTabs: #( 15 20 25).
txt textStyle: style.

txt displayOn: gc at: 5@5.

Printing a Text Object


A composed text can be printed on paper very simply, by sending the
hardcopy message to the composed text. This technique assumes that
you have configured your system to send output to a printer. If you can
successfully print by using the hardcopy command in a System Browser,
you can also print a composed text as shown here:
| txt |
txt := Object comment asComposedText.

txt hardcopy.

Text String Operations


You can perform the same operations on composed text as you can
perform on strings, such as counting characters, searching and replacing
strings, and so on. These operations are actually defined for the Text
object contained in the composed text.

450 VisualWorks
Formatted Text and Fonts

Counting Characters
Text objects support a size message which returns the number of
characters in the text.
| composedText plainText |
composedText := Object comment asComposedText.

plainText := composedText text.


^plainText size
Search for Text
To search for text in composed text, you search through either its Text or
through the String contained in the text.
The following example is essentially the same as the example given
earlier for string searches, except that the search is performed on the text
object.
| composedText txt |
composedText := Object comment asComposedText.

txt := composedText text.


"Could use: txt := composedText string."

^txt
findString: 'Var*:'
startingAt: 1
ignoreCase: true
useWildcards: true.
Replacing Text
Replacing part of a ComposedText is very much as with a string. The
substitution text can be either a Text or a String. If the replacement text is
a Text, it can have boldfacing or other emphasis properties.
To replace text, send a replaceFrom:to:with: message to the composed text.
This method is defined in the ComposedText class, so you don’t need to
extract the text or string.
As with string replacements, the first and second arguments are integers
indicating the range of text to be replaced. The third argument is the
replacement text, which can be either a string or a text.

Application Developer’s Guide 451


Chapter 19 - Working with Text

| txt gc |
gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
txt := 'Red Green Blue' asComposedText.
txt compositionWidth: 300.

txt replaceFrom: 1 to: 3 with: 'BloodRed' asText allBold.

txt displayOn: gc at: 5@5.


Comparing Text Objects
A ComposedText can only tell whether it is the same object as another text,
that is, equality (=) tests the same as identity (==).
In most situations it is more useful to test the underlying Text objects,
which compare their underlying strings. The comparisons can then be
performed just like on strings.
| text1 text2 |
text1 := 'abcd' asComposedText text.
text2 := 'ABCD' asComposedText text.
^text1 > text2. " Returns true "
Copying a Range of Text
A ComposedText does not directly support copying a range of it, so copy
operations are performed on the underlying Text and used to create a new
composed text with the copied text. The text style can also be transferred
to the new composed text.
| composedText plainText descriptionEnd copy gc |
gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
composedText := Object comment asComposedText.
composedText compositionWidth: 300.

plainText := composedText text.


descriptionEnd := plainText
findString: 'Class Variables'
startingAt: 1.
descriptionEnd := descriptionEnd - 1.

copy := plainText copyFrom: 1 to: descriptionEnd.


copy asComposedText displayOn: gc at: 5@15.
The composition width and word-wrap setting are not copied in this
approach. If needed, these settings can also be copied.
To get the width of the original composed text, send a width message to it.
Then give this value to the copy by sending a compositionWidth: message.

452 VisualWorks
Character Formatting

To get the word-wrap property of the original composed text, send a


wordWrap message to it. Then give this value to the copy by sending a
wordWrap: message to it.
| composedText plainText descriptionEnd copy gc |
gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
composedText := Object comment asComposedText.
composedText compositionWidth: 300.

plainText := composedText text.


descriptionEnd := plainText
findString: 'Class Variables'
startingAt: 1.
descriptionEnd := descriptionEnd - 1.

copy := plainText copyFrom: 1 to: descriptionEnd.


copy := copy asComposedText.

copy compositionWidth: composedText width.


copy wordWrap: composedText wordWrap.
copy displayOn: gc at: 5@15.

Character Formatting
Character formatting for composed text is primarily controlled by two
objects contained in a composed text object: a Text object and a
TextAttributes object.
The Text object has two parts: a String and an array of modifiers that
indicate how each character in the string is formatted. Modifiers, which
are called emphases because the modifiers are often used to emphasize
a portion of a text, specify features such as bold, italic, color, and, to a
limited extent, character size.
More complete control over character formatting, including font selection,
is handled by the TextAttributes object for the composed text. VisualWorks
provides a default TextAttributes, which is used by composed text unless
an alternate is specified. The effect of modifiers applied to the Text object
are defined by the text attributes assigned to the composed text.

Application Developer’s Guide 453


Chapter 19 - Working with Text

Applying Character Variations


Character variations, such as bolding, underlining, and color, are
specified in a Text object’s array of modifiers.
Applying Boldfacing and Other Emphases
To apply an emphasis to a range of text, send an emphasizeFrom:to:with:
message to a Text. The first and second arguments identify the character
range to be modified. The third argument is the emphasis value.
Standard emphases are #bold, #italic, #serif, #underline, #strikeout, #large,
and #small.
| txt gc |
gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
txt := 'normal bold italic serif underline strikeout large small' asText.

txt emphasizeFrom: 8 to: 11 with: #bold.


txt emphasizeFrom: 13 to: 18 with: #italic.
txt emphasizeFrom: 20 to: 24 with: #serif.
txt emphasizeFrom: 26 to: 34 with: #underline.
txt emphasizeFrom: 36 to: 44 with: #strikeout.
txt emphasizeFrom: 46 to: 50 with: #large.
txt emphasizeFrom: 52 to: 56 with: #small.

txt displayOn: gc at: 5@25.


When two or more emphases apply to the same range of characters, as
when applying both bold and italic emphases, an array containing the
emphases is used as the third argument:
txt emphasizeFrom: 8 to: 18 with: #( #bold #italic).
txt emphasizeFrom: 20 to: txt size with: #( #large #bold #italic #underline).
When an entire text is to be given the same emphasis, you can send an
emphasizeAllWith: message to the Text. The argument is the emphasis
value or an array containing multiple emphasis values:
txt emphasizeAllWith: #( #bold #italic).
Because boldfacing an entire text is a common operation, a convenient
means of applying the #bold emphasis to a text is provided. Send an
allBold message to the Text.
txt allBold displayOn: gc at: 5@25.
Applying Color to Text
You can apply color to a text as an emphasis by specifying #color and
providing an argument. The argument is provided by making an
association, using the -> message.

454 VisualWorks
Character Formatting

Send an emphasizeFrom:to:with: message to the Text, with the third


argument as an association:
| txt gc boldBlue |
gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
txt := 'BLACK RED GRAY BOLDBLUE' asText.

txt emphasizeFrom: 7 to: 9 with: #color -> ColorValue red.


txt emphasizeFrom: 11 to: 14 with: #color -> ColorValue gray.

boldBlue := Array with: #bold with: #color -> ColorValue blue.


txt emphasizeFrom: 16 to: 23 with: boldBlue.

txt displayOn: gc at: 5@25.


A Pattern object can be associated with #colorinstead of a color, if desired.
Changing Font Size
Two standard text emphases, #small and #large, give you limited control
over the font size within a narrow range.
Send an emphasizeFrom:to:with: message to the composed text’s
underlying Text. The first and second arguments define the character
range by specifying the starting and stopping indexes. The third argument
is #small or #large. The actual size depends on the fonts available from the
operating system, and on some platforms it may not differ at all. To return
to the default size, apply a nil emphasis to the text.
| txt gc |
gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
txt := 'large small' asText.

txt emphasizeFrom: 1 to: 5 with: #large.


txt emphasizeFrom: 7 to: 11 with: #small.
txt displayOn: gc at: 5@25.

txt emphasizeAllWith: nil.


txt displayOn: gc at: 5@40.

Defining Text and Character Styles


Fonts and other character attributes are defined within a composed text’s
TextAttributes. While the structure of the style definition is quite complex,
there are several operations that can be performed relatively easily,
employing the more useful text attribute components.

Application Developer’s Guide 455


Chapter 19 - Working with Text

The general procedure for all of the operations described below is to


define a text style, which is a TextAttributes object. The text style specifies
a variety of attributes, including alignment, indents, leading, and
character attributes. Character attributes are, in turn, defined by a
CharacterAttributes object which, among other things, defines the font for
the style.
You can examine this structure by evaluating this expression, then
digging down into the component objects, especially the #textStyle
variable:
| txt style |
txt := 'Hello, World' asComposedText.
style := TextAttributes styleNamed: #systemDefault.
txt textStyle: style.
txt inspect.
The examples all build on the very simple structure of this expression.
Using the Platform Default Font
VisualWorks provides a virtual text style that corresponds to the default
font supplied by the underlying window manager, when applicable.
When the UI Look is set to something other than the host window
manager, VisualWorks selects a font that mimics the appearance of the
default font for that look. In the fonts menu, this is the System font. Thus, a
widget that uses the System font has the best chance of looking like other
applications on any platform on which it is deployed.
To apply the system default font to composed text, get the text style
nearest the platform default by evaluating:
TextAttributes styleNamed: #systemDefault.
Then send a textStyle: message to the composed text with the result of the
above as argument.
| txt gc style |
gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
txt := 'Hello, World' asComposedText.

style := TextAttributes styleNamed: #systemDefault.


txt textStyle: style.

txt displayOn: gc at: 5@25.

456 VisualWorks
Character Formatting

Defining a Custom Text Style


You create a custom text style by replacing values in a default style.
Some styles are replaced in the TextAttributes object and others are
replaced in the CharacterAttributes object.
Associated with each emphasis symbol in the CharacterAttributes is a block
that operates on a FontDescription. The font description controls font
selection by specifying the font size, family, boldness, and so on.
A simple “bottom up” procedure for constructing a custom text style is:
1 Create a new instance of FontDescription, and make desired changes.
The example uses the default font description. If you have a font
description that already has all or many of the desired characteristics,
it is useful to copy that definition and then make further changes.
2 Create a new instance of CharacterAttributes by sending a
newWithDefaultAttributes message to the CharacterAttributes class. This
message initializes the CharacterAttributes with the standard
emphases.
3 Install an instance of FontDescription in the new CharacterAttributes by
sending a setDefaultQuery: message.
4 Customize the CharacterAttributes as desired. For illustrative purposes,
the example defines a new emphasis called #title, which specifies that
the font must be 24 pixels in height.
If you create unusually large or small text, as in the example, you
need to adjust the line spacing and baseline of the text style. The
example does this by sending the lineGrid: and baseline: messages to
the text style. Refer also to “Adjusting the Line Spacing and Baseline”
below for another method.
The following sections explain other useful changes.
5 Create a new TextAttributes by sending a characterAttributes: message
to the TextAttributes class. The argument is the CharacterAttributes that
you customized in step 4.
6 Install the custom text style by sending a textStyle: message to the
composed text. The argument is the custom TextAttributes from step
5.
7 Apply the new emphasis to the desired portions of the composed
text’s underlying Text.

Application Developer’s Guide 457


Chapter 19 - Working with Text

| txt gc ca ta |
gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
txt := Object comment asComposedText.
txt compositionWidth: 300.

"Create and install a custom text style."


ca := CharacterAttributes newWithDefaultAttributes.
ca setDefaultQuery: txt textStyle defaultFont.
ca at: #title put: [ :fontDesc | fontDesc pixelSize: 24].
ta := TextAttributes characterAttributes: ca.
ta lineGrid: 27; baseline: 18.
txt textStyle: ta.

txt text emphasizeAllWith: #title.


txt displayOn: gc at: 5@25.
Set text typeface family
The default font belongs to Helvetica, Arial, or a similar font family,
depending on the operating system. For other fonts, set a text emphasis
attribute that chooses a font from a another typeface family. This
emphasis is available for any text style set for default character attributes.
Send an emphasizeFrom:to:with: message to the underlying Text of a
ComposedText. The first and second arguments identify the range of
characters to be affected. The third argument is an association between a
lookup key (#family) and the name string or an array of name strings for
the font family to use. The name may include the wildcard character ‘*’ to
match a family name with a partial description. The available font that
most nearly matches the name in the argument is used. If an array of
family names is specified, the first matching font family in the array is
used. If no font is available from any family name specified, a font from
the text style’s default font family is used.

458 VisualWorks
Character Formatting

| txt gc |
gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
txt := 'normal courier times helvetica terminal foobar' asText.

txt emphasizeFrom: 8 to: 14 with: #family->'courier'.


"Match Times New Roman or Times"
txt emphasizeFrom: 16 to: 20 with: #family->'times*'.

txt emphasizeFrom: 22 to: 30 with: #family->'helvetica'.


txt emphasizeFrom: 32 to: 39 with: #family->'terminal'.

“Use the default font for a family unknown to the installation”


txt emphasizeFrom: 40 to: 45 with: #family->'foobar'.

txt displayOn: gc at: 5@25.


Setting Font Family or Name
The default font is Helvetica, Arial, or a similar font, depending on the
operating system. Two of the built-in text emphases give you some
control over the choice of font family: #serif (for a font with serifs, such as
Times) and #sansSerif (for a font without serifs, such as Helvetica).
When you want to be more specific about the font family, you can create a
custom emphasis to do so.
Because an operating system may not supply the font family or name that
you specify, it’s a good idea to specify alternatives. You can also specify a
wildcard pattern for any of the three attributes, such as helv* to indicate
that a partial match is acceptable. You can also use the #serif and
#sansSerif emphases to guide the selection of an alternative. The family
attribute supersedes those settings, and the name attribute supersedes
the family.

Setting the Font by Family


To set the font family, send a family: message to the FontDescription you
create for defining the custom text style. (Refer to “Defining a Custom
Text Style” above.) The argument is an array containing one or more
strings.
Each string names a font family or a wildcard pattern for partial matching.
A string containing only an asterisk is frequently used as the final element
in the array to indicate that any alternate is preferable to a “font not found”
error.
Remember to adjust the line spacing to suit the font, if necessary, by
sending a gridForFont:withLead: message to the text style.

Application Developer’s Guide 459


Chapter 19 - Working with Text

"Create and install a custom text style."


fd := FontDescription new
family: #( 'bookman' 'times*' '*' );
serif: true;
fixedWidth: false;
pixelSize: 14.
ca := CharacterAttributes newWithDefaultAttributes.
ca setDefaultQuery: fd.
ta := TextAttributes characterAttributes: ca.
ta gridForFont: nil
withLead: 2.
txt textStyle: ta.
Setting the Font by Name
The most specific technique is to provide the name string that the
operating system uses to identify a particular font. This approach is useful
when, for example, you want to examine the operating system’s fonts.
Rather than specifying a family, you can specify a font by name by
creating a new FontDescription and sending a name: message to it. The
argument is a string that names a font family.
The full name string is coded, for example,
'System~16~700~0~0~0~ansi~1', and so is not easily used. You can
partially specify the name, however, using a wildcard, for example,
'System*'. If this pattern matches several fonts, the first match is used, so
control is not precise.
A reasonable scenario might be to retrieve the list of font names using:
Screen default listFontNames
This returns an array that can be used to populate a list box widget. The
user can then select the font in the list, and the name string can then be
used.
The example shortcuts this longer process, taking the list of available
fonts from the operating system and using the first one.
"Create and install a custom text style."
fd := FontDescription new
name: (Screen default listFontNames at: 1).
ca := CharacterAttributes newWithDefaultAttributes.
ca setDefaultQuery: fd.
ta := TextAttributes characterAttributes: ca.
ta gridForFont: nil
withLead: 2.
txt textStyle: ta.

460 VisualWorks
Character Formatting

Defining Custom Sizes


Because fonts are supplied by the operating system, and VisualWorks
runs on several different operating systems, fonts are specified flexibly by
describing the desired properties. This font description is held by a
CharacterAttributes, which in turn is held by a composed text’s text style.
Font size is just one of the properties you can set by modifying the font
description.
A limitation to bear in mind is that a composed text applies the same line
spacing to its entire text, so mixing font sizes is effective within only a
narrow range for each composed text. Separate instances of
ComposedText are recommended in such situations.
The first variant shows how to define a #title emphasis, which modifies the
pixel size in the font description for any parts of the text that have the #title
emphasis.
When mixing font sizes in the same composed text, bear in mind that a
single text can have only one setting for line spacing. The second variant
shows how to adjust the line spacing and the baseline to suit the largest
font you are using. When this produces unsatisfactory results for smaller
text, put the smaller text in its own ComposedText, with appropriate line
spacing.
The built-in text styles (#large and #small, for example) automatically
adjust their pixel sizes to suit the pixel density of the display device. This
resizing feature is especially useful when deploying your application on
different types of hardware. To incorporate it into your custom text style,
use VariableSizeTextAttributes instead of its parent class, TextAttributes, in
the following examples.
Setting Font Pixel Size
To set a font size, send a pixelSize: message to a FontDescription. The
argument is the desired font size in pixels.
On platforms such as MS Windows and on PostScript printers, font size is
usually measured in points. On MS Windows, the font pixel size
equivalent to a given point size is given by the following relationship for
most VGA or better screen resolutions:
pixelSize := (pointSize * (96/72) asFloat) rounded.
The example below creates a text style for a 22 pixel font given the
default font preferences.

Application Developer’s Guide 461


Chapter 19 - Working with Text

| txt gc ca ta fd |
gc := (ExamplesBrowser prepareScratchWindow) graphicsContext.
txt := Object comment asComposedText.

"Copy the default font description and set its pixel size to 22"
fd := txt textStyle defaultFont copy.
fd pixelSize: 22.
ta := TextAttributes defaultFontQuery: fd.
ta gridForFont: nil
withLead: 2.
txt textStyle: ta.

txt compositionWidth: 300.


txt displayOn: gc at: 5@25.
If the text style is scaled (the text style is an instance of
VariableSizeTextAttributes), then changing the pixel size of its FontDescription
has no effect in text size. Scaled text styles need to be rescaled instead.
Text styles #default, #small, #large, #systemDefault, and #fixed are scaled.
Send the message scalingFactor: to the scaled text style. The argument is
a ratio of the desired pixel size to the preferred pixel size.
Creating a Scaled Text Style
When text is displayed on different screen sizes or resolutions often there
is a need to resize text for better visibility. A text style based on an
instance of VariableSizeTextAttributes permits composed text to be
scaled relative to a single preferred font pixel size set for the VisualWorks
Locale. A VariableCharacterAttributes instance is used with a
VariableSizeTextAttributes to define the emphases and scaling applied to
a composed text. Instances of VariableSizeTextAttributes and
VariableCharacterAttributes will work in place of TextAttributes and
CharacterAttributes instances; all respond to the same methods to define
emphases and format text. The pixel size attribute for a FontDescription
installed in a VariableCharacterAttributes instance is not used however.
Instead, the scalingFactor: message to either a
VariableSizeTextAttributes or VariableCharacterAttributes instance
determines text size.
1 Create a new text style from VariableSizeTextAttributes by sending its
class the message defaultFontQuery: with a FontDescription. The
example uses the FontDescription from the default text style.
Alternately, an instance of VariableSizeTextAttributes may be created
by sending the message characterAttributes: with an instance of
VariableCharacterAttributes.

462 VisualWorks
Character Formatting

2 To scale the text style either larger or smaller than the preferred pixel
size send the message scalingFactor: to the text style from step 1.
The argument is a ratio of the desired pixel size to the preferred pixel
size. For example, if the preferred pixel size is 16 a scaling of 1.5
displays the font at a pixel size of 24.
3 Install the text style in the composed text by sending a textStyle:
message to the composed text. The argument is the text style from
step 2.

| gc fd largeScaledStyle smallScaledStyle txt |


gc := ExamplesBrowser prepareScratchWindow graphicsContext.
fd := TextAttributes default defaultFont.

"Create scaled text styles"


smallScaledStyle := VariableSizeTextAttributes
defaultFontQuery: fd.
smallScaledStyle scalingFactor: 0.5.
largeScaledStyle := VariableSizeTextAttributes
defaultFontQuery: fd.
largeScaledStyle scalingFactor: 2.

"Display text one half the preferred pixel size"


txt:= ComposedText withText: 'This text is scaled small'
style: smallScaledStyle.
txt displayOn: gc at: 5@25.

"Display text normal size"


txt:= 'This text is scaled normal' asComposedText.
txt displayOn: gc at: 5@50.

"Display text twice the preferred pixel size"


txt:= ComposedText withText: 'This text is scaled large'
style: largeScaledStyle.
txt displayOn: gc at: 5@75.
Defining an Emphasis for a Custom Size
To define a new emphasis, send an at:put: message to the
CharacterAttributes. The first argument is the name of the emphasis (#title
in the example). The second argument is a block that sends a pixelSize:
message to the block argument, with the desired size of the font in pixels.

Application Developer’s Guide 463


Chapter 19 - Working with Text

Then, create a new TextAttributes by sending a characterAttributes:


message to the TextAttributes class with the CharacterAttributes you have
defined. Install the custom text style in the composed text by sending a
textStyle: message to the composed text, as usual.
"Create and install a custom text style."
ca := CharacterAttributes newWithDefaultAttributes.
ca setDefaultQuery: txt textStyle defaultFont.
ca at: #title put: [ :fontDesc | fontDesc pixelSize: 24].
ta := TextAttributes characterAttributes: ca.
txt textStyle: ta.

txt text emphasizeFrom: 1 to: 6 with: #title.


Adjusting the Line Spacing and Baseline
As shown above in “Defining a Custom Text Style” you can specify the
line spacing and baseline of a text style by sending the lineGrid: and
baseline: messages to the text style.
The line grid is the number of spaces, in pixels, between two lines of text
in a paragraph.
The baseline is the height of a line, typically measured from the top of the
tallest character to the bottom of a standard character, a character
without decenders. For most fonts, the baseline is the height of the capital
“A” character.
"Create and install a custom text style."
ca := CharacterAttributes newWithDefaultAttributes.
ca setDefaultQuery: txt textStyle defaultFont.
ca at: #title put: [ :fontDesc | fontDesc pixelSize: 24].
ta := TextAttributes characterAttributes: ca.
ta lineGrid: 27; baseline: 18.
txt textStyle: ta.

txt text emphasizeAllWith: #title.


To change the line spacing for a named custom text style, send a
gridForFont:withLead: message to the TextAttributes of the composed text.
The first argument is the name of the text emphasis (#title). The second
argument is the leading, which is the vertical space to be left between
one line and the next, typically zero to two pixels. This adjusts both the
line spacing and the baseline to suit the font’s size.

464 VisualWorks
Character Formatting

"Create and install a custom text style."


ca := CharacterAttributes newWithDefaultAttributes.
ca setDefaultQuery: txt textStyle defaultFont.
ca at: #title put: [ :fontDesc | fontDesc pixelSize: 24].
ta := TextAttributes characterAttributes: ca.
ta gridForFont: #title
withLead: 2.
txt textStyle: ta.

txt text emphasizeAllWith: #title.

Adding a Custom Font to the Fonts Menu


If you define a font, you may want to add it to the fonts menu in the
property sheet for widgets. This involves adding a new TextAttributes to the
system’s dictionary of text styles.
To install the text style in the system’s dictionary of styles, send a
styleNamed:put: message to the TextAttributes class. The first argument is a
lookup name, specified as a Symbol. A capitalized version of the name will
appear in the fonts menu. The second argument is the custom text style.
| fd ca ta |
fd := FontDescription new
pixelSize: 24.
ca := CharacterAttributes newWithDefaultAttributes.
ca setDefaultQuery: fd.
ta := TextAttributes characterAttributes: ca.
ta gridForFont: fd withLead: 2.

TextAttributes styleNamed: #title put: ta.


Removing a text style from the system’s dictionary can be troublesome
when existing widgets specify that font. For that reason, no supported
mechanism for removing a font exists. The best approach is to replace
the text style that is associated with a particular name, in the same way
that you added the original text style. For this reason, we recommend that
you expand the fonts menu with caution.

Changing the Default Font


The default font that is used by VisualWorks tools to display textual
information can be changed, as shown in the example. Widgets for which
the Default font has been selected, both in system tools and in your
applications, are also affected. Because many of the widgets use the
System font by default, they will not be affected unless you change their
font property to Default.

Application Developer’s Guide 465


Chapter 19 - Working with Text

To set the default font, send a setDefaultTo: message to the TextAttributes


class. The argument is the Symbol that names the desired text style.

Note: The text style must have been defined previously and installed
in the fonts menu.

To refresh any open windows to use the new font, send a resetViews
message to the TextAttributes class.
TextAttributes setDefaultTo: #default.
TextAttributes resetViews.

Setting the Preferred Font Family


Each text style that displays a different typeface uses a FontDescription
with a font family list. It is often preferrable to have all text styles choose a
font from a common list of preferred font families, before choosing from its
own list. If no font in the common list is found, the text selects a font family
from its own list.
1 Send a preferredFontFamily: message to the current Locale.
The argument is an array with one or more font family name strings.
2 Refresh any open windows by sending a resetViews message to the
TextAttributes class. When they are redisplayed, they will use the new
family preferences.
Locale current preferredFontFamily: #('system' 'gill').
TextAttributes resetViews.
Normally the family preference list is empty. To clear family preferences
set the list to be empty.
Locale current preferredFontFamily: #().
To determine what the current font family preferences are, if any, send the
preferredFontFamily message to the current Locale.
^Locale current preferredFontFamily

Setting the Preferred Font Pixel Size


When a scaled text style is used such as the default, the font is scaled
relative to the preferred pixel size set for the current Locale. You can
globally resize all scaled text styles by changing the preferred font pixel
size.

466 VisualWorks
Character Formatting

1 Send a preferredPixelSize: message to the current Locale, with an


integer argument specifying the pixel size.
2 Refresh the windows that are already open by sending a resetViews
message to the TextAttributes class. When they are redisplayed, text
using a scaled text style will be resized.
Locale current preferredPixelSize:18.
TextAttributes resetViews.
To determine what the preferred font pixel size is send a preferredPixelSize
message to the current Locale.
^Locale current preferredPixelSize

Application Developer’s Guide 467


20
Files

This chapter describes how VisualWorks operates on files and directories


(also referred to as “folders”).
Most VisualWorks file and directory operations are unified in the abstract
class Filename, with platform specific operations handed down to its
subclasses. By programming to the Filename protocol for these operations,
VisualWorks can support these operations and remain a platform-
portable environment.
As an environment for creating cross-platform portable applications,
VisualWorks provides mechanisms for constructing file names and
performing file operations in a platform-neutral manner. The actual file
name and operation is determined by the platform the VisualWorks
application is running on.
File input and output operations are performed by reading from and
writing to streams (subclasses of Stream) that are opened on a file.

File Names
The Filename class supports operations involving disk files and directories.
Filename is an abstract class, and directs the creation message to the
appropriate subclass. This keeps your file-creating code general enough
to run on any of the supported platforms.
Filenames themselves are a platform problem, due largely to platform
specific separator characters in path names and disk volume specifiers.
LogicalFilename and its subclass PortableFilename provide mechanisms for
storing absolute and relative pathnames in a platform neutral form.

468 VisualWorks
File Names

Creating a Filename
To create a simple file or directory name object, send asFilename to a
string identifying the desired file or directory:
| name filename |
name := '[Link]'.
filename := name asFilename.
^filename
In this case the filename includes no directory information, and so the
named file is relative to whatever the current directory is. You can specify
path information in the string as well, for example:
‘mydirectory\[Link]’ asFilename
‘c:\mydirectory\[Link]’ asFilename
‘/usr/tmp/[Link]’ asFilename
The disk file or directory is not affected by the mere creation of a Filename
object. No link exists to the disk file or directory, so you do not need to
release an external resource at this point.

Constructing a Portable Filename


While the different operating systems supported by VisualWorks all use
directory paths for file names, they differ in significant ways. Unix/Linux
systems use the forward slash (/), Windows systems use a backward
slash (\), and Macintosh systems use a colon (:). Further, Unix/Linux
systems unify all directory structures under a single hierarchy, while
Windows systems use drive letters and machine names, and Macintosh
systems allow naming each disk drive. A portable application must be
able to use work with file names independently of these differences. The
method described in the previous section, if applied to a full path name, is
not portable.
To create a portable file name from a Filename, send the
asLogicalFileSpecification message to it, for example:
| name filename |
name := 'mydirectory\[Link]'.
filename := name asFilename asLogicalFileSpecification.
^filename
The path name can be absolute or relative, and may include path, disk,
and machine names, and may begin with a system variable specifying a
path.
System variables are specified with the syntax:
$(variablename)

Application Developer’s Guide 469


Chapter 20 - Files

For example, VisualWorks assumes that its home directory is set in the
VISUALWORKS system variable. In the Settings Tool there are several
references to directories using this variable, for example in specifying
parcel paths, such as $(VISUALWORKS)/parcels. Depending on the path,
the result is either an instance of LogicalFilename or PortableFilename:
• If the path is absolute, starting with root, or with a machine or disk
specification, the system reders it as a LogicalFilename. The result is
not generally portable.
• If the path is relative, or begins with a system variable, the system
renders it as a PortableFilename. The result is generally portable.
To maximize portability, use only constructs that produce a
PortableFilename. Use system variables to ensure a portable root path
segment.

Creating a File or Directory


When the disk file does not already exist, it is created when a write
stream is opened on it, or when the first character is written to it. A
directory must be explicitly created.
The technique shown in the basic step works well for creating a file in the
working directory. You can also use that approach with a full pathname
that includes directory separators, but the separator character differs
across platforms, so you would be compromising the portability of your
application.

Creating an Empty File


VisualWorks creates an empty file as soon as a write stream
(WriteStream) is opened on the file name. A simple way to create an
empty file is to open the write stream, and then close it again:
| newFile stream |
newFile := 'testFile' asFilename.
stream := newFile writeStream.
stream close.
^stream
Normally, you would write to the stream before closing, writing data to the
file.

470 VisualWorks
Getting File Information

Creating a New Disk Directory


To create a directory, send a makeDirectory message to the Filename
representing the desired directory. The last path component is created as
a subdirectory of the directory specified by the prefixed path.
| directory |
directory := 'test' asFilename. "Directory relative to current directory"
directory makeDirectory.
directory := 'c:\temp\test' asFilename. "Absolute directory path"
dirctory makeDirectory.
^directory exists
If the disk directory already exists, or if the parent directory does not
exist, an error results.

Getting File Information


Often you need to collect information about a file; whether it exists, its
size, directory, and so on. VisualWorks provides messages for retrieving
this kind of information.

Testing for Existence


The exists message checks for the existence of a Filename on disk. If the
disk file or directory exists, true is returned.
| unlikelyFile |
unlikelyFile := 'qqqqzzzz' asFilename.
^unlikelyFile exists

Getting the Size of a File


Send a fileSize message to the Filename. If the file exists, the number of
characters it contains is returned. If the file does not exist, an error
results. If the Filename represents a disk directory rather than a disk file,
zero is returned.
| newFile stream |
newFile := 'testFile' asFilename.
stream := newFile writeStream.
stream nextPutAll: Object comment.
stream close.

^newFile fileSize.

Application Developer’s Guide 471


Chapter 20 - Files

Getting and Setting the Working Directory


The default directory for file operations is held in the shared variable
DefaultDirectoryString, which is initially set to the OS current directory upon
starting VisualWorks. To get this directory, send a defaultDirectory
message to the Filename class. A Filename representing the working
directory is returned.
| workingDir |
workingDir := Filename defaultDirectory.
^workingDir
To change the current directory, send beCurrentDirectory to a Filename
specifying a directory. For example:
( Filename named: '\vw7.2\bin' ) beCurrentDirectory.
This both changes the OS current directory for the VisualWorks session
and updates DefaultDirectoryString.
Note, however, especially for multi-threaded operations (multi-proc UI),
that the OS current directory can change underneath the current process
without DefaultDirectoryString being updated. If this occurs, a file access
operation running in one process and relying on a relative file name might
produce incorrect results (attempt to access a file in the wrong location) if
another process changes only the underlying OS current directory.
This happens in Windows environments, for example, if the native file
dialog is used to navigate the file system, because the dialog changes
the OS current directory during navigation without updating
DefaultDirectoryString. The correct directory is restored, however, once the
dialog is closed.
Accordingly, it is risky to rely on relative path names for file operations in
a multi-process application, and file access should be protected by
constructing an absolute path from a relative path, and using that for file
access.
To get the OS current directory, send:
Filename findDefaultDirectory.

Getting the Parent Directory


Send a directory message to the Filename. A Filename representing the
parent directory is returned.

472 VisualWorks
Getting File Information

| dir parentDir |
dir := Filename defaultDirectory.

parentDir := dir directory.


^parentDir

Getting the Parts of a Pathname


A Filename has a head and a tail. The head is the directory part of the
pathname, and the tail is the final file or directory name. The head and tail
messages return their respective parts as strings:
| filename pathString dirString fileString |
filename := Filename defaultDirectory.

pathString := filename asString.


dirString := filename head.
fileString := filename tail.

^'
PATH: ', pathString, '
DIRECTORY: ', dirString, '
FILE: ', fileString

Distinguishing a File from a Directory


You can test whether a Filename is a file or a directory by sending the
isDirectory message to it. The message returns true if the filename is a
directory, and false otherwise. If neither a file nor a directory exists with
the matching name, an error results.
| dir |
dir := Filename defaultDirectory.
^dir isDirectory

Getting the Access and Modification Times


Depending on the operating system, you can retrieve specific access
information for a file.
To get the access information, send a dates message to the Filename. This
returns a dictionary. Send an at: message to the dictionary, with one of
these arguments:
#accessed
The time the file’s contents were last accessed.
#modified
The time the file was last modified.

Application Developer’s Guide 473


Chapter 20 - Files

#statusChanged
The time of the most recent change in external attributes of the file,
such as ownership and permissions.
If the operating system does not support the requested type of
information, nil is returned; otherwise, an array containing a date and a
time is returned.
| newFile stream datesDict modifyDates modifyDate modifyTime |
newFile := 'testFile' asFilename.
stream := newFile writeStream.
stream nextPutAll: Object comment.
stream close.
datesDict := newFile dates.
modifyDates := datesDict at: #modified.
modifyDates isNil
ifFalse: [
modifyDate := modifyDates first.
modifyTime := modifyDates last].
^'
MODIFIED: ', modifyDate printString, ' at ', modifyTime printString

Getting File or Directory Contents


The contents of a disk file can be accessed in the form of a string. The
contents of a directory can be accessed in the form of an array of strings
naming files and subdirectories.

Getting the Contents of a File


Send a contentsOfEntireFile message to a Filename representing a disk file.
A string is returned.
| newFile stream contents |
newFile := 'testFile' asFilename.
stream := newFile writeStream.
stream nextPutAll: Object comment.
stream close.

contents := newFile contentsOfEntireFile.


^contents

Getting the Contents of a Directory


Send a directoryContents message to a Filename representing a disk
directory. An array of file and subdirectory names is returned.

474 VisualWorks
System Variables

| workingDir contents |
workingDir := Filename defaultDirectory.

contents := workingDir directoryContents.


^contents

System Variables
Operating systems use system variables for a variety of purposes,
typically related to the directory path locations of required resourses.
VisualWorks relies on one system variable, $(VISUALWORKS), as the
directory whose subdirectories contain its resources. If you installed the
working directory option during installation, there is also a $(WORK_VW).
A common system variable is PATH, which holds a list of directory paths
to executable programs.
Within VisualWorks, system variables are written as above, with the
variable name enclosed in parentheses, and preceeded by $.
System variables are generally used to specify a directory path relative to
the value held in the variable. To create a Filename instance from a String
containing a system variable, send an expandEnvrionmentIn: message to
Filename:
Filename expandEnvironmentIn: '$(VISUALWORKS)\bin'
This returns a ByteString. To get a Filename instance, send asFilename to the
ByteString:
( Filename expandEnvironmentIn: '$(VISUALWORKS)\bin' ) asFilename

Storing Text in a File


Putting data into a disk file involves using a stream to funnel the
characters to the file. A stream holds onto an external resource, which
must be released by closing the stream.
When your intention is to create a new disk file, it’s a good idea to test the
Filename to make sure a file with the same name does not already exist.
When your application will be deployed on a UNIX system, it’s also
advisable to make sure the user has the appropriate file permissions.

Application Developer’s Guide 475


Chapter 20 - Files

Writing a Stream to a File


The basic way of writing a stream to a file overwrites any existing
contents in the file. In many cases, this is acceptable, but it is the
responsibility of your application to do the right thing.
To write to a file, create a write stream on the file by sending a writeStream
message to the Filename. Then write to the stream by sending a nextPutAll:
message to the stream, with a string as argument. The write operation
can be repeated for a series of strings, and each successive string is
appended to the file until the file is closed.
Close the stream by sending a close message to it. This closes the file
and releases the resource.
| newFile stream |
newFile := 'testFile' asFilename.
stream := newFile writeStream.
stream nextPutAll: Object comment.
stream close.

^newFile contentsOfEntireFile

Appending Text to a File


Often you want to append data to a file rather than write the whole file
over again. To open a file for appending data, send an appendStream
message to the Filename:
| filename stream |
filename := 'testFile' asFilename.

"Creating the file."


stream := filename writeStream.
stream nextPutAll: 'FIRST STRING'.
stream close.

"Appending"
stream := filename appendStream.
stream nextPutAll: ' -- SECOND STRING'.
stream close.

Deleting a File or Directory


For file maintenance operations, your application may need to delete
directories or files.

476 VisualWorks
Copying or Moving a File

To delete either a file or a directory, send a delete message to the Filename.


If necessary, confirm that the disk file or directory to be deleted exists by
sending an exists message to the Filename.
| newFile stream pretest posttest |
newFile := 'testFile' asFilename.
stream := newFile writeStream.
stream nextPutAll: Object comment.
stream close.
pretest := newFile exists.

newFile delete.
posttest := newFile exists.

^'
EXISTS BEFORE DELETION: ', pretest printString, '
EXISTS AFTER DELETION: ', posttest printString.
On operating systems such as UNIX that support multiple pathnames for
the same physical disk file or directory, deleting as shown here removes
the reference that is identified by the pathname, but it does not delete the
physical file or directory if another reference exists.

Copying or Moving a File


Copying a File
To make a copy of a file, send a copyTo: message to the Filename. The
argument is a string containing the pathname of the copy. If the Filename
represents a directory or a nonexistent disk file, an error results.
| newFile stream |
newFile := 'testFile' asFilename.
stream := newFile writeStream.
stream nextPutAll: Object comment.
stream close.

newFile copyTo: '[Link]'.

^'[Link]' asFilename exists.

Moving a File
To move a file to another directory, send a moveTo: message to the
Filename. The argument is a string containing the new pathname, which
can include a different directory. If the Filename represents a directory or a
nonexistent disk file, an error results.

Application Developer’s Guide 477


Chapter 20 - Files

| newFile stream |
newFile := 'testFile' asFilename.
stream := newFile writeStream.
stream nextPutAll: Object comment.
stream close.

newFile moveTo: '[Link]'.

^'[Link]' asFilename exists.

Renaming a File
To rename a file send a renameTo: message to the Filename. The argument
is a string containing the new pathname, which can include a different
directory. If the Filename represents a directory or a nonexistent disk file,
an error results.
Renaming a file is more efficient than moving the file.
| newFile stream |
newFile := 'testFile' asFilename.
stream := newFile writeStream.
stream nextPutAll: Object comment.
stream close.

newFile renameTo: '[Link]'.

^'[Link]' asFilename exists.

Comparing Two Files or Directories


It is often necessary to compare the contents of files or directories. You
do this essentially by string comparisons on the contents of the files or
directories, as shown in the following sections.
When comparing two files or directories, remember the difference
between a Filename and the disk object that it represents.
Two Filenames are equal when they have the same pathname. To compare
two filenames, send an = message to one Filename. The argument is the
second Filename. If they have the same pathname (that is, they point to
the same physical disk file), true is returned.

478 VisualWorks
Comparing Two Files or Directories

Comparing Two Filenames or Two Files


To compare the contents of two disk files, get the contents of each file by
sending contentsOfEntireFile messages to the Filenames. Then send an =
message to one of the resulting strings, with the other string as the
argument.
| file1 file2 stream pathsAreEqual contentsAreEqual |
file1 := 'fileOne' asFilename.
file2 := 'fileTwo' asFilename.
stream := file1 writeStream.
stream nextPutAll: Object comment.
stream close.
file1 copyTo: file2 asString.

pathsAreEqual := (
file1 = file2).
contentsAreEqual := (
file1 contentsOfEntireFile = file2 contentsOfEntireFile).

^'
PATHS ARE EQUAL: ', pathsAreEqual printString, '
CONTENTS ARE EQUAL: ', contentsAreEqual printString.

Comparing Two Filenames or Two Directories


To compare the contents of two disk directories, get the contents of each
directory by sending directoryContents messages to the Filenames. Then
send an = message to one of the resulting arrays, with the other array as
the argument.
| dir1 dir2 pathsAreEqual contentsAreEqual |
dir1 := Filename defaultDirectory.
dir2 := dir1 directory.

pathsAreEqual := (
dir1 = dir2).
contentsAreEqual := (
dir1 directoryContents = dir2 directoryContents).

^'
PATHS ARE EQUAL: ', pathsAreEqual printString, '
CONTENTS ARE EQUAL: ', contentsAreEqual printString.

Application Developer’s Guide 479


Chapter 20 - Files

Printing a File
Some operating systems support printing a text file directly, and others
require that it first be converted to PostScript or another printer-specific
format. VisualWorks supports several approaches for printing files. Only
basic text printing is covered here.

Print a Text File


The hardcopy message provides a basic print command for text files that
works regardless of the operating system.
Get the contents of the text file by sending a contentsOfEntireFile message
to the Filename. Convert the resulting string to a ComposedText by sending
an asComposedText message to it. Then, print the composed text by
sending a hardcopy message to it.
| newFile stream contents composedText |
newFile := 'testFile' asFilename printTextFile.
stream := newFile writeStream.
stream nextPutAll: Object comment.
stream close.

contents := newFile contentsOfEntireFile.


composedText := contents asComposedText.
composedText hardcopy.

Printing a File Directly


Some operating system environments support printing a text file directly.
This avoids the overhead of converting the text to a ComposedText.
Send a printTextFile message to the Filename. If text file printing is not
supported by the operating system, an error results.
| newFile stream |
newFile := 'testFile' asFilename printTextFile.
stream := newFile writeStream.
stream nextPutAll: Object comment.
stream close.

newFile printTextFile

Writing Fields to a Data File


By using a designated character, such as a comma or a colon, to
separate fields of textual data, you can use a text file as a basic form of
database.

480 VisualWorks
Printing a File

This example shows how to create and edit a data file that contains
comma-delimited fields.
To build the records and fields, create a block in which, for each field of
data, a nextPutAll: message is sent to the stream with the data string as
argument, followed by a nextPut: message with the separator character as
argument.
Send a valueNowOrOnUnwindDo: message to the data-writing block. The
argument is another block that closes the stream by sending a close
message to it.
| dataFile stream separator writingBlock |
dataFile := 'dataFile' asFilename.
separator := $,."comma"

stream := dataFile writeStream.


writingBlock := [
ColorValue constantNames do: [ :color |
stream nextPutAll: color.
stream nextPut: separator]].
writingBlock valueNowOrOnUnwindDo: [stream close].

Reading Fields from a Data File


Files created as above can also be read back. Often, database programs
also have an export capability for writing comma delimited files, or files
using some other character delimiter. You can use a stream to read these
files as well.
Create a block in which the next field of data is fetched by sending an
upTo: message to the stream, with the separator character as the
argument. This is repeated by placing it within an inner block that is
repeated until the end of the stream is encountered.
Send a valueNowOrOnUnwindDo: message to the data-reading block. The
argument is another block that closes the stream by sending a close
message to it.

Application Developer’s Guide 481


Chapter 20 - Files

| dataFile stream separator writingBlock colorNames readingBlock |


dataFile := 'dataFile' asFilename.
separator := $,."comma"

"Write data"
stream := dataFile writeStream.
writingBlock := [
ColorValue constantNames do: [ :color |
stream nextPutAll: color.
stream nextPut: separator]].
writingBlock valueNowOrOnUnwindDo: [stream close].

"Read data"
stream := dataFile readStream.
colorNames := OrderedCollection new.
readingBlock := [
[stream atEnd] whileFalse: [
colorNames add: (stream upTo: separator)]].
readingBlock valueNowOrOnUnwindDo: [stream close].

^colorNames

Setting File Permissions


On operating systems such as UNIX that support file and directory
permissions, the permission to change a file can be added or removed.
The most general permission is affected—when possible, the permission
change applies to everyone else in addition to the current user.
You can also ask a Filename whether the associated disk file or directory
can be written to, which is a portable operation that can be used on any
operating system.
• To remove the permission to change the contents of a file or
directory, send a makeUnwritable message to the Filename.
• To restore the writing permission, send a makeWritable message.
• To find out whether the writing permission is enabled, send a
canBeWritten message. If the file or directory does not exist, a
response of true indicates that the parent directory is writable. The
canBeWritten test works on all operating systems.

482 VisualWorks
Setting File Permissions

"Print it"
| newFile stream removed restored |
newFile := 'testFile' asFilename.
stream := newFile writeStream.
stream nextPutAll: Object comment.
stream close.

newFile makeUnwritable.
removed := newFile canBeWritten.

newFile makeWritable.
restored := newFile canBeWritten.

^'
PERMISSION REMOVED: ', removed printString, '
PERMISSION RESTORED: ', restored printString.

Application Developer’s Guide 483


21
Binary Object Files (BOSS)

The VisualWorks Binary Object Streaming Service (BOSS) allows you to


store objects in a compact, binary format in an external file. Typically,
BOSS is used to store object instances, rather than classes, but there are
cases for storing classes as well.
For many of the uses to which BOSS has been employed in the past,
parcels provide a more efficient mechanism. However, parcels do not yet
support an object streaming interface, and so BOSS remains the only
supported method for this.
BOSS is intended for storing data objects, not interface objects.
Accordingly, avoid using BOSS for storing objects that are tied to the
windowing system or the execution machinery, such as Window, Context,
and BlockClosure. Also, avoid circular references, such as an application
model that holds onto a window that holds onto the application model,
and so on.
To begin using BOSS, you must first load the BOSS support parcel.
Using the Parcel Manager (select Tools ­ Parcel Manager... in the Launcher
window), open the “Suggestions” category for Application Development, and
click on BOSS; then select Load... from the <Operate> menu.

Storing Objects in a BOSS File


You store objects to a BOSS file by creating a write stream, and then
writing binary data onto the stream, as follows:
1 Create a data stream, typically a writeStream on a Filename.
2 Create a BinaryObjectStorage by sending an onNew: message to that
class, with the data stream as argument.

484 VisualWorks
Storing Objects in a BOSS File

3 Store each data object by sending a nextPut: message to the


BinaryObjectStorage, with the data object as argument.
This operation should be enclosed in a block, and with a ensure:
message sent to that block. The argument is another block in which
the stream is closed. This guards against leaving the file open when
an error or interrupt occurs.
| dataObject dataStream bos |
dataObject := PointExample x: 3 y: 4 z: 5.

dataStream := 'points.b' asFilename writeStream.


bos := BinaryObjectStorage onNew: dataStream.

[bos nextPut: dataObject]


ensure: [bos close].

Storing a Collection of Objects


Send a nextPutAll: message to the BinaryObjectStorage, instead of nextPut:,
with a collection of objects as argument. Each element in the collection is
stored separately, enabling you to access them separately later.
| dataCollection bos |
dataCollection := ColorValue constantNames.
bos := BinaryObjectStorage
onNew: 'colors.b' asFilename writeStream.

[bos nextPutAll: dataCollection]


ensure: [bos close].

Appending an Object to a File


1 Create a read-append data stream, by sending a readAppendStream
message to the Filename.
2 Create a BinaryObjectStorage by sending an onOld: message to that
class, with the data stream as the argument.
3 Set the writing position to the end of the file by sending a setToEnd
message to the BinaryObjectStorage.
4 For each object to be appended, send a nextPut: message to the
BinaryObjectStorage, the data object as argument.

Application Developer’s Guide 485


Chapter 21 - Binary Object Files (BOSS)

| colorNames newColor bos |

"First create a file containing color names."


colorNames := ColorValue constantNames.
bos := BinaryObjectStorage
onNew: 'colors.b' asFilename writeStream.
[bos nextPutAll: colorNames]
ensure: [bos close].

"Then append a new color name."


newColor := #mudBrown.
bos := BinaryObjectStorage
onOld: 'colors.b' asFilename readAppendStream.
bos setToEnd.
[bos nextPut: newColor]
ensure: [bos close].

Getting Objects from a BOSS File


You can retrieve either the entire contents of a BOSS file, or selectively
retrieve individual objects stored in it.

Retrieving All Objects


To retrieve the entire contents of a BOSS file:
1 Create a data stream, typically by sending a readStream message to a
Filename that represents the data file.
2 Create a BinaryObjectStorage by sending an onOld: message to that
class, with the data stream as argument. (When you do not intend to
write new objects onto the file, send an onOldNoScan: message
instead; this is faster because it does not scan the data file as it must
before writing more data.)
3 Get the objects in the file by sending a contents message to the
BinaryObjectStorage. An array containing the stored objects will be
returned.
4 Close the BinaryObjectStorage (which also closes the data stream).

486 VisualWorks
Getting Objects from a BOSS File

| colorNames bos array |

"First create a file containing color names."


colorNames := ColorValue constantNames.
bos := BinaryObjectStorage
onNew: 'colors.b' asFilename writeStream.
[bos nextPutAll: colorNames]
ensure: [bos close].

"Read the file contents"


bos := BinaryObjectStorage
onOldNoScan: 'colors.b' asFilename readStream.
[array := bos contents]
ensure: [bos close].

^array

Searching Sequentially for an Object


For selective access to the objects in the data stream, you can read them
sequentially until you find the desired object.
1 Create a block in which you test whether the end of the data stream
has been reached by sending an atEnd message to the
BinaryObjectStorage.
2 Send a whileFalse: message to the block. The argument is another
block, in which you get the next object in the data stream by sending
a next message to the BinaryObjectStorage. Test the object to find out
whether it is the desired object; if so, send a setToEnd message to the
BinaryObjectStorage to break out of the loop.
3 Close the BinaryObjectStorage.
| points bos foundObject nextObject |

"First create a file containing points."


points := OrderedCollection new.
1 to: 100 do: [ :coord |
points add: (PointExample x: coord y: coord z: coord)].
bos := BinaryObjectStorage
onNew: 'points.b' asFilename writeStream.
[bos nextPutAll: points]
ensure: [bos close].

Application Developer’s Guide 487


Chapter 21 - Binary Object Files (BOSS)

"Search sequentially."
foundObject := nil.
bos := BinaryObjectStorage
onOldNoScan: 'points.b' asFilename readStream.
[[bos atEnd]
whileFalse: [
nextObject := bos next.
(nextObject z > 45)
ifTrue: [
foundObject := nextObject.
bos setToEnd]]]
ensure: [bos close].

^foundObject

Getting an Object at a Specific Position


Another selective approach is to position the stream at the beginning of
the desired object. This technique, although swifter than reading each
object sequentially, assumes that your application keeps a position index
for each object in the file when the objects are stored.
1 Create a dictionary to be used as a lookup table. Each entry in the
dictionary will associate an object’s identifier with that object’s
position in the BOSS file.
2 Before each object-writing operation, record the binary stream’s
position in the lookup table.
3 After each object-writing operation, send a forgetInterval: message to
the binary stream. The argument is an Interval beginning with the
binary stream’s index before the write operation and ending with the
next index. This assures that the BinaryObjectStorage will not make use
of back-references to the object just stored when storing future
objects; such back-references thwart random access to stored
objects.
4 When reading the desired object, first send a position: message to the
binary stream. The argument is the object’s position, as recorded in
the lookup table.
5 To get the object at that position, send a next message to the binary
stream.

488 VisualWorks
Storing and Getting a Class

| bos foundObject positions prevIndex |


positions := Dictionary new.
bos := BinaryObjectStorage onNew: 'colors.b' asFilename writeStream.
prevIndex := bos nextIndex.

"First create a file containing colors."


[ColorValue constantNames do: [ :name |
positions at: name put: bos position.
bos nextPut: (ColorValue perform: name).
bos forgetInterval: (prevIndex to: bos nextIndex).
prevIndex := bos nextIndex]]
ensure: [bos close].

"Get the object at a certain location."


bos := BinaryObjectStorage onOld: 'colors.b' asFilename readStream.
[bos position: (positions at: #chartreuse).
foundObject := bos next]
ensure: [bos close].

^foundObject

Storing and Getting a Class


A BinaryObjectStorage is most often used to store instances rather than
classes, relying on the virtual image to contain the class definitions.
When the virtual image that is to read a BOSS file does not contain the
necessary classes, you can use BOSS, parcels, or file-ins to add the
necessary class definitions.
Unlike the file-in procedure, the BOSS technique does not normally
require the presence of any compilers in the receiving image. Thus, you
can use BOSS to introduce a new or redefined class into a deployment
image, perhaps as a means of delivering a patch that fixes a bug.
Note, however, that BOSSing in a class does require the Smalltalk
compiler to be present when any superclass of that class varies in
structure between the receiving image and the original image. In
particular, if any superclass varies between these two images with
respect to the number or order of its instance variables, BOSS will
attempt to invoke the Smalltalk compiler to recompile the class’s
methods.

Application Developer’s Guide 489


Chapter 21 - Binary Object Files (BOSS)

When a collection of classes is stored using BOSS, they are


automatically sorted into superclass order. BOSS writes the same
information that fileOut does: the class definition, method definitions, and
an expression that initializes the class if a class initialize method is
present.
By default, BOSS stores the source code for methods, the class
comment, and the protocols. To control whether source code is stored
with a class, send a sourceMode: message to the binary stream before
storing the classes. The argument is either #discard, to omit source code,
or #keep, to include source code.

Storing a Collection of Classes


To store a collection of classes in a BOSS file, send a nextPutClasses:
message to a binary stream. The argument is a collection containing the
desired classes.

Loading a Collection of Classes


To load a collection of classes from a BOSS file, send a nextClasses
message to a binary stream on the file. (In the example, loading the Date
class has no effect because the image already contains the same
definition of that class.)
| file bos |
file := 'date.b' asFilename.
bos := BinaryObjectStorage onNew: file writeStream.

"Write the Date class to a file."


[bos nextPutClasses: (Array with: Date)]
ensure: [bos close].

"Read the file contents"


bos := BinaryObjectStorage onOldNoScan: file readStream.
[bos nextClasses]
ensure: [bos close].

^file fileSize

490 VisualWorks
Converting Data After Changing a Class

Converting Data After Changing a Class


When you store instances of an object in a BOSS file and then add an
instance variable or otherwise change the definition of that object’s class,
BOSS detects the incompatibility when it tries to read the old data file.
For example, suppose the PointExample class began its life representing a
two-dimensional point, and later you extend it to represent three-
dimensional points by adding a z instance variable. The following
procedure show how to arrange for old files containing two-dimensional
instances of PointExample to be read without error.
1 In the class whose definition has been changed, create a class
method named binaryRepresentationVersion. This method is
responsible for returning a version identifier, commonly a sequential
number or a descriptive string. (The method must be rewritten each
time the class definition is changed, assuming BOSS files relying on
the prior version of the class definition will need to be read.)
2 Create a class method named binaryReaderBlockForVersion:format:.
This method must return a block that converts the old object to a new
instance. The block takes one argument, an array of the instance
variables (for pointer-type objects) or a ByteString (for byte-type
objects). The block typically assigns the data values from the old
instance variables and then sends a become: message to the old
object; the argument is the new instance. The first method argument
(oldVersion) identifies the version (nil, by default, and later defined by
the method you created in the preceding step) and enables you to
distinguish between old data and current data. The second method
argument (oldFormat) is typically ignored except for internal system
purposes.
binaryRepresentationVersion
"First version (nil) had x and y coordinates.
Second version (2) added a z coordinate."

^2

Application Developer’s Guide 491


Chapter 21 - Binary Object Files (BOSS)

binaryReaderBlockForVersion: oldVersion format: oldFormat


| newPoint |
oldVersion isNil ifTrue: [
^[ :oldPoint |
newPoint := PointExample new.

"Each oldPoint obtained from the BOSS file is an Array


that contains the state of an old instance of PointExample.
The array elements are the values of the old instance’s
variables, in the order in which the old version of
PointExample defined them."

newPoint x: (oldPoint at: 1).


newPoint y: (oldPoint at: 2).
newPoint z: 0.

oldPoint become: newPoint]].

Customizing the Storage Representation


By default, BOSS stores the entire contents of an object, including its
dependents and the dependents of its variables. Although this default is
appropriate for most data objects, it results in a BOSS error when an
interface object is a dependent of a data object that is being BOSSed out.
This kind of dependency is often encountered in the case of an instance
variable that holds onto a collection when the collection is displayed in a
list widget. BOSSing a copy of the collection is one way to remove the
dependency.
The example shows a technique for controlling which parts of an object
are BOSSed out. This technique is also useful when an instance variable
holds an object that points back to the original object.
The basic approach is to create an instance method named
representBinaryOn: in the class whose BOSS representation you want to
customize. The method typically returns a MessageSend, which is created
by sending a receiver:selector:arguments: message to that class. The
receiver argument identifies the class that is to create an instance,
typically the object’s class. The selector argument is the name of the
instance-creation method that is to be used when the data is read by
BOSS. The arguments argument is a collection of data values, typically the
values of the object’s instance variables.

492 VisualWorks
Customizing the Storage Representation

representBinaryOn: bos
"Represent a PointExample by its x, y and z coordinates
plus the message and receiver for creating an instance from
those coordinates."

^MessageSend
receiver: self class
selector: #x:y:z:
arguments: (Array with: x with: y with: z).

Application Developer’s Guide 493


22
Refactoring

Developing reusable software typically involves many design iterations.


Each iteration may introduce new requirements that change or extend the
original design. Simultaneously, the excesses of the original design may
be corrected or improved through deeper architectural changes.
This iterative process of re-architecting a design may be described as
code refactoring. Refactoring is a common development strategy that has
been formalized into a set of practices for reorganizing code while
preserving its behavior.
Whereas re-working or re-writing code may involve dramatic changes in
functionality, refactoring is an intermediate step that generally doesn’t
disturb the behavior of an application. Refactoring can help when tackling
reusability problems, but its primary goals are to clarify abstractions, to
simplify and thereby improve the code design.
The VisualWorks 7 browser provides full functionality for code refactoring.
This chapter provides an overview of the individual refactorings, and
shows you how to perform a few of the more common design changes
using code refactorings.
For a more in-depth discussion of the methodology of refactoring, you
may consult a number of articles on the Web and several books currently
in print. In particular, we recommend:
Refactoring: improving the design of existing code
By Martin Fowler
Contributions by Kent Beck, John Brant, William Opdyke, and Don Roberts.
Reading, MA : Addison-Wesley, 1999
ISBN: 0201485672.

494 VisualWorks
Overview

Overview
The VisualWorks browser provides over two-dozen distinct refactoring
operations for manipulating classes, methods, and individual statements
within a method. Refactoring operations are thus class-, method-, or
statement-oriented.
Class-oriented refactorings
These operate on classes, instance variables, and class variables
and are available on the browser’s Class menu (for details, see
“Refactoring Classes” on page 502).

Class-oriented Refactorings
Create a Subclass

Rename a Class and its References

Safely Remove a Class


Change a Class to a Sibling

Add a Variable

Rename a Variable and its References


Remove a Variable

Move a Variable to/from a Subclass

Create Variable Accessors

Make a Variable Abstract/Concrete

Method-oriented refactorings
These operate on methods, and are available on the Method menu
(for details, see “Refactoring Methods” on page 504).

Method-oriented Refactorings
Move a Definition to Another Component

Rename a Method and its References

Safely Remove a Method

Add a Parameter to a Method


Inline all Sends to Self

Move a Method to/from a Superclass

Application Developer’s Guide 495


Chapter 22 - Refactoring

Statement-oriented refactorings
These operate on individual statements in a method and are
available through the context sensitive menus in the code tool
(for details, see “Refactoring Portions of a Method” on page 505).

Statement-oriented Refactorings
Extract a Method
Inline a Temporary Variable

Convert a Temporary to an Instance Variable

Remove a Parameter

Inline a Parameter

Rename a Temporary Variable

Move a Temporary to an Inner Scope

Extract to a Temporary
Inline a Message

496 VisualWorks
Refactoring for Abstraction

Refactoring for Abstraction


It is often desirable to change the design of an application to use abstract
and concrete classes. This requirement may emerge as the application
evolves, and it is then necessary to create a new, abstract class.
Making this design change involves inserting a new superclass into an
existing hierarchy, then splitting the functionality of the existing concrete
class and the newly-created superclass.
Several refactorings may be used to simplify this type of design change.
Conceptually, there are three steps involved:
1. Create an abstract superclass for the existing concrete class(es).
2. Find all instance variables common to the concrete subclasses, and
move them into the new abstract superclass.
3. Find all methods or code fragments that are common to the concrete
subclasses, and move them into the new superclass.

Creating an Abstract Class


Let’s consider the following example: a Web application for retailers might
provide a framework for different types of shopping applications available
at a single site.
Suppose that a first retail application is developed to purchase items from
a catalog. The class that represents items in a shopping cart, might look
like this:
WebAppNamespace defineClass: #CatalogPurchase
superclass: #{[Link]}
...
instanceVariableNames: 'item catalog'
...
The business logic for this class is defined as a method:
CatalogPurchase>>purchaseItemFor: aCustomer
| price |
price := catalog costForItem: item.
item isAvailable
ifTrue: [aCustomer chargeForItem: item cost: price]
....

Application Developer’s Guide 497


Chapter 22 - Refactoring

Let’s further suppose that another application is developed for purchasing


items that have been discounted for clearance. The business logic for this
application is slightly different, so a new class is defined for purchases:
WebAppNamespace defineClass: #ClearancePurchase
superclass: #{[Link]}
...
instanceVariableNames: 'item catalog discount'
...
Class ClearancePurchase handles purchases that can be discounted, so it
defines a method that looks like this:
ClearancePurchase>>purchaseItemFor: aCustomer
| price discountedPrice |
price := catalog costForItem: item.
discountedPrice := price - (price * discount).
item isAvailable
ifTrue: [aCustomer chargeForItem: item cost: discountedPrice]
....
The design of these two applications can be simplified by using an
abstract class named Purchase that CatalogPurchase and ClearancePurchase
both inherit from.
To simplify the design by refactoring the code:
1 Open the browser on the superclass of CatalogPurchase (in this case:
[Link]). Select Class ­ Refactor ­ Create Subclass.... A dialog
prompts for the name for the new subclass.
2 Enter the name of the new abstract class: Purchase. Click OK.
3 A dialog with a list view prompts for the subclasses of the new
abstract class. Scroll down the list and select both CatalogPurchase
and ClearancePurchase. Click OK.
The new class Purchase is created and inserted in the hierarchy.
Moving Instance Variables to a Superclass
In the example described above, the instance variables item and catalog
are duplicated in two classes. We can eliminate this duplication by
moving these variables into a shared superclass:
1 Open the browser on the class definition for CatalogPurchase.
2 Highlight the instance variable item, and then select
Instance Variables ­ Push Up... from the Class menu.
3 Repeat step 2 for the variable catalog.

498 VisualWorks
Refactoring for Abstraction

Since the same instance variables are defined by the sibling class
ClearancePurchase, this refactoring operation also removes them from the
sibling class.
Consolidating Common Code
In the example framework, the method purchaseItemFor: is similar in both
classes CatalogPurchase and ClearancePurchase. We can make a further
refactoring to consolidate this code in a single method in the Purchase
superclass.
To separate the common code:
1 Open a browser on the method CatalogPurchase>>purchaseItemFor:,
and highlight the lines of code that are unique:
| price |
price := catalog costForItem: item.
2 Select Refactor ­ Extract Method from the <Operate> menu.
a A dialog prompts to ask whether you want to extract the
assignment of price. Answer No.
3 A new dialog appears, prompting for the name of a new method to
contain the extracted code. Enter: computePrice.
The refactoring operation creates a new method using the extracted
code:
computePrice
^catalog costForItem: item
4 Select the method ClearancePurchase>>purchaseItemFor: and highlight
the unique code:
| price discountedPrice |
price := catalog costForItem: item.
discountedPrice := price - (price * discount).
5 Select Extract Method from the <Operate> menu.
a A dialog prompts to ask whether you want to extract the
assignment of price. Answer No.
6 A new dialog appears, prompting for the name of a new method to
contain the extracted code. Enter: computePrice.

Application Developer’s Guide 499


Chapter 22 - Refactoring

The Extract Method refactoring operation creates a new method:


computePrice
| price |
price := catalog costForItem: item.
^price - (price * discount)
Note that the method ClearancePurchase>>purchaseItemFor: is now
functionally identical to the same method in class CatalogPurchase.
Accordingly, we can consolidate both into a single method in the common
superclass.
To move the method purchaseItemFor: to class Purchase:
1 Examine CatalogPurchase>>purchaseItemFor: in the browser and select
Refactor ­ Push Up from the Method menu.
2 A dialog prompts to ask whether you want to remove duplicate
subclass methods. Answer Yes.
The method purchaseItemFor: is moved to class Purchase, thus eliminating
all duplicate code in its subclasses.

Inlining Methods
It is often desirable or necessary to inline the functionality contained in a
method by moving it to a different, more appropriate, class.
For example, suppose an application class defines the following method:
copyDictionary: aDictionary
| newDictionary |
newDictionary := Dictionary new: aDictionary size.
aDictionary
keysAndValuesDo: [:key :value | newDictionary at: key put: value].
^newDictionary
Since this method works entirely with its parameter, aDictionary, it would
simplify the overall design of the application if this functionality were
relocated in class Dictionary, i.e.:
Dictionary>>copyWithAssociations
| newDictionary |
newDictionary := Dictionary new: self size.
self keysAndValuesDo:
[:key :value | newDictionary at: key put: value].
^newDictionary
By placing the functionality in class Dictionary, we can replace indirect
sends such as self copyDictionary: someDictionary with direct, inline sends
to the Dictionary object.

500 VisualWorks
Refactoring for Abstraction

To apply this refactoring:


1 Open a browser on the method copyDictionary:, and select
Move ­ to Component... from the Method menu.
2 A dialog prompts to ask whether you want to move the method using
an argument or instance variables. Select the argument to the
method, aDictionary.
3 A dialog prompts for the class(es) in which you would like to define
the new method. Select [Link].
4 A dialog prompts to ask for the name of the new method. Enter:
copyDictionary and click OK.
When the refactoring is applied, the original copyDictionary: method is
changed to use the new method, i.e.:
copyDictionary: aDictionary
^aDictionary copyDictionary
Since the new method essentially works only to forward the send, we can
inline all of its senders, making them bypass the forwarder.
To inline sends to the forwarder and then remove it:
1 Select Refactor ­ Inline All Self Sends from the Method menu.
2 A dialog prompts to ask whether you want to inline the parameters.
Answer Yes.
3 Select Remove... from the Method menu.

Application Developer’s Guide 501


Chapter 22 - Refactoring

Individual Refactorings
The VisualWorks browser provides over two-dozen distinct refactoring
operations for manipulating classes, methods, and individual statements
within a method.

Refactoring Classes
Creating a Subclass
To insert a new class into the middle of an existing hierarchy, use the
browser’s navigator to choose the superclass for the new class and then
select Class ­ Create Subclass.... A dialog prompts for the name of the new
subclass(es).
This refactoring operation may be used to insert a new class between an
abstract superclass and all of its subclasses.
Renaming a Class and Its References
To rename a class and every reference to it in the image, select
Class ­ Rename....
This refactoring operation checks for symbols with the same name as the
class, and these, too, are renamed (this catches the use of expressions
like Smalltalk at: ...).
Note that in the case of class names constructed by sending the asSymbol
message, the strings containing the class name will not be changed.
Safely Removing a Class
To remove a class, first checking for any references to it, select
Class ­ Safe Remove....
Note that if the class is referenced using constructed symbols or Smalltalk
at: ..., this refactoring may remove the class even though code still uses it.
Changing a Class to a Sibling
To insert a new superclass into an existing hierarchy, use the browser’s
navigator to choose the subclass for the new class and then select
Class ­ Refactor ­ Convert to Sibling.
When requested, enter the name of the class to be created. If the
selected class has subclasses, a class selection dialog opens, for you to
select classes to make as siblings of the selected class, under the new
superclass. The new class will be a superclass of the class selected in
the browser’s navigator, and the other selected classes are moved to be
siblings of the selected class under the new class. It also pushes up

502 VisualWorks
Individual Refactorings

common methods and variables to the new superclass. Finally, for


methods that are not common, it writes a self subclassResponsibility
method.
Adding a Variable
To add an instance or class variable to the currently selected class, select
Instance Variables ­ Add... or Class Variables ­ Add... from the Class menu.
This refactoring operation checks that the new variable’s name doesn’t
already exist in the scope of the definition.
Renaming a Variable and its References
To rename an instance or class variable and all references to it, select
Instance Variables ­ Rename... or Class Variables ­ Rename... from the Class
menu.
Any methods using instVarAt: may be broken by this refactoring operation,
since the renamed variable is always added to the end of the list of
variables.
Removing a Variable
To remove an instance or class variable only if it is not referenced by any
code in the image, select Instance Variables ­ Remove... or
Class Variables ­ Remove... from the Class menu.

Moving a Variable from or to a Subclass


When a variable definition is defined by a class but only used by one of its
subclasses, you may use Class ­ Instance Variables ­ Push Down... or
Class ­ Class Variables ­ Push Down... to move the variable to only those
subclasses that use it.
If no subclass has a reference to the variable, it is simply removed.
This refactoring operation is only allowed if the selected class contains no
references to the variable. For class variables, it can only move the
variable down into one subclass; otherwise, it would be necessary to split
the one class variable into two and possibly break the code.
Note also that if there are any instances or the class or its subclasses
exist, these variables will become nil.
Conversely, to move a variable definition from the currently selected class
into its superclass, you may use Class ­ Instance Variables ­ Push Up... or
Class ­ Class Variables ­ Push Up....
Any methods using instVarAt: may be broken by this refactoring operation.

Application Developer’s Guide 503


Chapter 22 - Refactoring

Creating Variable Accessors


To create accessor methods for a variable, select
Instance Variables ­ Create Accessors... or Class Variables ­ Create Accessors...
from the Class menu.
The new accessor methods are named with the name of the variable. If a
method with the chosen name already exists, the refactoring operation
adds a number to the message selector until it no longer conflicts.
Abstracting a Variable
To create accessor methods for a variable and then convert all direct
references to use the new accessor methods, select
Instance Variables ­ Abstract... or Class Variables ­ Abstract... from the Class
menu.
This operation uses the Create Accessors... refactoring operation.
When detecting accessors, this operation scans for methods that simply
assign a value to the variable in question, regardless of the method’s
name. For this reason, coding techniques such as lazy initialization are
not discovered, and new accessor methods are created.
Making a Variable Concrete
To convert all variable accessor sends to direct variable references,
select Instance Variables ­ Protect... or Class Variables ­ Protect... from the
Class menu.
If the accessor method is no longer used then it will be removed.

Refactoring Methods
Moving a Definition to Another Component
To move a method, an argument or an instance variable to another
component, select Move ­ to Component... from the Method menu.
This operation can be used to move the body of a method to another
component, leaving a forwarder and thereby not changing the external
interface of the class that contains the original method.
Renaming a Method and its References
To rename all implementors of a method, all senders, and all symbols
references, select Rename... from the Method menu.
In addition to strict renaming, this refactoring operation also enables you
to rearrange the method’s parameters. However, when rearranging the
parameters, any symbols that are performed cannot be permuted.

504 VisualWorks
Individual Refactorings

Safely Removing a Method


To remove a method, checking for senders and symbols that reference
the method’s name, select Safe Remove from the Method menu.
The method is only removed if there are no unresolved references to it.
This operation also removes the method if it is equivalent to the
superclass' definition.
Adding a Parameter to a Method
To add a default parameter to all implementors of the method, select
Refactor ­ Add Parameter... from the Method menu.

Inlining all Sends to Self


To inline all senders within the class of the method,
Refactor ­ Inline All Self Sends... from the Method menu.
If there are no remaining senders after all inlines have been performed,
this operation also removes the method.
Moving a Method to or from a Superclass
Select Refactor ­ Push Up from the Method menu to move a method up into
the superclass. If the superclass is abstract and already defines the
method, then the superclass' method is copied down into the other
subclasses (assuming they don't already define the method).
To move a method from the currently selected class down into all
subclasses that don't implement it, select Refactor ­ Push Down from the
Method menu.
This operation is only performed if the class is abstract, and the browser
checks for this by scanning the class for methods which send
subclassResponsibility, or for no references to the class.

Refactoring Portions of a Method


Extracting a Method
To extract a portion of code as a separate method, highlight the code
fragment and select Refactor ­ Extract Method from the <Operate> menu.
This refactoring operation determines which temporary variables are
needed in the new method, and prompts for a selector that takes
arguments.

Application Developer’s Guide 505


Chapter 22 - Refactoring

Inlining a Temporary Variable


To remove the assignment of a variable, replacing all references to the
variable with the right hand side of the assignment, highlight the code
fragment that contains the assignment and select
Refactor ­ Inline Temporary from the <Operate> menu.

Converting a Temporary into an Instance Variable


To convert a temporary into an instance variable, highlight the temporary
variable name and select Refactor ­ Convert to Instance Variable from the
<Operate> menu.
This operation is useful when eliminating parameters to methods that are
only used internally within a class.
Note: this refactoring should not be used on methods that are recursive.
Removing a Parameter
To remove an unused parameter from all implementors of the method,
and from all message sends, highlight the parameter and select
Refactor ­ Remove Parameter from the <Operate> menu.

Inlining a Parameter
To remove a parameter from the method, adding a corresponding
assignment at the beginning of the method, highlight the parameter and
select Refactor ­ Inline Parameter from the <Operate> menu.
This operation is only performed if all senders of the method have the
same value for the parameter.
Renaming a Temporary
To rename a temporary variable in the body of the method, highlight it
and select Refactor ­ Rename... from the <Operate> menu.
Moving a Temporary to an Inner Scope
To move a temporary variable definition into the tightest scope that
contains both the variable assignment and references, highlight it and
select Refactor ­ Move to Inner Scope from the <Operate> menu.
This operations is useful for improving code performance by converting
unoptimized blocks into optimized ones.
Extracting to a Temporary
To extract a message into an assignment statement, highlight the
statement and select Refactor ­ Extract to Temporary from the <Operate>
menu.

506 VisualWorks
Individual Refactorings

For example, in an expression such as:


self someMessage anotherMessage foo: 1 bar: 2
To code self someMessage may be extracted to a temporary named temp.
The result of the operation looks like:
| temp |
temp := self someMessage.
temp anotherMessage foo: 1 bar: 2
Inlining a Message
To inline a message send, highlight the statement and select
Refactor ­ Inline Method from the <Operate> menu.
If there are multiple implementors of the message, this operations
prompts for the implementation that should be inlined.

Application Developer’s Guide 507


23
Coding Tools

In addition to its basic browsing functionality, the VisualWorks 7 browser


includes additional features for use by experienced developers.
The browser provides a framework for adding special-purpose code tools.
Standard tools are available for code checking, automated code rewriting,
and unit testing.
This chapter describes:
• Code Critic
• Code Rewriting Editor
• Unit Testing

Code Critic
The VisualWorks 7 browser includes a Code Critic tool that may be used
to screen application code for over 60 common types of bugs.

508 VisualWorks
Code Critic

The Code Critic also provides a mechanism for applying a set of


pre-defined transformation rules to your application code. These rules
express “best practices” for code development, and are almost always
safe to apply.
To write your own transformation rules, refer to the discussion of the
“Code Rewriting Editor” on page 511.
For a detailed description of all the rules and transformations used by the
Code Critic, see “Code Critic Rules” on page 565.

Using the Code Critic


To check a class, a protocol, method or methods:
1 Use the browser navigator to set the scope of the test. Select multiple
classes, protocols or methods by holding down the <Shift> key.
2 Select the tool using the Code Critic tab control in the lower part of the
browser.
The Code Critic tool presents a hierarchical list of rules. You may
select the entire list, or individual rules that you wish to check against.
Select multiple rules by holding down the <Shift> key.
3 With both code and the rules chosen, run the critic by clicking on the
Check... button.
Once the critic finishes checking your code, it presents a list of
classes/methods that failed a check. If all checks pass without error, the
message No results found appears in the lower portion of the browser.
All methods that fail a check are gathered together and displayed in a
results window. Results are categorized by type, with a list of rules that
failed to pass followed by the number of methods that failed to pass each
rule (shown inside square brackets).
To open a Method List browser on all the methods that failed to pass a
given rule, select a rule in the results window and click on the Browse...
button.
Use the Remove button to remove items from the results list. This feature
may be helpful for keeping track of items that have been fixed.

Filtering Results
The Code Critic includes a mechanism for creating and applying special
rule filters. These enable you to ignore a particular rule for a particular
class or method.

Application Developer’s Guide 509


Chapter 23 - Coding Tools

For example, during a session with the Critic, once problems associated
with certain rules have been fixed, we may want to ignore those rules
during subsequent checks with the Code Critic.
Working with Rule Filters
To filter an item visible in the Critic’s results list, select it and choose
Add Filter from the <Operate> menu.
To save the current filter set, or load another one, select Save Filters... or
Load Filters... from the browser’s Edit menu. To reset the filters used by the
Critic, select Clear Filters.

Applying Transformation Rules


To view the available rules, select View ­ Transformation Rules from the
browser’s Edit menu.
Follow the same general steps for applying the rules to your code:
1 Use the browser navigator to set the scope of the transformation.
Select multiple classes, protocols or methods by holding down the
<Shift> key.
2 Select the individual transformation rules you wish to apply. You may
select multiple rules by holding down the <Shift> key.
3 With both code and the rules chosen, run the critic by clicking on the
Check... button.
Once the critic finishes checking your code, it presents a list of
classes/methods that may be transformed. If all checks pass without
error, the message No results found appears in the lower portion of the
browser.
To preview the effects of a code transformation, select a method in the
upper portion of the results window. The current version of the method
and the transformed result are displayed side-by-side in the lower half of
the results window.
To apply the code transformation to the highlighted method, select Execute
from the <Operate> menu. To apply all the transformations in the results
window, select Execute All.

Limitations of the Code Critic


As with all automated tools, not everything flagged by Code Critic is
necessary a bug, rather, they are potential problems that might merit
further attention. Be aware that for some rules, the Code Critic may return
false positives.

510 VisualWorks
Code Rewriting Editor

Code Rewriting Editor


The rewrite rule editor enables you to create search and replace patterns
that work at the method’s structural level. Unlike simple string matching,
these patterns are applied to the method’s parse tree.
The rewrite tool uses a special syntax to specify a transformation rule.
When a transformation rule is applied, it affects the method(s) selected in
the browser’s navigator. You may specify a single method, or any number
of methods, protocols, or classes as the target of a single transformation.

Search pattern

Replace pattern

The rewrite editor is available whenever you select the Rewrite tab of the
browser’s code tool. Specify a search pattern in the upper input area of
the tool, and a replacement pattern in the lower area.
Use the Search... button to locate all occurrences of the search pattern in
the method, protocol, or classes selected in the browser navigator.
Results are displayed in a new browser. No code is changed.
Use the Replace... button to locate all occurrences of the search pattern in
the specified code, and then open a transformation editor on all matching
methods. The transformation editor allows you to apply the rewrite rule.
The rewrite editor enables you to write your own transformation rules.
A set of pre-defined transformation rules are also available, but as part of
the Code Critic; for details, see “Applying Transformation Rules” on
page 510.

Application Developer’s Guide 511


Chapter 23 - Coding Tools

Transformation Rules
A transformation rule is specified using a pattern that is iteratively applied
to each expression in each method this is selected for rewriting.
Pattern-matching is performed against each node in the method’s parse
tree. When the rewrite editor finds a node in the method’s parse tree that
matches the node specified in the parse tree generated from the search
pattern, it applies a transformation.
You may also specify a pattern for a whole method, rather than just a
single node (for details, see “Replacing Whole Methods” on page 514).
When the rewrite tool scans for parse nodes that match the search
pattern, it first converts the pattern into a collection of meta-variables.
Each meta-variable is identified in the search pattern using a ` character.
Meta-variables allow pattern matching without having to specify specific
variable names. In general, a meta-variable is specified by one or more
special characters followed by a valid variable name. For example:
`receiver printOn: `variable
specifies a pattern with two meta-variables named receiver and variable.
This pattern would match the expression:
super printOn: aStream
Using Meta-variables and Modifiers
The ` character for specifying a meta-variable may be accompanied by
other special characters called “modifiers” that are used to specify the
type of node that the meta-variable can match. Modifiers are entered
immediately after the ` character.
For example, since it is often impractical to specify every possible match
exactly, the modifer @ may be added after the ` character to specify a
match for any type of node in the method’s parse-tree.
Suppose, to sustain the example, we want to replace every occurrance of
the message printOn: with the message print:. Using @, we can specify a
general pattern:
`@receiver printOn: `@variable
would now match the expression:
self name printOn: aStream
Slight variations of this pattern can be used to replace keyword
messages with several arguments. Messages taking block arguments
can be easily manipulated in this manner.

512 VisualWorks
Code Rewriting Editor

For example, to change:


maybeNil isNil ifTrue: [trueBlock] ifFalse: [falseBlock]
to:
maybeNil ifNil: [trueBlock] ifNotNil: [falseBlock]
We would use the following rules for search and replace:
`@maybeNil isNil ifTrue: `@trueBlock ifFalse: `@falseBlock
`@maybeNil ifNil: `@trueBlock ifNotNil: `@falseBlock
Note that the @ modifier is polymorphic, i.e., it matches any subtree in
the node. Depending upon where it appears in the pattern, it can specify
anything from an individual node to a collection of statements.
Similarly, a list of temporary variables can be matched with `@Temps, e.g.:
| `@Temps |
When a match is found, it is often necessary to search inside the node for
more matches. The ` character may be used twice (e.g., ``@variable) to
specify this pattern-matching behavior.
The . (period) character may be used to match a statement node, thus a
list of statements may be matched by using ``@.Statements.
Four different modifiers are currently supported by the rewrite editor:

Character Type Comment Examples


‘ recurse into Whenever a match is found, "``@object foo" — matches foo
look inside this matched node sent to any object, plus for each
for more matches. match found look for more
matches in the ``@object part
. statement Matches a statement in a "`.Statement" — matches a
sequence node. single statement
# literal Matches only literal objects. "`#literal" — matches any literal
(#(), #foo, 1, etc.)
@ list When applied to a variable "| `@Temps | …" — matches list
node, this will match a literal, of temporary variables.
variable, or a sequence of
messages sent to a literal or "`@.Statements" — matches list
variable. of statements
When applied to a keyword in "`@object" — matches any
a message, it will match a list message node, literal node or
of keyword messages (i.e., block node
any message send).
When applied with a "foo `@message: `@args" —
statement character, it will matches any message sent to
match a list of statements. foo

Application Developer’s Guide 513


Chapter 23 - Coding Tools

Rewriting Methods
To rewrite methods using a transformation rule:
1 Select the method or methods you wish to rewrite in the browser’s
navigator and then open the rewrite editor by clicking on the code
tool’s Rewrite tab.
2 Enter a search pattern in the upper input field of the rewrite editor.
3 Enter a replacement pattern in the lower input field.
4 To browse a list of methods that match the search pattern, click on
the Search... button.
5 Open a list of methods that are ready to be transformed, by clicking
on the Replace.. button. A transformation editor appears, showing a
list of methods and highlighting the code that will be transformed.
6 To actually transform the method(s) that match the search pattern,
select Execute or Execute All from the <Operate> menu in the
transformation editor.

Replacing Whole Methods


The rewrite tool also allows you to match and replace an entire method,
not just single expressions. To enable entire-method transformations,
select the Method check-box.
For example, you can search for methods that just return a super
message, use the following pattern:
`@msg: `@args
^super `@msg: `@args
To eliminate ifTrue: guard clauses, you might use this search pattern:
`@methodName: `@methodArgs
| `@Temps |
`@Condition ifTrue:
[`.Stmt1.
`.Stmt2.
`@.Statements]
with this replacement pattern:
`@methodName: `@methodArgs
| `@Temps |
`@Condition ifFalse: [^self].
`.Stmt1.
`.Stmt2.
`@.Statements

514 VisualWorks
Unit Testing

Unit Testing
The VisualWorks 7 browser includes a testing tool for using the SUnit
framework. SUnit (short for Smalltalk Unit Testing Framework) is the
de facto industry standard for developers to build test suites and attach
them to their application classes.
SUnit supports the XP Unit Test testing practices. It is currently available
for all Smalltalk dialects and is maintained by Camp Smalltalk. The SUnit
distributed with VisualWorks 7 is the most recent Camp Smalltalk version.
Additional information can be found at the following sites:
• SUnit Camp Smalltalk
[Link]
• Simple Smalltalk Testing: With Patterns
[Link]
Kent Beck’s original paper on SUnit.
• Various documents and papers are available at:
[Link]
The SUnit testing framework makes two general structures available to
developers:
Test Case
A test case is a collection of unit tests that are generally applied to a
single class. the smallest unit of testing available, checking for
specific conditions. Individual test cases are constructed by creating
concrete subclasses of the abstract framework class TestCase.
Test Resource
A test resource represents a resource needed to perform a
repeatable unit test (e.g., a temporary file or database instance).
Test resources are associated with specific test cases, and may need
to be prepared or initialized before testing can begin.
The unit testing methodology involves a novel approach to code
development: test cases are defined first, then the application code is
written and tested until the test passes.
The test case defines what the code should do or be like (the invariants of
the program execution), thereby integrating test-writing into the heart of
the development process.

Application Developer’s Guide 515


Chapter 23 - Coding Tools

Unit Testing in VisualWorks


The VisualWorks 7 browser provides a simple, integrated interface for
building tests and running them against your applications.
To begin using the unit testing framework, you must first load the
RBSUnitExtensions parcel.
Using the Parcel Manager (select Tools ­ Parcel Manager... in the Launcher
window), open the “Suggestions” category and click on RBSUnitExtensions;
then select Load... from the <Operate> menu.
To view some example test classes, you may also wish to load the
SUnitTests parcel.
Running Test Cases
To run the test cases defined in a class, use the browser to examine the
test class’s definition. When examining any subclass of class TestCase,
the browser displays the unit testing interface in the lower-right corner of
the code tool:

To gather all tests in the selected class into a suite and then run them,
click Run. The test results are displayed by the testing interface:

You may run selected tests, by selecting only a single method, protocol,
or a combination of protocols or methods. Similarly, you can gather a
number of test cases into a single suite by selecting several classes
(using the <Shift> key) before clicking on Run.
Although the unit test framework includes the notion of a test suite,
developers using the VisualWorks testing tool don’t need to worry about
suites. The testing tool automatically bundles all selected test cases into
a test suite before running the suite.

516 VisualWorks
Unit Testing

Creating a Test Case


Each new test case is represented using a separate class, each being a
subclass of class TestCase. Any resources the test case uses are
represented using instance variables (for details on resources, see
“Defining Test Resources” on page 518).
For example, to create a simple test case:
1 Create a subclass of TestCase (e.g., MyTest)
2 In MyTest, create a protocol called 'tests'
3 Create a test method (we'll call it testExistence), e.g.:
testExistence
self should:
[#{MyNewClass} ifDefinedDo: [:value | true] elseDo: [false]].
The method textExistence now defines a single test case.
To run this single test, select the method testExistence and press the Run
button located at the bottom of the browser code tool.
If MyNewClass does not exist, an error is shown in the lower-left corner of
the browser code tool. To follow the practice of unit testing, you would
next define the class and then run the test again to ensure it passes.
To run every test defined by a single test case, select the class (with no
method selected) and press the Run button (you can also select a protocol
to run all tests defined in that protocol).
Writing Test Methods
Class TestCase provides six basic methods for checking the specific
conditions that allow a test to pass:
should: aBlock
Requires a 0 argument block that must answer a boolean. To pass
the test, aBlock should answer true.
shouldnt: aBlock
Requires a 0 argument block that must answer a boolean. To pass
the test, aBlock should answer false.
should: aBlock raise: anExceptionSubclass
Requires a 0 argument block. To pass the test, aBlock should raise an
error of class anExceptionSubclass, i.e., a subclass of Exception.
shouldnt: aBlock raise: anExceptionSubclass
Requires a 0 argument block. To pass the test, aBlock should not
raise an error of class anExceptionSubclass, i.e., a subclass of
Exception.

Application Developer’s Guide 517


Chapter 23 - Coding Tools

assert: anExpression
To pass the test, anExpression should answer true. This is a
convenience method the behaves like should:.
deny: anExpression
To pass the test, anExpression should answer false. This is a
convenience method the behaves like shouldnt:.
Using should: and shouldnt:
Test cases may use the methods should: and shouldnt: to test for specific
conditions allowing a test to pass.
The method should: takes a block parameter and allows the test to pass if
the block returns true. For example:
testSomeCondition
self should: [someCondition == true].
Alternately, you may test using the method shouldnt:, which passes if the
block returns false.
For example, a test that uses shouldnt:raise: (a variant of shouldnt:) might
look like this:
testCreation
self shouldnt: [MyNewClass new] raise: Error
In place of should: and shouldnt:, you may also use the two convenience
methods assert: and deny:. For example:
self should: [empty include: 5]
is equivalent to:
self assert: (empty includes: 5)
Defining Test Resources
Individual test cases will vary in their complexity. A test case may check
against a simple set of predictions (e.g., “Should this condition be true?”),
or it may involve a test resource that is used during testing.
A resource may be a simple data structure, or something as complicated
as a database instance or a server connection. A single test case may
require a number of different resources, which may either be created and
destroyed for each individual test, or used for a series of tests.
Resources that are created and destroyed for each test are represented
using instance variables in the test case class. In practice, an instance
variable is added for each condition predicted by the resource.

518 VisualWorks
Unit Testing

Resources that are created and then used for a number of tests are
represented using subclasses of TestResource. These must be initialized
once before any test is run, and destroyed after all tests have been run.
As an example of a resource used for a single test, we might test the
behavior of a Set using two resources, one to represent the “empty”
condition, and one to represent the “full” condition. Thus, we would add
two instance variables, empty and full, to our test case.
To initialize these two resources, we override the method setUp defined in
class TestCase:
setUp
empty := Set new.
full := Set with: 5 with: #abc
Each time a test case is run, it begins by sending setUp and ends by
sending tearDown. To ensure that tests are repeatable and predictable,
setUp and tearDown are run once for each test. Subclasses of TestCase
should override these two methods to initialize and release resources.
An example method that would use a resource to test the add: behavior of
a Set might look like this:
testAdd
empty add: 5.
self should: [empty includes: 5]
This test adds the number 5 to the empty resource, and then uses should:
to test that the empty resource now include the number 5.
Subclasses of TestCase are instantiated once for each test that is run, and
any resources are initialized, used once, and then discarded.

Guidelines for Working with Test Cases


The SUnit methodology distinguishes between unit tests and functional
tests. Unit tests are always repeatable, without regard for anything in the
external environment (e.g., a database instance). Functional tests differ in
that they may not be repeatable, since they test behaviors that depend on
external resources which may change from one test run to another.
Generally, a test case is a collection of unit tests that are applied to a
single class in the target application. However, occasionally a common
set of behavioral tests are grouped into a single test case, thereby
crossing class boundaries. These test cases are also more appropriately
categorized as functional tests.

Application Developer’s Guide 519


Chapter 23 - Coding Tools

Criteria for Testing


The criteria for passing unit tests differ from those for passing functional
tests. As a rule, any code that fails a unit test isn’t ready for system
integration. Integration should only take place when all unit tests pass
100%.
Functional tests, on the other hand, may not all pass 100%, and code that
fails certain functional tests may still be integrated into the system.
Generally, functional tests and unit tests are written at the same time, with
a greater proportion of functional tests passing as the project progresses.

520 VisualWorks
24
XML Framework

VisualWorks 5i introduced an XML (eXtensible Markup Language)


framework, which was used internally as a (potentially) portable source
code representation. As XML and related specifications have grown and
become increasingly accepted as a standard for representing structured
data, the implementation in VisualWorks has grown and matured.
This chapter describes the XML framework, and how to use it to read and
build XML documents for use with other Web Service facilities. These
facilities are used by the higher-level Web Service frameworks described
in later chapters.
The XML framework supports working with XML documents using either
the DOM (Document Object Model) or SAX (Simple API for XML) APIs.
The discussion in this section assumes you already understand the
essentials of XML and its components. For more basic information, there
are a lot of resources available. See [Link] as a beginning
resource.

Working with XML documents


XML presents data as a structured document. The XML DOM (Document
Object Model) is an programming interface for accessing that data as a
tree structure. Using the DOM, you can build documents, navigate their
structure, and add, modify, or delete elements and content.
The DOM represents an XML document as a hierarchy of objects. Being
an object model, it is a natural way for VisualWorks to operate on XML
documents.

Application Developer’s Guide 521


Chapter 24 - XML Framework

Parsing an XML document


Frequently you receive an XML document as a resource on the internet.
Or, you may have it stored as a file. In any case, a standard way to work
with it is to first represent it in memory. In VisualWorks, you do this by
representing it as a [Link], which you do using XMLParser. (Note
that [Link] is a different class than [Link].)
The basic procedure is to generate an instance of XMLParser, and send it
a parse: message with the XML resource to be parsed.
| parser |
parser := XMLParser new.
parser parse: '[Link]' asFilename.
In this example, the resource is given as a Filename, but it could be an URI
or a ReadStream. For an URI, send asURI to a String describing the
protocol, host, and path (refer to the Internet Client Developer’s Guide for
information on URI support):
| parser |
parser := XMLParser new.
parser parse: '[Link] asURI.
By default, the parser is validating, so the document must include a
document type declaration (DTD). If the document is only well-formed,
you need to turn off validation by sending a validate: message to the
parser with false as argument. For example:
| parser |
parser := XMLParser new.
parser validate: false.
parser parse:
'<?xml version="1.0"?><doc><para>Hello, world!</para></doc>'
readStream.
To summarize this protocol:
parse: aDataSource
Selects aDataSource, which may be an URI, a Filename, or a
ReadStream. If successful, returns a [Link].
validate: aBoolean
Sets the parser’s validation flag, determining whether the parser will
validate the document against its document type definition. By
default, this is set to true.

522 VisualWorks
Working with XML documents

Validating Against a Schema


An XML Schema provides an alternative, and more powerful, document
structure specification than does a DTD document. As an alternative to a
DTD, you can validate a document against a schema. This is done by first
parsing the schema, then parsing the document, and finally by validating
the document against the schema. For example:
schemaURI := '[Link] asURI.
docURI := '[Link] asURI.
schema := SchemaHandler parse: schemaURI.
doc := XMLParser new
validate: false;
parse: docURI.
schema validate: doc
A Boolean value is returned, indicating whether the document is valid (true)
or not (false).
Schema support is provided as a preview at this time. Load the XSchema
parcel, preview/parcels/[Link].

Selecting a XMLParser driver


By default, the parser represents the XML document according to the
Domain Object Model (DOM), and the parser returns an [Link]
that supports the DOM API. There are occasions, however, when other
processing is necessary.
The parser operates by handling SAX (Simple API for XML) events as
specified by a SAX driver. The default driver is DOM_SAXDriver. There are
a few other drivers provided, and you can build your own (see “Building a
SAX driver” below).
To specify another driver, send a handlers: message to the parser.
handlers: aSAXDriver
Assigns aSAXDriver as the parser’s current SAX driver.
In general, you only want to assign an alternate driver when you have
built one for your own application. One driver that might be of some use,
however, is the NullSAXDriver. This driver does simple syntax checking of
a XML document without further processing. So, to substitute this driver
to check the file, send a handlers: message with a new instance of the
driver:
| parser |
parser := XMLParser new.
parser handlers: NullSAXDriver new.
parser parse: '[Link] asURI.

Application Developer’s Guide 523


Chapter 24 - XML Framework

This example does its work and returns nil, unless errors occur. For
syntax checking, you still need to provide handlers for syntax errors, as
described in “XML Error Handling” below.
Browse the SAXDriver hierarchy to see what drivers are available. In
general the classes provide superclasses for your own drivers.
For advanced users, it is possible to specify handlers for different aspects
of a document. Browse the XMLParser contentHandler:, dtdHandler:,
entityResolver: and errorHandler: methods for this option.

Accessing XML Document elements


In the DOM, a document is represented as a tree structure of nodes. The
main node is the document itself. In VisualWorks the DOM is
implemented as a collection of classes, all subclasses of Node.
The following classes give a high-level view of the parts of a Document:
Node
Attribute
Comment
Document
DocumentFragment
Element
Entity
Notation
PI
Text
To work with the document, a large number of messages are provided for
accessing these various parts of a document.
root
Sent to a Document, returns the root element of the document.
document
For any element, returns the enclosing Document object.
children
Returns an OrderedCollection of all nodes immediately in the receiver,
or an empty collection if there are none.
parent
Returns the node immediately containing the receiver.
elementNamed: aNodeTag
Returns the unique child element named aNodeTag in the receiver. An
error is raised if there is not exactly one.

524 VisualWorks
Accessing XML Document elements

elementsNamed: aNodeTag
Returns an OrderedCollection of child elements named aNodeTag.
anyElementNamed: aNodeTag
Same as elementNamed:, except that the search is recursive from the
receiver, so the receiver, its children, grandchildren, etc., are included
in the search. An error is raised if there is not exactly one.
anyElementsNamed: aNodeTag
Same as elementsNamed:, except that the search is recursive from the
receiver, so the receiver, its children, grandchildren, etc., are included
in the search.
attributes
Returns a OrderedCollection of Attribute objects in the receiving Element.
selectNodes: aBlock
Returns an OrderedCollection of Node objects satisfying the selection
criteria specified in aBlock.
The following sections will use these messages to explore a Document.

Get Document root element


The Document object may have many elements besides the root element,
such as various comments or processing instructions. For example,
parsing a help file yields a document with two elements: a processing
instruction and the root element. To verify this, evaluate the following in a
workspace:
parser := XMLParser new.
parser validate: false.
pdoc := parser parse: '..\help\01-xml-language\[Link]' asFilename.
pdoc children inspect.
(The filename in the above example is not portable, so will have to be
written differently on non-Windows platforms.)
To extract only the root element, which contains the whole DOM tree
structure, send a root message to the parsed document:
docRoot := pdoc root.
This is an Element object to which you can send other messages, and so
traverse the document structure.

Application Developer’s Guide 525


Chapter 24 - XML Framework

Selecting elements
An XML document is structured as a hierarchy of elements with a single
root element. Depending on the individual document, the structure may
be very shallow, as in the case of a well-formed but unstructured
document, or quite deep. To make use of the XML document involves
traversing and digging through this element hierarchy.
The children message returns an OrderedCollection of elements contained
immediately in the receiving element.
parser := XMLParser new.
pdoc := parser parse:
'[Link] asURI.
elementCollection := pdoc root children.
The contents of the resulting collection may not all be elements as such.
For example, elementCollection in the above code contains (at the time of
this writing) some [Link] nodes as well as Element nodes. This can be
important when working down through the hierarchy because a Text does
not respond to children.
The isElement message returns a Boolean indicating whether the receiver
is an Element or not. You can use it to collect just those nodes that are
elements, for example:
elementCollection := pdoc root children select: [ :el | el isElement ]
The elements of this collection now all respond to children, and you can
continue digging into the hierarchy.
It is also frequently desirable to select only those elements with a
particular tag, or name. For example, when dealing with a specific node,
you may want to deal only with elements tagged “partNum”. To collect all
these elements in a node (aNode), send a elementsNamed: message with a
NodeTag or String argument:
partNumElements := aNode elementsNamed: 'partNum'.
The String format shown here only works if the element is in the default
XML namespace; otherwise the argument must be an instance of
NodeTag. You may retrieve a NodeTag from an element by sending a tag
message to the Element, and then use that tag to identify other elements
with the same tag. This can be useful for retrieving all other elements with
the same tag as one you already have:
subjTag := someElement tag.
tagGroup := newDoc root elementsNamed: subjTag.
Alternatively, you can create a NodeTag by sending a qualifier:ns:type:
message to a new instance:

526 VisualWorks
Accessing XML Document elements

subjTag := NodeTag new


qualifier: '' ns: '[Link] type: 'a' .
tagGroup := newDoc root anyElementsNamed: subjTag.
There are variants of the elementsNamed: message, such as
elementNamed:, which returns the unique element, if there is one, or an
error otherwise. The messages anyElementNamed: and anyElementsNamed:
(used above) are similar, but are recursive from the receiver element, and
so include the receiver node and all children nodes, and all their children,
etc., in the search. So, the above example returns all elements tagged “a”
in the document.

Selecting attributes
Elements often have attributes, specifying special features of the
element. The attributes message, sent to an Element (anElement), returns
an OrderedCollection of an element’s attributes.
attrs := anElement attributes.
Attributes are essentially key/value pairs, where the key is the attribute
name, and the value is a String. The messages for accessing these are:
tag
Returns the Attribute name, as a NodeTag.
value
Returns the Attribute value.
To make use of an Attribute, you will need to search through the collection
of attributes until you find one you are interested in, and then get its
value. For example, if you need to process an “href” attribute for an
element, you will search for that attribute and return the value. For
example:
( attrs detect: [ :attr | attr tag type = 'href' ] ifNone: [ ] ) value.
Since attributes are already key/value pairs, it may be worth setting them
into a Dictionary, especially for repeated access:
attrDict := Dictionary withAll: (aCollection collect:
[ :each | Association key: each tag type value: each value ]) .

Application Developer’s Guide 527


Chapter 24 - XML Framework

Building a document
Besides handling XML documents that your application receives, for
conducting web-based commerce it is also necessary to build XML
documents. You can do this simply by assembling a long string and
transmitting that over the transport, but this places all of the responsibility
for building proper XML on your application.
VisualWorks provides facilities for building an XML DOM tree that
alleviates some of the responsibility for building a syntactically correct
XML document.
Not all aspects of a document are supported, however, so you may need
to provide some other mechanism for adding these aspects to the
document. For example, the XML prolog and DTD declarations are not
supported by the XML framework. If required in your application, these
need to be written onto output stream before any document elements,
and so are not handled as part of the document itself. (See “Writing the
XML Document” at the end of this section).
This section describes how to build an XML document using the
VisualWorks XML support, and noting where methods not included in the
XML framework are required. The general procedure is to create an
[Link] instance and add nodes.

Create a basic document


The basic document is built simply by creating an instance of
[Link]:
newDoc := [Link] new.
This is too basic to be useful, but this is the object to which you add
nodes to build the document.

Node ordering
The most straight-forward method for adding nodes is by sending
addNode: to an existing node, with the new node as argument. This is the
method we will use in the following discussion.
However, addNode: adds the new node to the end of the receiver’s
collection of nodes. Accordingly, you need to be careful to add nodes in
order, from the start of the XML document to the end.

528 VisualWorks
Building a document

If you must insert a node someplace other than at the end, realize that
you can add it using OrderedCollection messages. This may be useful, for
example, to ensure that processing instructions are added early in the
document, prior to the root element.

Add element nodes


Most of the document content is in elements, which are represented as
instances of [Link]. An Element is really just an envelope for other
nodes.
An Element must have a name, called its tag, an instance of NodeTag,
which is used to begin and end the element in the XML output. The
Element may also have attributes and/or entities. To create an Element with
only a tag, send a tag: instance creation message to the class:
[Link] tag: 'XML'
This simple creation method builds a simple NodeTag for the element,
consisting only of the tag name.
If you employ XML namespaces, things become a little more
complicated. Refer to “Using XML Namespaces” below for further
information.
Add a root element
An XML document has a single root element. If the document has a DTD,
the root element tag must match the declared root in the DTD. To add a
root, send an addNode: message to the Document with the Element as
argument:
newDoc := [Link] new.
newDoc addNode: ( [Link] name: 'xml' text: 'version="1.0" ' ) .
newDoc addNode: ( [Link] tag: ‘XML’ ).
A document can have only one root node. All further elements are added
to the root node or further subnodes. To access the root, send a root
message to the document:
newDoc root
Add nested elements
Adding other elements is similar; the only difference is the receiver node
of the addNode: message. For example, to create a document hierarchy
like:

Application Developer’s Guide 529


Chapter 24 - XML Framework

XML
heading1
heading2
body
send messages like this:
newDoc := [Link] new.
newDoc addNode: ( [Link] name: 'xml' text: 'version="1.0" ' ).
newDoc addNode: ( [Link] tag: 'XML' ).
newDoc root addNode: ( ( [Link] tag: 'heading1' )
addNode: ( ( [Link] tag: 'heading2' )
addNode: ( [Link] tag: 'body' ) ) ).
The nodes can, of course, be constructed individually and added to the
containing node in other ways.
The PI element defines a processing instruction. Refer to “Add processing
instructions” for more information.
If you assemble a collection of nodes, you can add them as subnodes as
a group when creating their parent, using the tag:elements: instance
creation method. For example, to add a node structure to newDoc, do:
nodeGroup := Array with: ([Link] tag: 'body')
with: ( ([Link] tag: 'heading2')
addNode: ([Link] tag: 'body' ) ).
newDoc root addNode: ( [Link] tag: 'heading1' elements: nodeGroup ).
Adding element attributes
An element may have attributes, which are additional labels identifying
the contents of an element. For example, an image element may include
alignment and source information:
<IMG ALIGN="left" SRC="[Link]
Attributes are instances of [Link], which is a subclass of Node. To
add attributes, create the Attribute instances and add them as a collection
by sending a tag:attributes:elements: instance creation message to Element,
sending an addNode: message to the containing element as usual. The
argument to the elements: keyword can be provided as a collection of
elements or as nil.
attrGroup := Array
with: ([Link] name: 'ALIGN' value: 'left' )
with: ([Link] name: 'SRC' value:
'[Link]

newDoc root addNode:


( [Link] tag: 'IMG' attributes: attrGroup elements: nil ).

530 VisualWorks
Building a document

Again, the element tag in this example is simple. To include a namespace


qualifier or declare a namespace, the specified tag must be an instance
of NodeTag.
Adding text
Many elements have a text content. Text is added as another node, as an
instance of [Link]. The instance creation method is simply text:, which
takes a String argument.
[Link] text: 'Hello, World!'
The text node is added using the usual addNode: message.
newDoc := [Link] new.
newDoc addNode: ( [Link] name: 'xml' text: 'version="1.0" ' ).
newDoc addNode: ( [Link] tag: 'XML' ).
newDoc root addNode: ( ( [Link] tag: 'body' )
addNode: ( [Link] text: 'Hello, World!' ) ).

Add processing instructions


Processing instructions contain special instructions to the application that
will process the XML.
A processing instruction is represented by an instance of [Link]. Its
instance creation method, name:text:, specifies the target application and
the specific instruction, both as Strings. To create the initial instruction,
send:
[Link] name: 'target' text: 'instruction'
Note that the text contains all instructions for this processing instruction,
including any attributes and values for the instruction (see the next
example).
For example, the processing instruction that occurs at the beginning of a
VisualWorks help file is:
<?xml-stylesheet href="[Link]" type="text/css" title="Smalltalk
Language" charset="UTF-8"?>:
To create the processing instruction in VisualWorks write:
[Link] name: 'xml-stylesheet' text: 'href="[Link]" type="text/css"
title="Smalltalk Language" charset="UTF-8" '
To add this to the document, we send addNode: with the new processing
instruction as the argument:

Application Developer’s Guide 531


Chapter 24 - XML Framework

newDoc := [Link] new.


newDoc addNode: ([Link] name: 'xml-stylesheet'
text: 'href="[Link]" type="text/css"
title="Smalltalk Language" charset="UTF-8" ').
Evaluate and inspect the above code in a workspace to see that it
produces what we want.
Note that the XML prologue line,
<?XML version='1.0'?>
while it looks like a processing instruction, technically is not. It, together
with DTD declarations, is part of the prologue rather than part of the XML
data itself. No support for these items is included in the XML framework
at this time, and so they must be written separately, before the XML data.
Refer to “Writing the XML Document” below for a suggested approach.

Writing the XML Document


Once you have built a DOM tree, you can write it out as XML on a Stream.
The stream can be on a file or a communication channel.
Remember that the XML framework does not support all aspects of an
XML document, such as the prolog and any document type definition
information. We can handle this, however, by writing this information on
the write stream before the document itself.
To write the document on the stream, send a saxDo: message to the
Document with a SAXWriter instance as argument. The SAXWriter has its
output set to the output stream.
Suppose the goal is this document:
<?xml version="1.0"?>
<!DOCTYPE XML SYSTEM "[Link]">
<?xml-stylesheet href="[Link]" type="text/css"
title="Smalltalk Language" charset="UTF-8" ?>
<xml>Hello, world!</xml>
We create the [Link], which has the document content:
newDoc := [Link] new.
newDoc addNode: ([Link] name: 'xml-stylesheet'
text: 'href="[Link]" type="text/css"
title="Smalltalk Language" charset="UTF-8" ').
newDoc addNode: ([Link] tag: 'xml').
newDoc root addNode: ([Link] text: 'Hello, world!').
Create an output stream:

532 VisualWorks
Using XML Namespaces

str := 'c:\xmlTest\[Link]' asFilename writeStream.


Next, write the prolog and any DTD information:
str nextPutAll: '<?xml version="1.0"?>'; cr.
str nextPutAll: '<!DOCTYPE XML SYSTEM "[Link]">'; cr.
Finally, we create a SAXWriter, write the document, and close the stream:
writer := SAXWriter new output: str.
[newDoc saxDo: writer] ensure: [str close].
Examine the resulting file to see that it is what we expected.

Using XML Namespaces


XML namespaces allow documents to employ multiple markup
vocabularies without collision. For example, different parts of a document
might need to refer to different elements both named “employee”. XML
namespaces provide a mechanism for differentiating these references by
associating each with a URI.

Declare namespaces
A Document can specify one or more namespaces for resolving element
or attribute names within the document. A root element often specifies a
namespace, such as this, from [Link]
<html xmlns="[Link]
A document can also have multiple namespaces, one of which may have
no prefix, as in the above. All additional namespaces must have a prefix.
To specify two XML namespaces, one without and the other with a prefix,
the XML is specified like this:
< html xmlns="[Link]
xmlns:foo="[Link] >
To declare these namespace specifications in an XML Document in
VisualWorks, create a Dictionary containing these namespaces, and then
add the Dictionary to the document root element by sending it a
namespaces: message. The Dictionary contains associations between a
prefix string and the URI string. A namespace without a prefix is
associated with an empty prefix.

Application Developer’s Guide 533


Chapter 24 - XML Framework

nsDict := Dictionary new.


nsDict at: '' put: '[Link]
at: 'foo' put: '[Link]
newDoc := [Link] new.
newDoc addNode: ( [Link] tag: 'XML' ).
newDoc root namespaces: nsDict.
Evaluate the above in a workspace and inspect newDoc to see that the
root element specifies the namespaces as intended.
There is one problem with the above example, however. If you inspect the
newDoc tag variable, which contains a NodeTag, there is no namespace
specified. This is a problem if you need to extract data from the DOM, or
pass it to a processor such as XSchema, XSLT, or XPath. To include the
namespace information in the document tag, modify the above to:
nsDict := Dictionary new.
nsDict at: '' put: '[Link]
at: 'foo' put: '[Link]
newDoc := [Link] new.
newDoc addNode: ( [Link] tag:
(NodeTag new qualifier: '' ns: (nsDict at: '') type: 'XML') ).
newDoc root namespaces: nsDict.
Note that holding the namespace declarations dictionary in a temporary
variable, as above, is not necessary (the dictionary could be defined
inline), but simplifies referring to the namespace, as it is in the tag
definition shown here.

Applying a namespace to an element


If you use namespaces, you should use them consistently, and include
the namespace in specifying the element tag. Do this by creating a
NodeTag, and specify the qualifier (namespace prefix name), namespace,
and type (tag name). This is the same as specifying the NodeTag for the
root element shown above. For example:
nsDict := Dictionary new.
nsDict at: '' put: '[Link]
at: 'foo' put: '[Link]

newDoc := [Link] new.


newDoc addNode:
( [Link] tag:
( NodeTag new qualifier: '' ns: ( nsDict at: '' ) type: 'XML' ) ).
newDoc root namespaces: nsDict.

534 VisualWorks
Using XML Namespaces

newDoc root addNode:


( ( [Link] tag:
( NodeTag new
qualifier: 'foo' ns: (nsDict at: 'foo') type: 'heading1' ) )
addNode: ((( [Link] tag:
(NodeTag new
qualifier: 'foo' ns: (nsDict at: 'foo') type: 'heading2'))
addNode: ( [Link] tag:
(NodeTag new qualifier: '' ns: (nsDict at: '') type: 'body') ) ) ) ).
which produces the XML:
<XML xmlns:foo="[Link]
xmlns="[Link]
<foo:heading1>
<foo:heading2>
<body/>
</foo:heading2>
</foo:heading1>
</XML>
Elements can also declare additional namespaces for use within their
scope. To do this, send a namespaces: message to the element, after it
has been created. In this example, while both the heading1 and heading2
elements specify the foo namespace qualifier, heading2 is in a different
namespace than heading 1 due to the new declaration:
nsDict1 := Dictionary new.
nsDict1 at: '' put: '[Link]
at: 'foo' put: '[Link]
nsDict2 := Dictionary new.
nsDict2 at: 'foo' put: '[Link]

newDoc := [Link] new.


newDoc addNode:
( [Link] tag:
(NodeTag new qualifier: '' ns: (nsDict1 at: '') type: 'XML') ).
newDoc root namespaces: nsDict1.

newDoc root addNode:


( ( [Link] tag:
(NodeTag new
qualifier: 'foo' ns: ( nsDict1 at: 'foo' ) type: 'heading1' ) )
addNode: ( ( [Link] tag:
( NodeTag new
qualifier: 'foo' ns: ( nsDict2 at: 'foo' ) type: 'heading2' ) )
namespaces: nsDict2 ;
addNode: ( [Link] tag:
(NodeTag new qualifier: '' ns: (nsDict1 at: '') type: 'body') ) ) ).

Application Developer’s Guide 535


Chapter 24 - XML Framework

The resulting XML is:


<XML xmlns:foo="[Link]
xmlns="[Link]
<foo:heading1>
<foo:heading2 xmlns:foo="[Link]
<body/>
</foo:heading2>
</foo:heading1>
</XML>

Assigning a namespace to an attribute


Attribute names can be assigned a namespace to, as for elements.
Again, instead of a simple String for the name, you define and assign a
NodeTag. So, expanding the example used earlier for attributes, you can
assign a namespace as follows:
nsDict := Dictionary new.
nsDict at: '' put: '[Link]
at: 'foo' put: '[Link]

attrGroup := Array
with: ([Link]
name:
(NodeTag new qualifier: 'foo' ns: (nsDict1 at: 'foo') type: 'ALIGN')
value: 'left' )
with: ([Link]
name: (NodeTag new qualifier: '' ns: (nsDict1 at: '') type: 'SRC')
value: '[Link]

newDoc := [Link] new.


newDoc addNode:
( [Link] tag:
(NodeTag new qualifier: '' ns: (nsDict at: '') type: 'XML') ).
newDoc root namespaces: nsDict.
newDoc root addNode:
( [Link]
tag: (NodeTag new qualifier: '' ns: (nsDict at: '') type: 'IMG')
attributes: attrGroup elements: nil ).
Namespace declarations are not allowed in attribute specifications.

536 VisualWorks
Building a SAX driver

Building a SAX driver


SAX (Simple API for XML) is an event-driven interface for accessing XML
documents without having to model the whole document in memory.
Using SAX is often preferred, such as when the application needs to
construct its own data structure from the XML document. In such a case,
modeling the entire node tree first only to discard it is inefficient.
A SAX parser breaks a document into a linear set of events. For example,
the XML document:
<?xml version="1.0"?>
<doc>
<para>Hello, world!</para>
</doc>
is rendered as this series of events:
start document
start element: doc
start element: para
characters: Hello, world!
end element: para
end element: doc
end document
The application specifies how to process each event in its event handlers.

Handling SAX events


To create a SAX application, define a custom SAX driver as a subclass of
SAXDriver or one of its subclasses. Your driver class defines handler
methods for each of the SAX parsing events, specifying the action to take
for each element or attribute of interest.
The default action for events, defined in SAXDriver, is to do nothing. Your
driver overrides these with more appropriate handling. The following are
the basic events to handle. For additional events provided for special
purposes, browse the content handler method category in SAXDriver, and
read the method comments.
startDocument
Triggered once at the start of the document.
endDocument
Triggered once at the end of the document.

Application Developer’s Guide 537


Chapter 24 - XML Framework

startElement: namespaceURI localName: localName qName: name


attributes: attrList
Triggered by an element start tag. namespaceURI is the namespace
URI, or nil if there is none. localName is the name of the element,
without prefix. name is the literal name of the element, or nil if
processing namespaces. attrList is a SequenceableCollection of Attribute
instances.
endElement: namespaceURI localName: localName qName: name
Triggered by an element end tag. Parameters are as described for
startElement:localName:qName:attributes:.
startPrefixMapping: prefix uri: anURI
Triggered by an element with a namespace declaration. prefix is a
String, if a prefix is specified in the declaration. anURI is the
namespace URI, as a String.
endPrefixMapping: prefix
Triggered by the closing tag for an element that declared the
namespace. prefix, if any, is the declared namespace prefix as a
String.
characters: aString
Triggered by character data (CDATA). aString contains the character
data.
skippedEntity: name
Triggered by a skipped entity. name is the name of the skipped entity.
Parameter entity names start with '%'. If the entity is an external DTD
subset, name is '[dtd]'.
processingInstruction: name data: dataString
Triggered by a processing instruction. name is the instruction name,
and dataString is the instruction data.
ignorableWhitespace: aString
Triggered by ignorable whitespace in the document. aString contains
the whitespace characters.
For example, to handle the simple document above, a driver should
handle start and end document, start and end element, and character
events. These five methods could be implemented, say in MySAXDriver, to
simply write information to the Transcript:
characters: aString
Transcript show: 'cdata: ', aString; cr.

538 VisualWorks
Building a SAX driver

startDocument
Transcript show: 'Start of Document'; cr.

endDocument
Transcript show: 'End of Doc';cr.

startElement: nsURI localName: lName qName: name attributes: attrList


Transcript show: 'start: ', name; cr.

endElement: namespaceURI localName: localName qName: name


Transcript show: 'end: ', name; cr.
To exercise this driver on the example document above, evaluate this in a
workspace:
| doc p |
doc := '<?xml version="1.0"?><doc><para>Hello, world!</para></doc>'
readStream.
p := XMLParser new.
p handlers: MySAXDriver new.
p validate: false.
p parse: doc.

Configuring SAX features and properties


VisualWorks supports the standard SAX2 interface for querying and
setting the parser’s feature and property set, to control the parser’s
behavior. Features and properties are identified by a URI with which is
associated a Boolean value.
The general messages to set and get parser features and properties are:
atFeature: featureURI
Returns the Boolean value of featureURI, if recognized; otherwise
raises a SAXNotRecognizedException exception.
atFeature: featureURI put: aBoolean
Sets the value of featureURI to aBoolean, if recognized; otherwise
raises a SAXNotRecognizedException exception.
atProperty: propertyURI
Returns the Boolean value of propertyURI, if recognized. No properties
are recognized, by default, so returns SAXNotRecognizedException.
atProperty: propertyURI put: aBoolean
Sets the value of propertyURI to aBoolean, if recognized. No properties
are recognized, by default, so returns SAXNotRecognizedException.

Application Developer’s Guide 539


Chapter 24 - XML Framework

Several common features are represented by shared variables defined in


the [Link] namespace. Each shared variable holds a default URI for
the feature, which is set in the variable’s initialization string. Note that only
the SAX namespace, namespace-prefixes, and validating features are
currently supported by the VisualWorks XML framework, though you may
add support for additional features and properties.
SAXExternalGeneralEntities
Not currently supported. Attempting to set or get the value raises a
SAXNotSupportedException. Would be set false to ignore external
general entities in the document.
SAXExternalParameterEntities
Not currently supported. Attempting to set or get the value raises a
SAXNotSupportedException. Would be set false to ignore external
parameter entities in the DTD.
SAXNamespace
Default true. Set to true if the parser should process namespaces, or
false if the parser should ignore xmlns attributes.
SAXNamespacePrefixes
Default false. Set to true if xmlns attributes should appear in the
attribute list of an element, or false if they should be filtered out.
SAXValidate
Default true. Set to true if the parser should do full validation, or false
to suppress validation.
For accessing the values of the SAXNamespace, SAXNamespacePrefixes, and
SAXValidate features, send these messages to the parser:
isValidating
Returns the Boolean value of the validation feature (SAXValidate).
validate: aBoolean
Sets the Boolean value of the validation feature (SAXValidate).
processNamespaces
Returns the Boolean value of the namespaces feature
(SAXNamespace).
processNamespaces: aBoolean
Sets the Boolean value of the namespace feature (SAXNamespace).
showNamespaceDeclarations
Returns the Boolean value of the namespace-prefixes feature
(SAXNamespacePrefixes).

540 VisualWorks
Building a SAX driver

showNamespaceDeclarations: aBoolean
Sets the Boolean value of the namespace-prefixes feature
(SAXNamespacePrefixes).
Setting the validating feature using the validate: message was illustrated
above, to parse a document without a DTD (see “Parsing an XML
document”). Using the more general messages, turning off validation can
be done like this:
parser := XMLParser new.
parser atFeature: SAXValidate put: false.
The feature can also be identified by an URI, in which case the above
could be:
parser := XMLParser new.
parser atFeature: '[Link] put: false.
For setting or getting SAX feature and property values, you should trap
SAXNotRecognizedException and SAXNotSupportedException.
parser := XMLParser new.
featureStr := '[Link] .
[ [ parser atFeature: featureStr ]
on: SAXNotRecognizedException
do: [ :e | Dialog warn: 'Feature ', featureStr, ' is not recognized.' ] ]
on: SAXNotSupportedException
do: [ :e | Dialog warn: 'Feature ', featureStr, ' is not supported.' ]

Document Fragments
When using XML to exchange data, it is frequently inconvenient, or
inefficient, to have to parse an entire document up to the element that
one is actually interested in. For example, if you are only interested in one
chapter (e.g., chapter 23), or one paragraph, of a book, it would be
inefficient to have to parse all of the book up to that element.
Document fragments provide a way to represent a part of a document.
The challenge for using fragments is to have enough context to be able to
parse the fragment correctly.
The VisualWorks XML framework supports document fragments in the
[Link] class. The main difference between a Document
and a DocumentFragment is that a DocumentFragment does not require a
single top-level element, but may have a sequence of elements at its top
level. It may also have character data outside of an element. So, for
example, a document fragment could include:

Application Developer’s Guide 541


Chapter 24 - XML Framework

<body>Some introductory text.</body>


<heading2>
Some heading
<body>Discussion of this topic</body>
</heading2>
<heading2>
Some other heading
<body>Discussion of this topic</body>
</heading2>
This is understood as being parsed within a larger XML context that
provides the missing information.

Building a fragment
To build the above fragment, send the appropriate addNode: messages to
an instance of DocumentFragment.
docFrag := [Link] new.
docFrag addNode: ( ( [Link] tag: 'body' )
addNode: ( [Link] text: 'Some introductory text.' ) ).
docFrag addNode: ( ( ( [Link] tag: 'heading2' )
addNode: ([Link] text: 'Some heading' ) )
addNode: ( ([Link] tag: 'body')
addNode: ( [Link] text: 'Discussion of this topic.') ) ).
docFrag addNode: ( ( ( [Link] tag: 'heading2' )
addNode: ([Link] text: 'Some heading' ) )
addNode: ( ([Link] tag: 'body')
addNode: ( [Link] text: 'Discussion of this topic.') ) ).
Attempting this construct with a Document instance would result in errors
due to the multiple top-level nodes, but it is acceptable as a
DocumentFragment.

Parsing a fragment
If an XML document references a fragment as an entity, you can parse
the entire document as usual. The fragment is simply included in the
document as if it were physically present within the XML.
A fragment-aware application, however, will want to deal with fragments it
may receive from a data source. The application will have to be able to
provide the context necessary for including the fragment in a document.
In the case of the above fragment, the context may be simply:

542 VisualWorks
XSL stylesheet processing

<document>
<heading1>
Title
</heading1>
***insert fragment here***
</heading1>
</document>
The XML framework provides no specific support for providing this
context. Recommendations are available from the World Wide Web
Consortium (see [Link] but it is the
responsibility of your application to implement a strategy.

XSL stylesheet processing


VisualWorks supports applying an XSL stylesheet to an XML file to
transform the XML file into another representation.
Most XSL Transformation elements are supported, and are implemented
as subclasses of XSLCommand.

Loading XSL support


XSL support is an add-in component to VisualWorks. To use XSL
facilities, load the XSL parcel ([Link]).
XSL support classes are in the XSL namespace. Your application may
need to import this namespace into its own namespace or into relevant
classes.

Applying a stylesheet to a document


XMLParser does not automatically apply a stylesheet to an XML
document, even if the stylesheet is specified in the document. Instead,
you generate an XSL rule database from the stylesheet and apply it to
the parsed XML document.
For example (borrowed from The XML Bible, second edition, by Elliotte
Rusty Harold), suppose we have an XML document representing the
periodic table ([Link]):

Application Developer’s Guide 543


Chapter 24 - XML Framework

<?xml version="1.0"?>
<?xml-stylesheet type="text/xml" href="[Link]"?>
<PERIODIC_TABLE>
<ATOM STATE="GAS">
<NAME>Hydrogen</NAME>
<SYMBOL>H</SYMBOL>
<ATOMIC_NUMBER>1</ATOMIC_NUMBER>
<ATOMIC_WEIGHT>1.00794</ATOMIC_WEIGHT>
<BOILING_POINT UNITS="Kelvin">20.28</BOILING_POINT>
<MELTING_POINT UNITS="Kelvin">13.81</MELTING_POINT>
<DENSITY UNITS="grams/cubic centimeter">
<!-- At 300K, 1 atm -->
0.0000899
</DENSITY>
</ATOM>
<ATOM STATE="GAS">
<NAME>Helium</NAME>
<SYMBOL>He</SYMBOL>
<ATOMIC_NUMBER>2</ATOMIC_NUMBER>
<ATOMIC_WEIGHT>4.0026</ATOMIC_WEIGHT>
<BOILING_POINT UNITS="Kelvin">4.216</BOILING_POINT>
<MELTING_POINT UNITS="Kelvin">0.95</MELTING_POINT>
<DENSITY UNITS="grams/cubic centimeter"><!-- At 300K -->
0.0001785
</DENSITY>
</ATOM>
</PERIODIC_TABLE>
and an XSL document ([Link]) to transform the document into
HTML:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="[Link]
<xsl:template match="PERIODIC_TABLE">
<html>
<xsl:apply-templates/>
</html>
</xsl:template>
<xsl:template match="ATOM">
<P>
<xsl:apply-templates/>
</P>
</xsl:template>
</xsl:stylesheet>
First generate the rules database, then parse the document and apply the
rules as follows:

544 VisualWorks
Using XPath

xslRules := ( [Link] new ) readFileNamed: 'c:\xmlTest\[Link]'.


parser := XMLParser new validate: false.
doc := parser parse: 'c:\xmlTest\[Link]' asFilename readStream.
transDoc := xslRules process: testDoc.
The result is a new document, actually a DocumentFragment, that has been
transformed according to the rules in the stylesheet:
<html>
<P>
Hydrogen
H
1
1.00794
20.28
13.81
0.0000899
</P>
<P>
Helium
He
2
4.0026
4.216
0.95
0.0001785
</P>
</html>
Note that the XSL namespace declared in the stylesheet must be
[Link] If it is not, the resulting document
contains the stylesheet itself and not a transformed document.
There are several examples of applying an XSL transformation in class
methods of RuleDatabase which you can examine and execute.

Using XPath
XPath is a language for addressing parts of an XML document. XPath
models a document as a tree structure, allowing elements to be accessed
by specifying a path to those elements, like a filesystem path.

Creating a path expression


An XPath expression is a string specifying selection criteria for a
collection of nodes in a document. The XPath specification
([Link] provides the full, abstract syntax for XPath
expressions. A few examples are:

Application Developer’s Guide 545


Chapter 24 - XML Framework

Expression Selection
/AAA The root node AAA
/AAA/BBB/CCC All elements tagged CCC that are children of BBB
that are children of root AAA.
//CCC All elements CCC in the document
//BBB/CCC All elements CCC that are children of BBB
//BBB/* All elements that are children of BBB
/*/*/* All elements with two ancestors
//BBB/CCC[2] Each second instance of element BBB that is a child
of BBB
//CCC[text()] All text elements in any CCC
//BBB | //CCC All elements BBB and CCC
/AAA/BBB/descendant::*i All elements that are descendents of /AAA/BBB
//@id All attributes id
//BBB[@id] Al elements BBB with an id attribute

The return value of an XPath expression can be either a Number, String,


Boolean, or XPathNodeContext. The above expressions return an
XPathNodeContext, which is a collection of XML nodes.
To use an XPath expression in VisualWorks, it must be parsed, using
XPathParser. For example:
exprString := '//CCC[text()]'.
expr := [Link] new
parse: exprString as: #expression.
In this example, exprString holds is assigned some legal (per the XPath
specification) XPath expression, as a String. The XPathParser returns an
instance of XPathRoot, which can then be applied to an XML node to
retrieve the desired information.
If the XML uses namespaces, you must also provide the parser with an
XML node that gives the context in which to resolve the namespace
qualifiers. For example, if the expression includes a namespace qualifier
“foo”, a node defining the qualifier must be provided:

546 VisualWorks
Using XPath

exprString := '//foo:CCC[text()]'.
expr := [Link] new
xmlNode: ( myDoc root) ;
parse: exprString as: #expression.
The element’s sole purpose is to map "foo" to an URL, but could be, for
example, the document root node, as done above. If you don't use
namespaces in the path, the XML Element is optional.

Applying an XPath expression


You apply the expression to an XML node by sending a
xpathValueFor:variables: message to the XPathRoot instance, the result of
parsing the expression string.
result := expr
xpathValueFor: otherXmlNode
variables: Dictionary new.
The XML document, or node, to be searched is the first argument value.
The Dictionary passed as the second argument maps variable names to
values, and is only important if the XPath expression uses variable
references.
The return value of an XPath expression can be either a Number, String,
Boolean, or XPathNodeContext, which is a collection of nodes. Usually the
programmer will know, based on the syntax of the expression string, what
type of value will be returned. These four return types can be converted
amongst themselves using xpathAsBoolean, xpathAsString, and
xpathAsNumber. These messages use the XPath conversion rules.

Selecting nodes with an XPath


For expressions that return a collection of nodes, you can now use the
XPathRoot to select nodes. First, get the XPathNodeContext by applying
the expression to a node. To do this, send an xpathValueFor:variables:
message to the XPathRoot instance. You can then retrieve the nodes as a
sorted or unsorted collection, by sending sortedNodes or unsortedNodes
message:
nodeSet := expr
xpathValueFor: otherXmlNode
variables: Dictionary new.
nodeSet xpathIsNodeSet
ifTrue: [nodeSet := nodeSet unsortedNodes].

Application Developer’s Guide 547


Chapter 24 - XML Framework

XML Error Handling


The VisualWorks XML engine is a SAX engine, so all error handling is
provided by SAXException subclasses:
Error
SAXException
SAXNotRecognizedException
SAXNotSupportedException
SAXParseException
InvalidSignal
MalformedSignal
BadCharacterSignal
WarningSignal
Most of these exceptions are raised only during parsing, the exceptions
being SAXNotRecognizedException and SAXNotSupportedException, which are
raised when querying or setting a SAX parser’s features or properties.
Catching these exceptions is shown under “Configuring SAX features and
properties”.)
The argument passed into the handler block is an instance of the specific
error class, which you can use for further handling.
parser := XMLParser new.
[ pdoc := parser parse:
'..\help\01-xml-language\[Link]' asFilename ]
on: SAXException do: [ :e | Transcript show: e printString ; cr ]

548 VisualWorks
A
VisualWorks Smalltalk Syntax Description

In the sections that follow, the syntax of the Smalltalk language is formally
defined with the aid of Backus-Naur form. The following characters have
special meanings unless they are enclosed in quotation marks.

Character Description
= expands to
‘’ terminal (single quotes surround an atomic literal)
“” comment (double quotes surround a comment)
| or
+ one or more
* zero or more
[] zero or one
\ excluding the following
... through
() grouping
<> keyboard key

Lexical Primitives
The lexical syntax is formally ambiguous, in that, for example, the string
abc: can be parsed either as an identifier followed by a non-quote-
character, or as a keyword. We resolve this ambiguity in all cases in favor
of the longest token that can be formed starting at a given point in the
source text. Thus abc: is always considered to be a keyword, if the a is
the beginning of the token.

Application Developer’s Guide 549


Appendix A - VisualWorks Smalltalk Syntax Description

Character Classes
The definition of token is not used anywhere else in the syntax; it is
supplied only for exposition.

token = number | identifier | special-character | keyword | block-argument |


assignment-operator | binary-selector | character-constant | string
digit = ‘0’ | ... | ‘9’
letter = ‘A’ | ... | ‘Z’ | ‘a’ | ... | ‘z’ | non_ASCII_Unicode_letters
binary-character = ‘+’ | ‘/’ | ‘\’ | ‘*’ | ‘~’ | ‘<’ | ‘>’ | ‘=’ | ‘@’ | ‘%’ | ‘|’ | ‘&’ |
‘?’ | ‘!’ | ‘,’ | Unicode_Symbol_math | Unicode_Symbol_currency |
Unicode_Symbol_other
whitespace-character = <tab> | <space> | <newline>
non-quote-character = digit | letter | binary-character | whitespace-character |
‘[’ | ‘]’ | ‘{’ | ‘}’ | ‘(’ | ‘)’ | ‘_’ | ‘^’ | ‘;’ | ‘$’ | ‘#’ | ‘:’ | ‘.’ | ‘-’ | ‘`’

Numbers
digits = digit+
big-digits = (digit | letter)+ “as appropriate for radix”
number = digits (‘r’ [‘-’] big-digits | optional-fraction-and-exponent)
optional-fraction-and-exponent = [‘.’ digits] [(‘e’ | ‘d’ | ‘s’) [‘-’] digits]

Other Lexical Constructs


extended-letter = letter | ‘_’
identifier = extended-letter (extended-letter | digit)*
block-argument = ‘:’ identifier
assignment-operator = ‘:’ ‘=’
keyword = identifier ‘:’
binary-selector = (‘-’ | binary-character) [binary-character]
unary-selector = identifier
character-constant = ‘$’ (non-quote-character | ‘'’ | ‘"’)
symbol = identifier | binary-selector | keyword+
string = ‘'’ (non-quote-character | ‘'’ ‘'’ | ‘"’)* ‘'’

550 VisualWorks
Atomic Terms

comment = ‘"’ (non-quote-character | ‘'’ )* ‘"’


separator = (whitespace-character | comment)+

Atomic Terms

literal = [‘-’] number | named-literal | symbol-literal | character-literal | string |


array-literal | byte-array-literal | binding-literal
named-literal = ‘nil’ | ‘true’ | ‘false’
symbol-literal = ‘#’ (symbol | string)
array-literal = ‘#’ array-literal-body
array-literal-body =
‘(’ (literal | symbol | array-literal-body | byte-array-literal-body)* ‘)’
byte-array-literal = ‘#’ byte-array-literal-body
byte-array-literal-body = ‘[’ number* “integer between 0 and 255” ‘]’
binding-name = identifier \ ( named-literal | pseudovariable-name | ‘super’ )
extended-binding-name = binding-name [ ( ‘.’ binding-name )* ]
binding-reference = ‘#’ ‘{’ extended-binding-name ‘}’

Note that “binding” here is used in a more general sense than elsewhere
in this document, to include variables and bindings.
We originally intended that the definition of array-literal be the following:
array-literal = ‘#’ ‘(’ literal* ‘)’
This would have simplified the syntax, eliminating the need for array-
literal-body and byte-array-literal-body as separate constructs. However,
this definition is not backward-compatible with previous versions of the
Smalltalk-80 language. Specifically, it requires symbols and arrays
appearing within an array literal to be quoted with #. Because of this, we
adopted the more complex definition.

Application Developer’s Guide 551


Appendix A - VisualWorks Smalltalk Syntax Description

Expressions and Statements

primary = extended-binding-name | binding-reference | pseudovariable-name |


literal | block-constructor | ‘(’ expression ‘)’
pseudovariable-name = ‘self’ | ‘thisContext’
unary-message = unary-selector
binary-message = binary-selector primary unary-message*
keyword-message = (keyword primary unary-message* binary-message*)+
cascaded-messages = (‘;’ (unary-message | binary-message |
keyword-message))*
messages = unary-message+ binary-message* [keyword-message] |
binary-message+ [keyword-message] | keyword-message
rest-of-expression = [messages cascaded-messages]
expression = ( extended-binding-name | binding-reference )
(assignment-operator expression | rest-of-expression)
| keyword ‘=’ expression “see below” |
primary rest-of-expression | ‘super’ messages cascaded-messages
expression-list = expression (‘.’ expression)* [‘.’]
temporaries = ‘|’ temporary-list ‘|’ | ‘||’
temporary-list = declared-variable-name*
declared-variable-name = binding-name
statements = [‘^’ expression [‘.’] | expression [‘.’ statements]]
block-constructor = ‘[’ [block-declarations] statements ‘]’
block-declarations = temporaries |
block-argument+ (‘|’ [temporaries] | ‘||’ temporary-list ‘|’ | ‘|||’)

In order to keep lexical analysis and parsing separate, but still allow
constructs like x:=3 (without a space, making it look like a keyword, x:), we
have had to introduce the alternative
keyword ‘=’ expression
for assignment. This should really be read as though it were
binding-name ‘:=’ assignment

552 VisualWorks
Methods

Methods

method = message-pattern pragma* [temporaries] statements


message-pattern = unary-selector | binary-selector declared-variable-name |
(keyword declared-variable-name)+
pragma = ‘<’ ( keyword literal )+ ‘>’

A special case of a pragma are the <primitive: N> and


<primitive: N errorCode: errName> pragmas. In these cases, the method
invokes a primitive before or instead of invoking the following statements.
The N may be an integer between 0 and 65535. The errName is not a
literal, but a binding-reference, which identifies an object explaining why
the primitive could not run successfully.

Application Developer’s Guide 553


B
Special Characters

A variety of special characters, such as the yen sign ( ¥ ), can be typed


into VisualWorks text views by using a special key sequence. A prefix
known as the compose key is the first element in the key sequence,
followed by two characters that define the desired special character. On
some keyboards, a single key has been defined to send the required
sequence, such as the dollar sign on American keyboards. If the font in
use does not contain a character, it is displayed as a black square.
For example, <Control>-k = Y is the sequence for composing the yen
sign.
The default compose key is <Control>-k. To change the default key,
execute the expression CharacterComposer setComposeKey. The new
compose key will affect newly created views but not existing views.
To create special characters programmatically, send an asCharacter
message to the numeric representation of the character. Numeric codes
are given in the following charts as hexadecimal values, and so can be
displayed as follows, again, for the yen sign:
Transcript cr; nextPut: 16r00A5 asCharacter; flush
Note that not all characters can be displayed on all platforms, but are
dependent on fonts installed on the platform. Unsupported characters are
displayed as a black box.

554 VisualWorks
Composed Characters

Composed Characters
The following table lists the special characters in the left column. The two
characters that make up the body of the compose sequence are shown in
the second column. The hexadecimal equivalents of these two columns
are displayed in the right-hand columns. A description is shown in the
middle column.

Special character Composition sequence Description Character


hex code
characters hex codes
# ++ 2B 2B number sign 0023
$ |S 7C 53 dollar sign 0024
@ AA 41 41 at 0040
[ (( 28 28 left bracket 005B
\ // 2F 2F backslash 005C
] )) 29 29 right bracket 005D
{ (- 28 2D left brace 007B
| /^ 2F 5E vertical bar 007C
} )- 29 2D right brace 007D
~ ^^ 5E 5E tilde 007E
¡ !! 21 21 inverted exclamation 00A1
¢ |c 7C 63 cent sign 00A2
£ =L 3D 4C pound sign 00A3
¤ xo 78 6F currency 00A4
¥ =Y 3D 59 yen sign 00A5
§ !s 21 73 section 00A7
© OC 4F 43 copyright 00A9
ª _a 5F 61 ordfeminine 00AA
« << 3C 3C << 00AB
- -- 2D 2D horizontal bar 00AD
® OR 4F 52 registered 00AE

° ^0 5E 30 degree sign 00B0

± +- 2B 2D plus or minus 00B1

Application Developer’s Guide 555


Appendix B - Special Characters

Special character Composition sequence Description Character


hex code
characters hex codes
2
^2 5E 32 superscript 2 00B2
3
^3 5E 33 superscript 3 00B3
µ /u 2F 75 micro, mu 00B5
¶ !p 21 70 paragraph sign 00B6

· .^ 2E 5E middle dot 00B7


1
^1 5E 31 superscript 1 00B9
º _o 5F 6F ordmasculine 00BA
» >> 3E 3E >> 00BB
¼ 14 31 34 one fourth 00BC
½ 12 31 32 one half 00BD
¾ 34 33 34 three fourths 00BE
¿ ?? 3F 3F inverted ? 00BF
Æ AE 41 45 AE diphthong 00C6
Ð +D 2B 44 capital eth 00D0

× xx 78 78 cross 00D7
Ø /O 2F 4F O slash 00D8
|O 7C 4F capital thorn 00DE
ß ss 73 73 German double-s 00DF
æ ae 61 65 ae diphthong 00E6
+d 2B 64 small eth 00F0
÷ -: 2D 3A divide 00F7
ø /o 2F 6F o slash 00F8
|o 7C 6F small thorn 00FE
Ð -D 2D 44 D with stroke 0110
d -d 2D 64 d with stroke 0111
H -H 2D 48 H with stroke 0126
h -h 2D 68 h with stroke 0127
i .i 2E 69 dotless i 0131

556 VisualWorks
Composed Characters

Special character Composition sequence Description Character


hex code
characters hex codes
IJ IJ 49 4A IJ ligature 0132
ij ij 69 6A ij ligature 0133
k kk 6B 6B kra 0138
L. .L 2E 4C L with dot 013F
l. .l 2E 6C l with dot 0140
L -L 2D 4C L with stroke 0141
l -l 2D 6C l with stroke 0142
’n n’ 6E 27 n apostrophe 0149
N) 4E 29 capital eng 014A
n) 6E 29 small eng 014B
Œ OE 4F 45 OE diphthong 0152
œ oe 6F 65 oe diphthong 0153
T -T 2D 54 T with stroke 0166
t -t 2D 74 t with stroke 0167
‘ ‘1 60 31 single quote left 2018
’ ’1 27 31 single quote right 2019

“ ‘‘ 60 60 double quote left 201C

” ’’ 27 27 double quote right 201D

€ =C 3D 43 euro 20AC

™ TM 54 4D trademark 2122
Ω om 6F 6D omega 2126
1/ 18 31 38 one eighth 215B
8
3/
8 38 33 38 three eighths 215C
5
/8 58 35 38 five eighths 215D
7
/8 78 37 38 seven eighths 215E
← -< 2D 3C arrow left 2190
↑ |^ 7C 5E arrow up 2191
→ -> 2D 3E arrow right 2192

Application Developer’s Guide 557


Appendix B - Special Characters

Special character Composition sequence Description Character


hex code
characters hex codes
↓ |v 7C 76 arrow down 2193
no 6E 6F musical note 266A
j .j 2E 6A dotless j FC10

Diacritical Marks
A diacritical mark, such as a circumflex ( ^ ), is combined with a character
in a similar fashion. The compose key (<Control>-k by default) comes
first, then a character representing the diacritical mark (taken from the
table below) and finally the base character. For example, to get ñ, you
would type <Control>-k, followed by a tilde ( ~ ) and the letter ‘n’.
Programmatically, add the diacritical by sending, for example:
| baseChar diacrit composedChar |
baseChar := $a.
diacrit := 16r0300 asCharacter.
composedChar := baseChar composeDiacritical: diacrit.
Transcript cr; nextPut: composedChar; flush
You can also identify the diacritical by name:
diacrit := Character diacriticalNamed: #grave.
The names are specified in the diacriticalNamed: method definition.
In the following table, the diacritical mark is shown in position relative to a
broken circle representing the base character.

Diacritical mark Composition sequence Description Diacritical


hex code
character hex code

` 60 grave 0300

’ 27 acute 0301

^ 5E circumflex 0302

~ 7E tilde 0303

- 2D macron 0304

u 75 breve 0306

. 2E dot above 0307

558 VisualWorks
Diacritical Marks

Diacritical mark Composition sequence Description Diacritical


hex code
character hex code
" 22 dieresis 0308

* 2A ring above 030A

: 3A double acute 030B

v 76 hacek (caron) 030C

, 2C cedilla 0327

; 3B ogonek 0328

_ 5F underline 0332

Application Developer’s Guide 559


C
Virtual Machines

VisualWorks provides special-purpose virtual machines for development,


deployment, server (headless) deployment, linking with external libraries,
and for engine debugging.
This appendix describes each virtual machine and its appropriate use.
These optional VMs are available for each supported platform.
For details on command line options used when starting an engine, see
“Virtual Machine Command Line Options” on page 562.

VisualWorks Virtual Machines


By default, the standard development and deployment virtual machines
are installed in the bin/<platform>/ subdirectory of the root
VisualWorks installation directory. Special-purpose engines are installed
in subdirectories, as noted below.

Production Engines
The production engines are called visual or [Link]. These are
the standard engines, which are stripped of all debug symbols, and are
suitable for deploying VisualWorks applications because of their relatively
small size.
There are also "unstripped" versions of the production engines, which
can be useful in your own development. These include debug symbols,
and so, if you encounter a crash (e.g. by calling external C or COM code
incorrectly), you may be able to use your platform's debugger to
investigate the problem. They are named vwPlatformName, for example
vwlinux86 or [Link], to distinguish them from the standard
engines.

560 VisualWorks
VisualWorks Virtual Machines

Debug Engines
Debug engines include debug symbols and have assertion-checking
code compiled throughout. They are considerably slower than their
production counterparts, but are suitable for debugging object engine
crashes. They are named vwPlatformNamedbg, for example
vwlinux86dbg or [Link], and are located in the debug/
subdirectory for each platform engine. All engines contain debug
functions that can be used to examine the state of the system, trace the
Smalltalk (engine) stack, and so on, using platform debuggers.

Assert Engines
These engines are fully-optimized, but with asserts compiled-in and
enabled. They run at least 50% of the speed of the fully-optimized
production engine, even though they check engine asserts. For for
normal development, this provides perfectly acceptable performance
while checking the engine during normal use. They are named
vwPlatformNameast, for example vwlinux86ast or [Link],
and are located in the asser/ subdirectory for each platform engine.

Headless and Headful Engines


Most of the Unix platforms now provide a headless engine. These
engines exclude the GUI and window management primitives,
dynamically loading them as required from a shared library. The all-in-
one, “headful” engines are still provided.
The headless engines are named in the vw<platform> format, as
usual. The GUI inclusive engines are named vw<platform>gui.
Each headless engine automatically searches for an associated GUI
shared library when a GUI primitive is first invoked. Engines look for a
shared library of the same name as the engine with "[Link]" appended.
For example, the vwlinux86 engine is headless, and will search for
[Link] if a GUI primitive is invoked. Headful engines have
“gui” appended to their name, to the corresponding headful engine is
vwlinux86gui.

Linkable Object Engines


All VisualWorks object engines can access external code by dynamically
loading external libraries (called variously shared libraries, shared
objects, DLLs, etc.). This is the preferred way of interfacing to external
libraries. But, if required, you can statically link in code using the linkable

Application Developer’s Guide 561


Appendix C - Virtual Machines

object engines. These are called visual.o or [Link], and are in


the $(VISUALWORKS)/bin/<platform>/userprim directories, along
with associated makefiles.

Console Object Engines


In server configurations one may want to run the system from the
command-line, or console, possibly without a user interface (headless),
and possibly to read from standard input and/or write to standard output.
On the UNIX and Linux platforms, the standard object engines can run
from the command-line. But on Windows, GUI and command-line
applications require different executables. So for Windows we also
provide "console" versions of the three standard engines called,
[Link], [Link], [Link], which
can be used to read from standard input and/or write to standard output.

Virtual Machine Command Line Options


All platforms
When starting VisualWorks, you may specify the following command line
switches after the name of the virtual machine:
-?
Report the available object engine level command-line options.
-console
Open a console window for stdout and stderr.
-v
Report version information.
-h bytesValue
Request the specified number of extra bytes be reserved for heap
headroom at startup. The value is decimal, unless prefixed with O for
octal or Ox for hexadecimal. The value is bytes unless suffixed with k
for kilobytes, m for megabytes, or g for gigabytes.
-l loadPolicy
Specifies the image load policy:
promote - load all objects into perm space
demote - load all objects into old space
normal - (default) load all objects into their current space
-z bytes
Make the CompiledCodeCache, or native methods zone, bytes large.
On all platforms except MacOS, these additional switches are supported:

562 VisualWorks
Virtual Machine Command Line Options

-logo bmpFile
Displays the specified bitmap file as the startup splash screen. On
Windows, prepend .\ to a relative path name.
-nologo
Suppress the splash screen on startup
-sound wavFile
Play wavFile on image startup.
-nosound
Suppress playing the startup sound

Windows platforms
On MS Windows systems, this switch is supported:
-walltime
Use the lower-resolution GetSystemTimeAsFileTime instead of
QueryPerformanceCounter to drive the microsecond clock. This is
useful for long-running applications that are very time sensitive, since
the default QueryPerformanceCounter, while higher-resolution, tends
to drift from the wall clock over a period of hours.

Unix/Linux platforms
These headless engines support two new command-line switches:
-gui:
Load default GUI subsystem shared library on startup.
-guilib [Link]:
Load specified GUI subsystem shared library on startup.
The headless GUI engines currently do not support Input Management
when used with their GUI library. This is a bug we will fix in a subsequent
release. For now I18N users who want to use headful applications should
use the all-in-one engines.

Note: All UNIX and Linux VMs now write all herald information to
/dev/tty, instead of stdout, so VisualWorks can be used on a
pipe.

Application Developer’s Guide 563


Appendix C - Virtual Machines

System Colors on X11


The Windows and Mac OS window managers provide “schemes” that
define things like symbolic colours for button highlight, menu selection,
and so on. X11 provides no such niceties. Instead we have provided a
scheme built above Xrdb, the X11 "relational desktop database," which
allows you to define your own scheme.
On starting X11, the system arranges that the contents of .Xresources
(or .Xdeefaults on older systems) to be loaded into the Xrdb. One can
also use Xrdb directly to change settings while X11 is running (see
Xrdb(1)). An example of the relevant entries for .Xresources is:
St80*background: AntiqueWhite
St80*selectionForeground: White
St80*selectionBackground: RoyalBlue
St80*inactiveSelectionForeground: LightGrey
St80*inactiveSelectionBackground: RoyalBlue3
St80*menuBackground: LightCyan

St80a*background: moccasin
St80a*selectionForeground: White
St80a*selectionBackground: RoyalBlue
St80a*inactiveSelectionForeground: LightGrey
St80a*inactiveSelectionBackground: RoyalBlue3
St80a*menuBackground: LightCyan
Note that there are two schemes in the file, one for key St80 and one for
key St80a. The vm's default lookup key in Xrdb is St80. One can change
the key the vm uses using the -className className switch when
starting the X11 engines. For example:
$VISUALWORKS/bin/linux86/vwlinux86gui -className
St80a [Link]

564 VisualWorks
D
Code Critic Rules

This appendix summarizes the rules used by the browser’s Code Critic
tool. The rules are organized in five groups:
• Bugs
• Possible Bugs
• Unnecessary Code
• Intention Revealing
• Miscellaneous
Additionally, the Code Critic provides a set of predefined transformations
to be used as simple method-level code refactorings:
• Code Transformations

Application Developer’s Guide 565


Appendix D - Code Critic Rules

Bugs
Rule Description
Messages sent but not implemented Checks for messages that are sent by a
method, but no class in the system
implements such a message. These will
certainly cause a doesNotUnderstand:
message when they are executed.
Self/Super sends not implemented Similar to the "Message sent but not
implemented" check, but only checks
messages sent to self or super since these
can be statically typed.
Overrides a "special" message Checks that a class does not override a
message that is essential to the base system
(e.g., Object>>class).
References an undeclared variable Checks for references to a variable in the
Undeclared dictionary. If you remove a
variable from a class that is accessed by a
method, you will create an undeclared
variable reference for those methods that
accessed the variable.
Subclass responsibility not defined Checks that all subclassResponsibility
methods are defined in all leaf classes.
Uses A | B = C instead of A | (B = C) Checks precedence ordering of & and | with
equality operators. Since | and & have the
same precedence as =, there are common
mistakes where parenthesis are missing
around equality operators.
Uses True/False instead of true/false Checks for uses of the classes True and False
instead of the objects true and false.
Variable used, but not defined anywhere Similar to the "References an undeclared
variable" check, but looks for variables that
are not defined in the class or in the
Undeclared dictionary.

566 VisualWorks
Possible Bugs

Possible Bugs
Rule Description
Assignment inside unwind blocks Checks assignment to a variable that is the
should be outside first statement inside a value block that is
also used in an unwind block.
Defines = but not hash Checks that all classes that define = also
define hash. If hash is not defined then the
instances of the class might not be able to
be used in sets since elements that are
equal must have the same hash.
Has class instance variable but no Checks that all classes that have class
initialize method instance variables also have an initialize
method. This ensures that all class instance
variables are initialized properly when the
class is filed-in to a new image.
Instance variable overridden by Checks for methods with block temporary
temporary variable variables that override an instance variable.
This causes problems when using the
instance variable inside the method.
Missing super sends Checks that some methods contain a send
to super. For example, the postCopy method
should always contain super postCopy.
Modifies collection while iterating Checks for sends to remove: from inside of
collection iteration methods such as do:.
These can cause the do: method to break
since it will iterate beyond the end of the
collection. The common fix for this problem
is to copy the collection before iterating over
it.
More addDependent: messages then Check that the number of addDependent:
removeDependent: message sends in a class is less than or
equal to the number of removeDependent:
messages. If there are more addDependent:
sends, it is possible that some dependents
are not being released, which may lead to
memory leaks.
Possible missing "; yourself" Checks for missing "; yourself" cascaded
message send for cascaded messages.
This helps locate common coding mistakes
such as:
anArray := (Array new: 2) at: 1 put: 1; at: 2 put: 2.
I.e., anArray would be assigned the value 2
rather than the array object.
Possible three element point; Checks arithmetic statements for possible
E.g., x @ y + q @ r three element points (i.e., a point that has
another point in its x or y part).

Application Developer’s Guide 567


Appendix D - Code Critic Rules

Rule Description
References an abstract class Checks for references to classes that have
subclassResponsibility methods. Such
references might create instances of the
abstract class or might be used as the
argument to an isKindOf: message (the latter
is considered bad style).
Returns a boolean and non boolean Checks for methods that return a boolean
value (true or false) and return some other
value such as (nil or self).
If the method is supposed to return a
boolean, this suggests there is a path
through the method that might return a non-
boolean.
If the method doesn't need to return a
boolean, you should probably rewrite it to
return some non-boolean value since other
programmers reading your method might
assume that it returns a boolean.
Returns value of ifTrue:/ifFalse: without Check for methods returning the value of an
ifFalse:/ifTrue: block ifTrue: or ifFalse: message. These statements
return nil when the block is not executed. For
example, the following code will return nil
when aBoolean is false:
methodName
^aBoolean ifTrue: [0]
If the code should return nil when aBoolean is
false, then it should probably be written as:
methodName
^aBoolean
ifTrue: [0]
ifFalse: [nil]
Sends different super message Checks for methods whose source sends a
different super message. A common example
of this is in creation methods. You might
define a method such as:
createInstance
^super new initialize
If the new method is not defined in the class,
you should probably rewrite this to use self
instead. Also, if the new method is defined,
you might question why you need to use the
superclass' new method instead of new
method defined in the class.
Subclass of class that has instance Checks that all subclasses of Collection
variable but doesn't define copyEmpty classes that add an instance variable also
redefine the copyEmpty method. This method
is used when growing the collection. It
copies over the necessary instance variables
to the new, larger collection.

568 VisualWorks
Unnecessary Code

Rule Description
Temporaries read before written Checks that all temporaries are assigned
before they are used. This can help find
possible paths through the code where a
variable might still be unassigned when it is
used.
Uses the result of an add: message Check for possible uses of the result
returned by the add: or addAll: messages.
These messages return their arguments not
the receiver. As a result, many uses of the
results are wrong.

Unnecessary Code
Rule Description
Block immediately evaluated Check for blocks that are immediately
evaluated. Since the block is immediately
evaluated, there is no need for the
statements to be in a block.
Check for same statements at end of Checks for ifTrue:ifFalse: blocks that have the
ifTrue:ifFalse: blocks same code at the beginning or end. Instead
of having the same code in two places, it
should be moved outside the blocks.
Class not referenced Check if a class is referenced either directly
or indirectly by a symbol. If a class is not
referenced, it can be removed.
Instance variables not read and written Checks that all instance variables are both
read and written. This check does not work
for data model classes since they use the
instVarAt:put: messages to set instance
variables.
Method just sends super message Check for methods that forward the message
to its superclass. These methods can be
removed.
Methods equivalently defined in Check for methods that are equivalent to
superclass their superclass methods. Such methods
don't add anything to the computation and
can be removed, since the superclass's
method will work just fine.
Methods implemented but not sent Check for methods that are never sent.
If a method is not sent, it can be removed.

Application Developer’s Guide 569


Appendix D - Code Critic Rules

Rule Description
Unnecessary = true Check for an =, ==, ~=, or ~~ message being
sent to true/false or with true/false as the
argument. Many times these can be
eliminated since their receivers are already
booleans.
For example, anObject isFoo == false could be
replaced with anObject isFoo not if isFoo
always returns a boolean. Sometimes
variables might refer to true, false, and
something else, but this is considered bad
style since the variable has multiple types.
Variable referenced in only one method Checks for instance variables that might
and always assigned first better be defined as temporary variables. If
an instance variable is only used in one
method and it is always assigned before it is
used, then that method could define that
variable as a temporary variable of the
method instead (assuming that the method
is not recursive).
Variables not referenced Check for variables not referenced.
If a variable isn’t used in a class, it should be
deleted.

570 VisualWorks
Intention Revealing

Intention Revealing
Rule Description
Assignment to same variable at the end Checks for ifTrue:ifFalse: blocks that assign
of ifTrue:ifFalse: blocks the same variable at the end of the block.
Instead of having the assignment being in
both blocks, we can instead assign the
variable the result of the ifTrue:ifFalse:
message. For example, this code:
aBoolean
ifTrue: [foo := true]
ifFalse: [foo := anotherBoolean]
could be rewritten as:
foo := aBoolean
ifTrue: [true]
ifFalse: [anotherBoolean]
Once we have simplified the expression by
pulling the assignment out of the blocks,
then we could see that the code is equivalent
to:
foo := aBoolean or: [anotherBoolean]
Guarding clauses Checks for ifTrue: or ifFalse: conditions at the
end of methods with two or more statements
inside their blocks. Such methods might be
more comprehensible if they returned self
instead. For example, the following code:
someMethod
a isNil
ifFalse:
[self doSomething.
self doAnotherThing]
might be better represented as:
someMethod
a isNil ifTrue: [^self].
self doSomething.
self doAnotherThing
In the first method, a not being nil looks like
the exception, but most likely a being nil is
the exception which is more obvious in the
second method.
ifTrue:/ifFalse: returns instead of Checks for common ifTrue: returns that could
and:/or:'s be simplified. For example,
foo
aCondition ifTrue: [^false].
^true
can be simplified as:
foo
^aCondition not

Application Developer’s Guide 571


Appendix D - Code Critic Rules

Rule Description
Method defined in all subclasses, but Checks classes for methods that are defined
not in superclass in all subclasses, but not defined in self. Such
methods should most likely be defined as
subclassResponsibility methods to help
document the class. Furthermore, this check
helps to find similar code that might be
occurring in all the subclasses that should be
pulled up into the superclass.
Sends add:/remove: to external Checks for methods that appear to be
collection modifying a collection that is owned by
another object. Such modifications can
cause problems especially if other variables
are modified when the collection is modified.
For example, CompositePart must set the
containers of all its parts when adding a new
component.
Unnecessary size check Check for code that checks that a collection
is non-empty before sending it an iteration
message (e.g., do:, collect:, etc.). Since the
collection iteration messages work for empty
collections, the method does not need to be
cluttered with the extra size check.
Uses "size = 0" or "= nil" instead of Checks for methods using equality tests
"isEmpty" or "isNil" instead of the message sends. Since the
code aCollection size = 0 works for all objects,
it is more difficult for someone reading such
code to determine that aCollection is a
collection. Whereas, in the expression
aCollection isEmpty, it is clear that aCollection
must be a collection since isEmpty is only
defined for collections.
Uses at:ifAbsent: instead of Checks for uses of at:ifAbsent: in place of the
at:ifAbsentPut: shorter at:ifAbsentPut: message. For example:
aDictionary
at: aKey
ifAbsent: [aDictionary at: aKey put: anObject]
should be rewritten as:
aDictionary
at: aKey ifAbsentPut: [anObject].
You may also use one of the Code Critic’s
transformation rules to convert these
methods.
Uses detect:ifNone: instead of contains: Checks for the common code fragment:
(aCollection detect: [:each | 'some condition']
ifNone: [nil]) ~= nil
which can be simplified and clarified as:
aCollection contains: [:each | 'some condition'].

572 VisualWorks
Intention Revealing

Rule Description
Uses do: instead of collect: or select:'s Checks for methods using do: instead of
collect: or select:. The collect: and select:
variants are preferred for clearly expressing
intention.
Uses do: instead of contains: or detect: Checks for methods using do: instead of
using contains: or detect:.
Uses ifTrue:/ifFalse: instead of min: or Checks for uses of ifTrue:/ifFalse: when it
max: could use min: or max:. For example:
a < b ifTrue: [a] ifFalse: [b]
may be rewritten as:
a min: b
Uses to:do: instead of do:, with:do:, or Checks for methods using to:do: when a do:,
timesRepeat: with:do: or timesRepeat: should be used.

Uses whileTrue: instead of to:do: Checks for methods using whileTrue: when
the shorter to:do: would work. For example,
this common C-like code:
i := 1.
[i <= size]
whileTrue:
["self do something with i".
i := i + 1]
can be written as:
1 to: size do: [:i | "self do something with i"]

Application Developer’s Guide 573


Appendix D - Code Critic Rules

Miscellaneous
Rule Description
Doesn't use the result of a yourself Check for methods sending the yourself
message message unnecessarily. For example, the
following statement doesn't need yourself,
since it is not used:
aCollection addAll: #(a b c); yourself
If this statement were assigned to a variable,
then the cascade with yourself would be
needed to get the value of aCollection.
Inspect instances of "A + B * C" might Checks for methods that might have
be "A + (B * C)" precedence problems. Developers who are
used to other languages often make
mistakes when writing Smalltalk code since
in Smalltalk all binary operations are
performed left-to-right.
Instance variables defined in all Checks classes for instance variables that
subclasses are defined in all subclasses. It is often
better style to move the instance variable up
into the class so that all the subclasses don’t
have to define it.
Long methods Checks for methods that have more 10
statements (this check counts statements,
not lines.)
Methods with full blocks Checks for methods that contain full blocks
or create a context with the thisContext
keyword. These methods are a place where
inefficiencies can creep in. For example, a
common reason why a full block is created is
because a block assigns a temporary
variable that is not defined inside the block. If
the temporary variable is only used inside
the block, then the definition of the
temporary should be moved inside the block.
The "move to inner scope" refactoring can be
used to correct this.
Non-blocks in ifTrue:/ifFalse: messages Checks for methods that don’t use blocks in
the ifTrue:ifFalse: messages. Developers new
to Smalltalk may write code such as:
aBoolean ifTrue: (self doSomething).
instead of the correct version:
aBoolean ifTrue: [self doSomething].
Even if such expressions are correct, they
cannot be optimized by the compiler.

574 VisualWorks
Miscellaneous

Rule Description
Redundant class name in selector Checks for the class name in a selector, e.g.:
openHierarchyBrowserFrom:, which is a
redundant name for HierarchyBrowser.
Refers to class name instead of "self Checks for classes that have their class
class" name directly in the source instead of self
class. Using self class allows you to create
subclasses without needing to redefine the
method.
Sends "questionable" message Check for methods that send messages
which perform low level actions. For
example, using become: throughout an
application should be avoided. Also,
messages such as isKindOf: suggest a lack of
polymorphism.
String concatenation instead of streams Check for methods that use string
concatenation inside an iteration message.
Since string concatenation is O(n^2), it is
better to use streaming since it is O(n) -
assuming that n is large enough.
Unnecessary assignment or return in Checks valueNowOrOnUnwindDo:,
block valueOnUnwindDo:, ensure:, and showWhile:
blocks for assignments or returns that are
the last statement in the block. These
assignments or returns should be moved
outside the block since they return the value
of the block.
For example, the code:
someMethod
| bos |
bos := BinaryObjectStorage
onOld: 'test' asFilename readStream.
[^bos next]
valueNowOrOnUnwindDo: [bos close]
can be rewritten as:
someMethod
| bos |
bos := BinaryObjectStorage
onOld: 'test' asFilename readStream.
^[bos next]
valueNowOrOnUnwindDo: [bos close]
Having the assignment or return inside the
block runs much slower than copying or
optimizing blocks.

Application Developer’s Guide 575


Appendix D - Code Critic Rules

Rule Description
Utility method Check for methods that have one or more
arguments and do not refer to self or an
instance variable. These methods might be
better defined in some other class or as
class methods.
Variable is only assigned a single literal If a variable is only assigned a single literal
value value then that variable is either nil or that
literal value. If the variable is always
initialized with that literal value, then each
variable reference could be replaced with a
message send to get the value. If the
variable can also be nil, then it might be
better to replace that variable with another
that stores true or false, depending on
whether the old variable had been assigned.

576 VisualWorks
Code Transformations

Code Transformations
The following predefined code transformations are provided by the Code
Critic (the patterns are defined in class ParseTreeTransformationRule):

Transformation Description
"a >= b and: [a <= c]" -> "a between: b Transform:
and: c" a >= b and: [a <= c]
to:
a between: b and: c
= nil -> isNil AND ~= nil -> notNil Transform = nil to isNil
and transform ~= nil to notNil.
at:ifAbsent: -> at:ifAbsentPut: Transform:
aDictionary
at: aKey
ifAbsent: [aDictionary at: aKey put: anObject]
to:
aDictionary
at: aKey ifAbsentPut: [anObject].
detect:ifNone: -> contains: Transform:
(foo detect: [:a| a test] ifNone: [nil]) isNil
to:
(foo anySatisfy: [:a | a test])
Eliminate guarding clauses Transform methods ending with an ifTrue: or
ifFalse: that have multiple statements inside
the block, replacing them with ifFalse: [^self].
followed by straight-line code that was inside
the block. For example:
someMethod
a isNil
ifFalse:
[self doSomething.
self doAnotherThing]
is transformed to:
someMethod
a isNil ifTrue: [^self].
self doSomething.
self doAnotherThing
Eliminate unnecessary not Transform:
aTest not ifTrue:
to:
aTest ifFalse:

Application Developer’s Guide 577


Appendix D - Code Critic Rules

Transformation Description
Move assignment out of showWhile: Transform:
blocks Cursor busy
showWhile: [x := self someLongCalc].
to:
x := Cursor busy
showWhile: [self someLongCalc]
This eliminates a full block.
Move assignment out of ensure: blocks Transform:
[x := self aCalc]
ensure: [self close]
to:
x := [self aCalc]
ensure: [self close]
This eliminates a full block.
Includes the ifCurtailed: variation.
Move variable assignment outside of Transform:
single statement ifTrue:ifFalse: blocks aTest ifTrue: [x:=1] ifFalse: [x:=2]
to:
x := (aTest ifTrue: [1] ifFalse: [2])
Rewrite ifTrue:ifFalse: using min:/max: Transform:
a < b ifTrue: [a] ifFalse: [b]
to:
a max: b
(includes many variations)
Rewrite super messages to self Transform:
messages when both refer to same Singleton class>>default
method ^super new initialize
to:
Singleton class>>default
^self new initialize
if Singleton class does not define new.
Use cascaded nextPutAll: instead of #, Transform:
in #nextPutAll: aStream nextPutAll: 'any ', 'time ', 'now '.
to:
aStrean nextPutAll: 'any ';
nextPutAll: 'time ';
nextPutAll: 'now '.

578 VisualWorks
Index
button 42 Application settings, defined 196
button 42 arc function 381, 395
Symbols argument variable 80
arithmetic operations 342
ArithmeticError class 259
$ 431 array
* (multiplication) 342 defined 76
** (power function) 342 expanding 362
- (minus) removing an element 365
collection subtraction 369 size 359, 362
numeric subtraction 342 Array class 357
+ (plus) assigning variable values 90
numeric addition 342 association
.st files 141 in a dictionary 358
/ (division) 342 removing from dictionary 365
// (integer division) 342 Association class 358
<Operate> button 27
<Select> button 27 B
<Window> button 27 Bag class 357
\ (division remainder) 342 baseline in text 464
^ 110 behavior, defined 65
’ (single quote) 433 Bezier class 399
A Bezier curve, defined 399
binary file
A 271 See also BOSS
abbreviating a string 444 BinaryObjectStorage 484
aboutToQuit 296 bitmap, defined 404
aboutToQuit event 296 block expression 98
aboutToSnapshot 296 BlockClosure class 112
absolute value function 342 bold text emphasis 454
abstract class 71 boolean
access date of a file 473 values 77
activate 208 Boolean class 112
adding BOSS
class definition 50 retrieving contents of a file 486
elements to a collection 360 retrieving specific objects 488
method definition 50 searching for an Object 487
aligning sequential access 487
text 448 skipping the initial scan 486
appending storing a class 489
a string 436 storing objects 484
text to a file 476 storing objects in a file 484
application stream positioning 485
framework 185 using custom storage formats 492
model 185 versioning 491

Application Developer’s Guide 579


Index

BOSS vs. file out 490 testing 39


branching 112–114 collection
button 42 adding elements 360
buttons capacity 370
mouse 27 choosing a class 356
ByteArray classes 356
class 357 combining 369
C concatenating 369
converting types 375
caching a graphic image 408 copying elements 368
cap style of a line 402 counting occurrences 371
capitalization conventions 78 creating 359
cascade 96 inserting an element 361
case statement 114 looping 117–118, 376
Change List removing elements 363
See also changes replacing elements 366
browsing 169 size 359, 370
condensing 180 sorting 374
change list 173 subtracting a subset 369
Change Set testing for emptiness 370
See also changes Collection subclasses 356
browsing changes 178 color 416–430
clearing 172 See also palette, pattern
sharing code between images 142 applying 420, 421
updating 171 creating 417
changes dithering 426, 427
See also Change List, Change Set geometric object 420
browsing 178 map 424
managing 172–180 predefined 417
character rendering policies 426
See also string rendering policy 426
counting in text 435, 451 color text emphasis 454
line ends 444 coloring
operations 432 a graphic image 408
testing 432 text 465
Character class 431 ColorValue class 416
character literal 76 combining collections 369
CharacterAttributes class 461 command line options 298
circle 396 comparing
class dates 349
abstract 71 files or directories 478
creating 50 numbers 342
defined 67 texts 452
hierarchy 69 composed character 432
in a BOSS file 489 ComposedText 480
inheritance 69–70 See also text
method 67 composite object 64
variable 67 compressing a string 444
class button 49 concatenate strings 436
cleanup blocks 267 conditional looping 115
code conditional selection 112
formatting 99 control structure 112–118

580 VisualWorks
Index

methods 118 dimension


conventions of a display surface 382
naming 66, 67 of a rectangle 394
typographic 26 of an image 412
converting directory
collection types 375 characteristics 471
numeric types 344 comparing 478
copying contents 474
a file or directory 477 copying or moving 477
elements in a collection 368 creating 470
CR (line end) 444 dates 473
crash recovery 178 default 472
creating deleting 476
filename 469 distinguishing from file 473
point 380 parent 472
process 275 display surface types 382
signal instance 270 displaying
Cursor class 384 a graphic image 407
curves 399 points 396
CustomViewExample 387 text 446
D DisplaySurface class 220, 382
dithered color 427
date dithering color 426
comparing 349 diving inspector 55
day information 349 Do it 40
formatting 350 Document class 522, 524
Date class 347 document object model (DOM), See XML
day information 349 domain model 185
deactivate 208 Double class 340
Debug it 40
Debugger 242–244 E
See also debugging techniques earlySystemInstallation 296
debugging editing
external libraries 256 source code 50
virtual machine code 256 electronic mail 29
debugging techniques elements
inspecting and changing variables 246 adding to collection 360
interrupting a program 253 ellipse
reading the execution stack 244 graphic 396
tracing message flow ??–245, 247–?? EllipticalArc class 397
decompiled code 50 Emergency Evaluator 38
default Emergency exit 38
paint policy 427 enumerating, See looping
palette 426 error
DefaultDirectoryString shared variable 472 compilation 110
Delay class 280 Error class 259
deleting a file or directory 476 as Exception subclass 259
diacritical mark 432, 558 nonresumable exceptions 267
dictionary error notifier, See notifier
adding elements 361 ErrorDiffusion class 427
removing an association 365 Event class 221
Dictionary class 358 eventHandlers instance variable 222

Application Developer’s Guide 581


Index

EventHandlers shared variable 219, 221 See also BOSS


EventManager class 222 characteristics 471
events comparing 478
aboutToQuit 296 contents 474
defining 220 copying or moving 477
described 214 creating 470
exceptional 259 dates 473
register handler 216 deleting 476
removing handlers 219 distinguishing from directory 473
returnFromSnapshot 296 parts of name 473
triggering 215 printing 480
examples 31 storing text 475
Examples Browser 31 file name
exception create 468
adding handlers 263 file out
cleaning up 269 vs. a BOSS file 490
defined 257 Filename class 468
defining handlers 260 file-out file 141
environment 263–265 FileOut30 141
executing handler blocks 267 filtering
exiting handlers 265 change list 181
flow of control 272–273 finalization 282
get description 259 finding a method 69
handling 260–270 finding, See searching
nonresumable 267 finishedSnapshot 296
raising 262 FixedPoint class 341
resumable 267 fixed-point number
setting parameters 272 definition 341
signaling 262 Float class 340
terminating handler blocks 266 FloatingPoint
translating 269 comparing 343
Exception class 270, 271 flopping an image 413
exception handlers font
active 263 family 458
described 260 in a text 454
exiting explicity 265 name 458
Exception subclasses 268 size 455
exception FontDescription class 457, 460
See also error fonts 26
ExceptionSet class 262 for loop, See number looping
execution stack 244 formatting
executor, defined 285 a date 350
exiting the system formatting conventions 99
emergency 38 Fraction class 341
expanding functions
graphic images 412 see Methods 109
expression 93 G
F geometrics
false 77 arcs, circles, and wedges 381, 395
file color 420
binary lines and polygons 381, 390

582 VisualWorks
Index

rendering color 429 dive 55


splines and Bezier curves 399 pop 55
graphic image variable 243
as graphic object 382, 403 instance
caching 408 defined 67
capturing 406 instance button 49
coloring 408 instance method 67
converting to display surface 408 instance variable 81
creating 405 Integer class 340
displaying 407 Interval class 357
expanding and shrinking 412 italic emphasis 454
flopping 413 iterating, See looping
masking 411 iterative operations 115
packed rows 407 iterative operations, See looping
palette 404 J
performance 424
read from file 406 join style of a line 403
rotating 414 L
save as resource 406 large emphasis 454, 455
graphics LargeInteger class 340
coordinate system 379 LargeNegativeInteger class 340
display surfaces 382 LargePositiveInteger class 340
image 382, 403 length of a string 435
integrating into application 387 LF (line end) 444
graphics context 446 line
GraphicsAttributes class 389 cap style 402
GraphicsAttributesWrapper class 389 displaying 381, 390
grid in lines of text 464 join style 403
H thickness 401
HandleRegistry class 285 line end characters 444
hardcopy, See printing line spacing in text 464
hierarchy of objects 64 LineSegment class 391
LinkedList class 358
I literal
IdentityDictionary class 358 array 76
IdentitySet class 356 character 76
if statements 112 number 74
image string 76
See also graphic image symbol 76
Smalltalk 32 looping 112–118
Image class 382, 403 through a collection 376
immediate object 105 types of 115
indenting text 448 loose coupling 214
indexed instance variable 81 M
informational message, displaying 39
inheritance 69–70 macro expansion 442
inherited method, overriding 70 mail
Inspect it 40 electronic 29
inspector MappedPalette 425
debugger 246 mask 411
defined 54 Mask class 383
mask value 424

Application Developer’s Guide 583


Index

memory leaks 107 O


message 66 object
cascade 96 behavior 65
category 66 composite 64
expression 93 examining variable values 54
in sequence 96 hierarchy 64
keyword 95 state, defined 65
selector 66 Object class 71
types 93 object engine
unary 93 command line switches 562
MessageNotUnderstood class 259 object engine, See virtual machine
method 65–70 object file
class method 67 See also BOSS
creating 50 ObjectMemory 296, 298
defined 63 ObjectMemory class 208
grouping 66 object-oriented programming 61–73
instance method 67 Options, application-specific 196
overriding 70 OrderedCollection class 357
method lookup 67, 69 OrderedDither 427
model overrides 181
application 186 packages 184
domain 185 parcels 184
modification date of a file 473
mouse buttons 27 P
<Operate> button 27 packages
<Select> button 27 creating in StORE 143
<Window> button 27 overrides 184
moving a file or directory 477 packed row, in an image 407
N pad source 157
paint
name spaces 73, 89 See also color
named change sets 168 applying 420, 421
named instance variable 81 color 416
naming conventions 66, 67, 78 coverage 417
nil 77 paint policy 426
nonresumable exceptions 267–268 PaintPolicy class 430
notational conventions 26 palette
Notification, class 8-bit color 424
as Exception subclass 259 color 423
exceptional events 259 conversion 424
notifier coverage 423
debugging window 242 creating 423
execution stack 244 default 426
number defined 423
See also fixed-point 341 effect on performance 424
comparing 342 fixed 423
creating 341 mapped 423
number literal 74 Palette class 423
number looping 116 parcels
numeric operations 342 overrides 184
prerequisites 160
pattern

584 VisualWorks
Index

See also tile range


applying 421 iterating on numbers 116
tile phase 422 RasterOp class 415
Pattern class 416 read stream 481
pause 208 reciprocal function 342
persistence recover, from crash 178
See also BOSS rectangle
phase of a tiled pattern 422 creating 394
PI class 531 dimensions 394
pixel 404 messages 394
Pixmap class 383 scaling 395
Point class registry
arithmetic functions supported 381 handles 285
creating an instance 380 rendering color 426
specifying polar coordinates 380 replacing
polygon, displaying 381, 390 elements in a collection 366
Polyline 392 part of a text 451
power function 342 resumable exceptions 267
Pragma class 96 resume 208
pragmas 95 retained medium 383
C 96 return
pragmas, in settings definitions 197, 200 from a method 110
Preferences, application 196 returnFromSnapshot 296, 298
Print it 40 returnFromSnapshot event 296
printing RGB color 418
a text file 480 rotating a graphic image 414
text 450 RunArray class 357
priority level 277–278 Runtime Packager 288
proceedability attribute 271 RuntimeErrorNotifierFramework class 328
process ??–270 RuntimeManager 297
coordinating ??–277, 278–?? S
creating 275
fork 276 sansSerif font 459
postponing 280 save source code 50
running multiple 276 SAXDriver class 524
scheduling 276 SAXExternalGeneralEntities class 540
setting the priority level 277–278 SAXExternalParameterEntities class 540
sharing data 280 SAXNamespace class 540
states of 277 SAXNamespacePrefixes class 540
terminating 277 SAXValidate class 540
Processor object 276 scavengeOccurred 296
project ScheduledWindow class 383
managing 172–180 screen
summarizing changes 169 capture 406
protected blocks of code 268 default palette 426
searching
Q a string 438
quotation mark in a text 450
creating a string 433 selector 66
R self 91
Semaphore class 278
Random class 347 sequential access

Application Developer’s Guide 585


Index

in a BOSS file 487 StORE


serif emphasis 454, 459 packages, creating 143
Set class 356 stream
Settings framework 196 closing 476
Settings Manager 59, 196 creating 476
Settings, defined 196 strikeout emphasis 454
shared variables button 49 string
SharedQueue class 280 See also character
shift value 424 abbreviating 444
shortening a string 444 concatenation 436
shrinking graphic images 412 converting to text 445
shutdown 295, 297 getting a substring 440
signal length and width 435
choosing 270 removing a substring 441
creating 270 replacing a substring 441
global 270 searching 438
nested 273–274 String class 431, 433
Signal class 270 string literal 76
signaling exceptions 262 string substitutions 442
Simple API for XML (SAX), See XML style, See text style, font
size substitution parameters 442
of a string 435 substring operations 440
of font 455 Subsystem class 208
of text 435, 451 super 91
Sketch 387 superclass 71
SketchView1 387 support, technical
small emphasis 454, 455 electronic mail 29
SmallInteger class 340 World Wide Web 29
Smalltalk at symbol 76
105 symbols used in documentation 26
snap-shot 36 syntax
SortedCollection class 358 fixed-point numbers 75
sorting a collection 374 floating-point numbers 75
sound 563 integers 74
source code nondecimal numbers 75
editing 50 numbers 74
missing 50 scientific notation 75
saving 50 System Browser 45
spacing system constant 67
lines in text 464 system events 295
spawn command 110 System font 465
special characters 554–559 system variable 469
special symbols 26 systemDefault text style 456
splash screen 34, 563 SystemEventInterest 296
spline 399 SystemEventInterest class 208
Spline class 399 T
square root function 342
stack 244 tab stops in text 448
startup 295 technical support
startup sound 563 electonic mail 29
state of an object 65 World Wide Web 29
stencil, See mask temporary variable 79

586 VisualWorks
Index

text V
adding emphasis 454 variable
aligning 448 argument 80
boldfacing 454 assignment 90
changing case 454 defined 65
color 465 instance 81
comparing 452 system 469
creating 445 temporary 79
displaying 446 variables
font family 458 workspace 40
font size 455 VariableSizeTextAttributes class 461
indents and tabs 448 version
line spacing 464 of a BOSS file 491
printing 450 version control 172–180
replacing a subtext 451 virtual machine 32
searching 450 command line switches 562
size 435, 451 debugging and deployment 560
storing in file 475 VisualComponent class 220
string 445
TextAttributes 446, 461, 465, 466 W
See also text style Warning class 259, 268
thickness of a line 401 weak array 281
tile finalization 282
pattern 416 WeakArray class 281
phase 422 WeakDictionary class 285
time wedge 396
creating 351 while loop 115
zones 353 width
time stamp 352 of a string 435
Timestamp class 352 Window class 382
tools WordArray class 357
Debugger 242 working directory 472
Settings Manager 59 Workspace 39
Workspace 39 workspace variables 40
transcript 39 World Wide Web 29
translating exceptions 269 write stream 480
translation protocol 386 WriteStream 436
true 77 X
truncating a string 444
typographic conventions 26 XML
accessing elements 524
U add attribute 530, 543
unary message 93 add element 529
underline emphasis 454 add text 531
uninstalled code 161 attributes 527
unloaded code 161 build document 528
unwind protection 269 children 526
user interrupt 253 document fragment 541
User settings, application-specific 196 DOM 521
User settings, defined 196 DTD 522
error handling 548
parser drivers 523

Application Developer’s Guide 587


Index

parsing 522
processing instruction 531
root 525
SAX 537
SAX event handler 537
SAX2 539
Schema 523
select elements 526
stylesheet 543
validating 523
XML support 521
XMLParser class 522, 524
XSL, See XML
Z
ZeroDivide class 259

588 VisualWorks
Method Index
Symbols B
= 112 baseline: 457, 464
== 112 beCurrentDirectory 472
A binary 93
binaryReaderBlockForVersion:format: 491
abs 342 boundingBox:startAngle:sweepAngle: 397
actionForEvent: 219, 222
activate 212 C
add: 359, 360 canBeWritten 482
add:before: 361 canTriggerEvent: 223
add:beforeIndex: 361 centered 448
addAll: 362 changed:with: 388
addAll:beforeIndex: 362 characterAttributes: 457, 464
addAllFirst: 362 characters: 538
addDays: 349 children 524
addFirst: 361 close 476
addTime: 352 collect: 377
after: 373 colorPalette 426
allBold 454 colors 425
ambivalentEventChecking 220 completeContentsOfArea: 411
anyElementNamed: 525 composeDiacritical: 432
anyElementsNamed: 525 compositionWidth: 447
appendStream 476 constructEventsTriggered 221
asComposedText 445, 480 contentHandler: 524
asDays 349 contents 486
asDouble 344 contentsOfEntireFile 474, 479
asFilename 469 contractTo: 444
asFiller 392 convertForGraphicsDevice: 407
asFixedPoint: 344 convertForGraphicsDevice:renderedBy: 428
asLowercase 434 convertToPalette: 425
asPattern 421 convertToPalette:renderedBy: 429
asRational 344 copy:from:in:rule: 415
asRetainedMedium 408, 411 copyArea:from:sourceOffset:destinationOffs
at: 371, 372 et: 412
at:ifAbsent: 372 copyEmpty 414
at:put 366 copyFrom:to: 368, 440
at:put: 361 copyReplaceAll:with: 442
atAllPut: 366 copyReplaceFrom:to:with: 441
atEnd 487 copyTo: 477
atFeature: 539 copyUpTo: 441
atPoint: 409 copyWith: 362
atPoint:put: 409 copyWithout: 365
atProperty: 539 coveragePalette 426
attributes 525

Application Developer’s Guide 589


Method Index

D forgetInterval: 488
dates 473 fork 276
dayOfMonth 349 from:to: 391
daysInMonth 349 fromFile: 406
daysInYear 349 fromSeconds: 352
defaultAction 265 fromUser 406
defaultDirectory 472 G
degreesToRadians 344 gridForFont:withLead: 459, 464
delete 477
detect:ifNone: 373 H
directoryContents 474, 479 halt 253
displayArcBoundedBy:startAngle:sweepAngl handlers: 523
e: 397 hasActionForEvent: 223
displayDotOfDiameter:at: 396 hue:saturation:brightness: 419
displayLineFrom:to: 391
displayOn: 446 I
displayOn:at: 407 ignorableWhitespace: 538
displayPolygon: 392 image 406
displayPolyline: 392 imageFromFile:toClass:selector: 406
displayWedgeBoundedBy:startAngle:sweep includesAssociation: 371
Angle: 397 includesKey: 371
do: 376 indexOf: 371, 439
document 524 indexOfSubCollection:startingAt: 372
dropFinalVowels 444 isDirectory 473
dtdHandler: 524 isEmpty 370
dumpFailedMsg 328 isInteger 343
isNil 77
E isResumable 268
elementNamed: 524 isZero 343
elementsNamed: 525
emergencyAbortText 328 J
emphasizeAllWith: 454 justified 448
emphasizeFrom:to:with: 454, 455 K
endDocument 537
keyAtValue: 372
endElement: 538 keysAndValuesDo: 377
endPrefixMapping: 538
ensure: 485 keysDo: 377
entityResolver: 524 L
errorHandler: 524 lastIndexOf: 372
errorOccurredMsg 328 leftFlush 448
even 343 lineGrid: 457, 464
eventsHandled 223 lineWidth: 401
exists 471 ln 345
exp 345 log 345
F M
family: 459 magnifiedBy: 412
fileSize 471 main 209
findDefaultDirectory 472 makeDirectory 471
findString:ignoreCase:useWildcards: 439 makeUnwritable 482
findString:startingAt:ifAbsent: 439 makeWritable 482
firstIndent: 448 model: 389

590 VisualWorks
Method Index

monthName 349 readFromString: 347, 351


moveTo: 477 readStream 486
myEventTable 222 receiver:selector:arguments: 492
myEventTable: 222 red:green:blue: 418
N reflectedInX 413
reflectedInY 413
name 105 reject: 373
name: 460 release 219
negative 344 releaseEventTable 219
new:withAll: 359 remove: 363
newDay:monthNumber:year: 348 remove:ifAbsent: 363
newWithDefaultAttributes 457 removeAction:forEvent: (and variants) 220
nextIndexOf:from:to: 371 removeActionsWithReceiver:forEvent: 219
nextPut: 485 removeAll: 363
nextPutAll: 436, 476, 485 removeAllSuchThat: 364
nextPutClasses: 490 removeFirst 364
notify:context: 328 removeFirst: 364
now 351 removeFrom:to: 364
O removeKey: 365
occurrencesOf: 371 removeKey:ifAbsent: 365
on:do: 260, 263, 266, 267 removeLast 364
onNew: 484 removeLast: 364
onOld: 485, 486 renameTo: 478
onOldNoScan: 486 replaceAll:with: 367
replaceFrom:to:with: 451
P replaceFrom:to:with:startingAt: 367
paint: 420, 421 representBinaryOn: 492
paintPolicy: 430 resetViews 466
paintRenderer: 430 resignalAs: 265, 269
palette 425 respondsToArithmetic 343
parent 524 restIndent: 448
parse: 522 resumeAction 209
pass 265, 267 retry 265, 266
pauseAction 209 retryUsing: 265, 266
pi 346 return 265, 266
pixelSize: 463 reverse 375
position: 488 root 524
positive 343 rotateByQuadrants:to: 414
preSave: 162 rotatedByQuadrants: 414
printFormat: 350 S
printTextFile 480
processingInstruction: 538 sameAs: 437
sameCharacters: 438
Q select: 373
quitBlock selectNodes: 525
297 setDefaultQuery: 457
setDefaultTo: 466
R setDispatchTableForPlatform 38
radiansToDegrees 345 setToEnd 485
raiseSignal 258 setUp 209
raiseSignal: 263 shrunkenBy: 413
readAppendStream 485 signal 258, 262, 278

Application Developer’s Guide 591


Method Index

signalWith: 263
size 370, 435
skippedEntity: 538
sort 375
sortWith: 375
sourceMode: 490
spellAgainst: 438
sqrt 342
startDocument 537
startElement: 538
startPrefixMapping: 538
strictlyPositive 344
styleNamed: 446, 456
styleNamed:put: 465
subtractDate: 349
T
tearDown 209
textStyle: 449, 456, 457, 464
thisContext 91
tilePhase: 422
today 347
triggerEvent: (and variants) 215
U
update:with: 388
userInterruptMsg 328
useTabs: 449
V
validate: 522
valueAtPoint: 409
valueAtPoint:put: 409
vertices: 392
W
wait 278
weekday 349
when:send:to: 216
widget:when:do: (and variants) 223
withAll: 360
withColors: 425
withCRs 445
withText:style: 446
wordWrap: 447, 453
writeStream 476
Y
yourself 359

592 VisualWorks
Reader Comment Sheet
Name:

Job title/function:

Company name:

Address:

Telephone number: ( ) - Date: / /

How often do you use this product? # Daily # Weekly # Monthly # Less
How long have you been using this product? # Months # Years

FAX
Can you find the information you need?

Please comment.
# Yes # No

Is the information easy to understand? # Yes # No


Please comment.

IT!
Is the information adequate to perform your task? # Yes # No
Please comment.

General comment:

WE STRIVE FOR QUALITY


To respond, please fax to Larry Fasse at (513) 612-2000.

P46-0101-10

You might also like