Professional Documents
Culture Documents
Programmers Reference
Version 5.5
Note
Before using this document, read the general information under Notices on page ix.
May 2001
This edition applies to Version 5.5 of the VisualAge Smalltalk products, and to all subsequent releases and
modifications until otherwise indicated in new editions. Make sure you are using the correct edition for the level of
the product. The term VisualAge, as used in this publication, refers to the VisualAge Smalltalk product set.
Portions of this book describe materials developed by Object Technology International Inc. of Ottawa, Ontario,
Canada. Object Technology International Inc. is a subsidiary of the IBM Corporation.
If you have comments about the product or this document, address them to: IBM Corporation, Attn: IBM Smalltalk
Group, 621-107 Hutton Street, Raleigh, NC 27606-6324. You can fax comments to (919) 828-9633.
When you send information to IBM, you grant IBM a nonexclusive right to use or distribute the information in any
way it believes appropriate without incurring any obligation to you.
Copyright International Business Machines Corporation 1994, 2000. All rights reserved.
US Government Users Restricted Rights Use, duplication or disclosure restricted by GSA ADP Schedule Contract
with IBM Corp.
Contents
Notices . . . . . . . . . . . . . . . ix Magnitude classes . . . . . . . . . . . . 13
Trademarks . . . . . . . . . . . . . . ix Magnitude comparing . . . . . . . . . . 14
Association . . . . . . . . . . . . . 14
About this book . . . . . . . . . . . xi Character . . . . . . . . . . . . . . 14
Date . . . . . . . . . . . . . . . . 16
What this book includes . . . . . . . . . . xi
Time. . . . . . . . . . . . . . . . 16
Who this book is for . . . . . . . . . . . xi
Number . . . . . . . . . . . . . . 17
About this product or feature . . . . . . . . xi
Integer . . . . . . . . . . . . . . . 18
Conventions used in this book . . . . . . . . xii
Fraction . . . . . . . . . . . . . . 19
References. . . . . . . . . . . . . . . xii
Float. . . . . . . . . . . . . . . . 19
Common Language Data Types, Common
ScaledDecimal . . . . . . . . . . . . 20
Language Implementation, and Common Process
Graphical classes . . . . . . . . . . . . 20
Model . . . . . . . . . . . . . . . xii
Point . . . . . . . . . . . . . . . 20
Common File System . . . . . . . . . . xiii
Rectangle . . . . . . . . . . . . . . 21
Common Graphics. . . . . . . . . . . xiii
Stream classes . . . . . . . . . . . . . 22
Common Widgets . . . . . . . . . . . xiii
Accessing . . . . . . . . . . . . . . 23
Common Printing . . . . . . . . . . . xiv
Copying . . . . . . . . . . . . . . 23
Dynamic Data Exchange . . . . . . . . . xiv
Enumerating . . . . . . . . . . . . . 23
National Language Support. . . . . . . . xiv
Positioning . . . . . . . . . . . . . 24
Tell us what you think . . . . . . . . . . xiv
Reading . . . . . . . . . . . . . . 24
Testing . . . . . . . . . . . . . . . 24
Chapter 1. IBM Smalltalk overview . . . 1 Truncating . . . . . . . . . . . . . . 24
Base subsystems . . . . . . . . . . . . . 1 Writing . . . . . . . . . . . . . . . 24
Common Language Data Types . . . . . . . 1 Creating instances . . . . . . . . . . . 24
Common Language Implementation . . . . . 1 Support classes . . . . . . . . . . . . . 24
Common Process Model . . . . . . . . . 1 UndefinedObject. . . . . . . . . . . . 24
Common File System . . . . . . . . . . 1 Message and DirectedMessage . . . . . . . 25
Common Graphics . . . . . . . . . . . 2 Block . . . . . . . . . . . . . . . 25
Common Widgets . . . . . . . . . . . 2 Exception handling classes . . . . . . . . . 25
Extended Widgets . . . . . . . . . . . 2 ExceptionalEvent . . . . . . . . . . . 25
Drag and Drop . . . . . . . . . . . . 2 Signal . . . . . . . . . . . . . . . 25
Common Printing . . . . . . . . . . . 2 Creating new exceptions . . . . . . . . . 25
Design intent . . . . . . . . . . . . . . 2 Signaling an exception. . . . . . . . . . 26
Architecture . . . . . . . . . . . . . . 3 Handling of exceptions . . . . . . . . . 26
Industry-standard support . . . . . . . . . . 3 ExceptionalEvent collections . . . . . . . . 28
Platform support . . . . . . . . . . . . . 4 Completion blocks . . . . . . . . . . . 29
Default exception handler . . . . . . . . 30
Chapter 2. Common Language Data System exceptions . . . . . . . . . . . 30
Types . . . . . . . . . . . . . . . . 5 Examples of exception handling . . . . . . 30
Object behavior . . . . . . . . . . . . . 6
Basic dependents mechanism . . . . . . . . 6 Chapter 3. Common Language
Behavior testing . . . . . . . . . . . . 6 Implementation . . . . . . . . . . . 33
Class identity testing . . . . . . . . . . 6 Behavior messages . . . . . . . . . . . . 34
Copying . . . . . . . . . . . . . . . 6 Class queries . . . . . . . . . . . . . 34
Printing and storing . . . . . . . . . . . 6 Compiling . . . . . . . . . . . . . . 34
Performing . . . . . . . . . . . . . . 7 Creating instances . . . . . . . . . . . 34
Error handling . . . . . . . . . . . . . 7 Enumeration . . . . . . . . . . . . . 35
Primitive accessing . . . . . . . . . . . 7 Instance accessing . . . . . . . . . . . 35
Mutating . . . . . . . . . . . . . . 7 Instance structure testing . . . . . . . . . 35
Testing object equality or identity . . . . . . 7 Method accessing . . . . . . . . . . . 35
Boolean classes . . . . . . . . . . . . . 7 Method adding and deleting . . . . . . . 36
Boolean messages. . . . . . . . . . . . 7 Method queries . . . . . . . . . . . . 36
Collection classes . . . . . . . . . . . . . 8 Class messages . . . . . . . . . . . . . 36
Collection protocols . . . . . . . . . . . 9 Class variable accessing . . . . . . . . . 37
Common collection protocols . . . . . . . 10 Instance variable accessing . . . . . . . . 37
Unique collection protocol . . . . . . . . 12
Contents v
Fonts . . . . . . . . . . . . . . . . 211 Leaving a target . . . . . . . . . . . 268
Using the system browser font . . . . . . 212 Dropping. . . . . . . . . . . . . . 268
Colors . . . . . . . . . . . . . . . . 213 Canceling a drag . . . . . . . . . . . 269
Clipboard operations . . . . . . . . . . . 213 System configuration . . . . . . . . . . . 269
Examples for using the clipboard . . . . . . 214 Simple drag and drop . . . . . . . . . . 270
Platform-integrated drag and drop . . . . . . 215 Widget limitations . . . . . . . . . . . . 270
Target types . . . . . . . . . . . . . 216
Transferring data . . . . . . . . . . . 216 Chapter 10. Common Printing . . . . 273
Drag and drop objects . . . . . . . . . 217 Common Printing classes . . . . . . . . . 273
Procs . . . . . . . . . . . . . . . 217 Printing process overview . . . . . . . . . 274
Common Widgets drag and drop classes . . . 217 Selecting a printer . . . . . . . . . . . 274
Drag source widgets and the CwDragContext Configuring print job attributes . . . . . . 275
object . . . . . . . . . . . . . . . 217 Creating a print job . . . . . . . . . . 275
Drop site widgets and CwDropSite objects . . 219 Using the printer prompter . . . . . . . . . 275
Data transfer and the CwDropTransfer object 223 Print job attributes. . . . . . . . . . . 276
The user interface process model . . . . . . . 225 Using a printer shell . . . . . . . . . . . 276
The system view . . . . . . . . . . . 226 Creating a shell. . . . . . . . . . . . 277
The application programmers view . . . . . 228 Adding callbacks . . . . . . . . . . . 277
Examples of applications with long-running Starting a job . . . . . . . . . . . . 278
operations . . . . . . . . . . . . . 231 Producing a page . . . . . . . . . . . 278
Ending a job. . . . . . . . . . . . . 279
Chapter 8. Extended Widgets . . . . 233 CwPrinterShell resources and convenience
Extended Widgets class hierarchy . . . . . . 233 methods . . . . . . . . . . . . . . 279
EwConstants pool dictionary . . . . . . . . 233 Printing with Common Graphics . . . . . . . 279
Creation convenience methods . . . . . . . 234 A complete example . . . . . . . . . . . 280
Extended list widgets. . . . . . . . . . . 234 Setting up printers and queues on UNIX platforms 282
Common resources . . . . . . . . . . . 235 Configuring printer setup options . . . . . 283
Scrolled lists . . . . . . . . . . . . . . 235 Printer configuration information. . . . . . 284
Drawn list widget . . . . . . . . . . . . 236
Icon list widgets . . . . . . . . . . . . 237 Chapter 11. IBM Smalltalk Virtual
Renderables . . . . . . . . . . . . . 238 Machine API . . . . . . . . . . . . 285
Direct editing of labels . . . . . . . . . 239
Who should read this chapter . . . . . . . . 285
Flowed icon list widget . . . . . . . . . . 240
Conventions . . . . . . . . . . . . . 285
Icon area widget . . . . . . . . . . . . 241
IBM Smalltalk C programming model . . . . . 286
Table list widget . . . . . . . . . . . . 241
Defined types . . . . . . . . . . . . 286
Table list resources . . . . . . . . . . 242
Object types . . . . . . . . . . . . . 286
Table columns . . . . . . . . . . . . 243
Macros versus functions . . . . . . . . . 288
Direct editing of cell values. . . . . . . . 245
External language interface . . . . . . . . . 288
Edit policies . . . . . . . . . . . . . 246
Parameter types . . . . . . . . . . . 288
Tree widgets. . . . . . . . . . . . . . 247
Calling a PlatformFunction . . . . . . . . 290
Icon trees. . . . . . . . . . . . . . 248
Inline external function calls . . . . . . . 290
Table trees . . . . . . . . . . . . . 249
PlatformFunction protocols . . . . . . . . 291
Notebook widgets . . . . . . . . . . . . 250
PlatformLibrary protocols . . . . . . . . 293
Creating pages . . . . . . . . . . . . 250
Entry points . . . . . . . . . . . . . . 294
Callbacks . . . . . . . . . . . . . . 250
Parameter types and return types . . . . . 295
PM notebook widget . . . . . . . . . . 251
Calling an EsEntryPoint . . . . . . . . . 296
WIN notebook widget . . . . . . . . . 253
EsEntryPoint protocols . . . . . . . . . 297
Progress bar widget . . . . . . . . . . . 253
Asynchronous callouts . . . . . . . . . . 298
Slider widget . . . . . . . . . . . . . 254
Calling a Platform Function asynchronously . . 298
Spin button widget . . . . . . . . . . . 256
Locking resources for an asynchronous call . . 301
Split window widget . . . . . . . . . . . 257
ACO errors and error cases. . . . . . . . 301
Tool bar widget . . . . . . . . . . . . 259
Walkbacks . . . . . . . . . . . . . 302
Creating tools . . . . . . . . . . . . 259
ACO errors . . . . . . . . . . . . . 302
Using tools . . . . . . . . . . . . . 260
Parameter types and return types . . . . . 303
Managing resources . . . . . . . . . . 303
Chapter 9. Drag and Drop . . . . . . 263 Extensions to platform function protocols . . . 305
Drag and drop adapters . . . . . . . . . . 263 ACO resource manager protocols. . . . . . 306
Sequence of events . . . . . . . . . . . 264 Resource future protocols . . . . . . . . 307
Voting and cursors . . . . . . . . . . 267 Static future protocols . . . . . . . . . 308
Source vote and image changes . . . . . . 268 ACO error protocols . . . . . . . . . . 308
Contents vii
UNIX environment . . . . . . . . . . . 425 Getting and setting properties . . . . . . . 459
Method specification . . . . . . . . . . 425 Releasing an OLE automation object. . . . . 460
Usage example . . . . . . . . . . . . 426 Using OLE custom controls (OCXs) . . . . . . 460
UNIX process . . . . . . . . . . . . . 427 Creating the OLE main window . . . . . . 461
Method specification . . . . . . . . . . 428 Creating OCXs . . . . . . . . . . . . 461
Usage example . . . . . . . . . . . . 430 Implementing an ambient-property handler . . 462
UNIX pipe stream . . . . . . . . . . . . 431 Using automation aspects of an OCX . . . . 463
UNIX read pipe stream method specification 431 Implementing event handlers . . . . . . . 464
UNIX write pipe stream method specification 434 Wrapping OCXs with extended widgets . . . 466
Using licensed OCXs . . . . . . . . . . 467
Chapter 15. Object Linking and
Embedding (OLE) . . . . . . . . . 437 Appendix A. Widget resources and
OLE concepts and definitions . . . . . . . . 438 callbacks . . . . . . . . . . . . . 469
OLE classes . . . . . . . . . . . . . . 440
OleMainWindow . . . . . . . . . . . 440 Appendix B. Extended widgets
OlePrimitive. . . . . . . . . . . . . 441 resources and callbacks . . . . . . 491
OleClient . . . . . . . . . . . . . . 441
OleControl . . . . . . . . . . . . . 442
OleAutomationObject . . . . . . . . . 442 Appendix C. Drag and drop resources
OleFile . . . . . . . . . . . . . . 442 and callbacks . . . . . . . . . . . 513
Building an OLE document container . . . . . 442
Creating an OLE main window . . . . . . 443 Appendix D. Common graphics
Creating OLE clients . . . . . . . . . . 444 platform differences . . . . . . . . 515
Enabling clipboard operations . . . . . . . 449
Creating an object verb menu . . . . . . . 451
Invoking the Links dialog . . . . . . . . 454
Appendix E. Common widgets
Saving and retrieving an OLE client . . . . . 455 platform differences . . . . . . . . 517
Using OLE automation . . . . . . . . . . 457
Creating OLE Automation Objects . . . . . 458 Index . . . . . . . . . . . . . . . 525
Invoking methods of OLE automation objects 458
IBM may have patents or pending patent applications covering subject matter in
this document. The furnishing of this document does not give you any license to
these patents. You can send license inquiries, in writing, to the IBM Director of
Licensing, IBM Corporation, 500 Columbus Avenue, Thornwood, NY, USA 10594.
IBM may change this publication, the product described herein, or both.
Trademarks
The following terms are trademarks of the IBM Corporation in the United States or
other countries or both:
UNIX is a registered trademark in the United States and other countries licensed
exclusively through X/Open Company Limited.
It is assumed that the reader has previous programming experience and a working
knowledge of the following:
v Object-oriented programming concepts
v The Smalltalk programming language
v The IBM Smalltalk programming environment
Highlight
style Used for Example
Boldface New terms the first time they are VisualAge uses construction from
used parts to develop software by
assembling and connecting reusable
components called parts.
Items you can select, such as push Select Add Part from the Options
buttons and menu choices pull-down. Type the parts class and
select OK.
Italics Special emphasis Do not save the image.
Titles of publications Refer to the VisualAge Smalltalk Users
Guide.
Text that the product displays The status area displays Category:
Data Entry.
VisualAge programming objects, such Connect the windows
as attributes, actions, events, composite aboutToOpenWidget event to the
parts, and script names initializeWhereClause script.
Monospace VisualAge scripts and other examples doSomething
font of Smalltalk code | aNumber aString |
aNumber := 5 * 10.
aString := 'abc'.
Text you can enter For the customer name, type John
Doe
References
The following are suggested references for IBM Smalltalk programmers.
Common Graphics
James L. Conger, Windows API Bible: The Definitive Programmers Reference, New
York, N.Y., Waite Group Press, 1992
Brian Myers and Eric Hamer, Mastering Windows NT Programming, New York, N.Y.,
SyBex, 1993
Ney ed., Xlib Programming Manual, Volume One, OReilly & Associates, Inc.,
Sebastopol, Calif., 1988
Nye ed., Xlib Reference Manual, Volume Two, OReilly & Associates, Inc., Sebastopol,
Calif., 1988
Steve Rimmer, Bit-Mapped Graphics, 2nd ed., New York, N.Y.: Wincrest/McGraw-
Hill, 1993
Steve Rimmer, Supercharged Bitmapped Graphics, 1st ed., New York, N.Y.,
Wincrest/McGraw-Hill, 1992
Presentation Manager Programming Reference Volume III, OS/2 2.0 Technical Library,
IBM, 1992
Any other manuals discussing the X Window Systems graphics capabilities (Xlib).
Common Widgets
Asente and Swick, X Window System Toolkit, Digital Press
Eric F. Johnson and Kevin Reichard, Power Programming... Motif (Second Edition),
New York, N.Y., MIS Press, 1993
Nye and OReilly, X Toolkit Intrinsics Reference Manual, Volume Five, Sebastopol,
Calif., OReilly & Associates, Inc., 1990
Common Printing
Axel Deininger, An X Print Server-Bringing WYSIWYG to X, The X Resource, X
(April 1994), 141
National Language Design Guide, Designing Enabled Products, Volume 1, 2nd ed., IBM
reference SE09800101
National Language Support Reference Manual, Volume 2, 2nd Ed., IBM reference
SE09800101
IBM Smalltalk directly utilizes the native functionality provided by each platform.
IBM Smalltalk emulates API functionality not directly supported by a particular
platform in order to support a complete and consistent interface across all
supported platforms. As part of the implementation of IBM Smalltalk, access to
platform-specific functionality is also provided.
Base subsystems
IBM Smalltalk comprises nine subsystems:
Common Widgets
The Common Widgets (CW) subsystem contains the classes and associated
methods for constructing application user interfaces through the composition of
user interface components called widgets.
Extended Widgets
The Extended Widgets (EW) subsystem contains the classes and associated
methods for expanding on graphical user interfaces built using the Common
Widgets subsystem.
Common Printing
The Common Printing (CP) subsystem contains the classes and associated methods
for performing printer job and page control, setup, and graphical and text output.
Design intent
The increasing number of platforms supporting graphical user interfaces has
resulted in a demand for simultaneous deployment of applications on multiple
platforms. At the same time, applications are expected to adopt platform look and
feel standards in order to be accepted by end users. One-shot porting is not a
viable option, because maintenance becomes very difficult if applications cannot be
single sourced. Applications need to meet the following requirements:
v It must be possible to single source applications across diverse platforms.
v Applications must conform to the look and feel standards of each platform.
v For maximum performance and platform integration, applications must leverage
native functionality wherever possible.
v Programmers capable of developing portable applications are in increasing
demand. It is expensive for organizations to move their development teams to
new, non-standardized software technologies. There is a growing emphasis on
software standards as a means to maximize the return on investment when
training developers.
IBM Smalltalk provides the virtual machine layer implemented for the target
platform. IBM Smalltalk is built on the native capabilities of the platform, together
with additional platform-specific classes and methods developed in order to
support the full IBM Smalltalk API.
Developers working in the IBM Smalltalk environment have the capabilities of all
layers at their disposal to use as the needs of the product and organization dictate.
Of course, developers sacrifice portability when they make use of the layers below
the common classes and methods. However, even in such cases, they can use the
development environment to identify and control non-portable parts of the
application.
Industry-standard support
Unlike most Smalltalk class libraries that were developed in the absence of
standards, IBM Smalltalk was developed based on existing industry software and
hardware standards. The use of these standards is a major benefit to development
teams because it means that Smalltalk developers can use the same terminology
and application protocols used in other languages. This reduces the need for
vendor-specific Smalltalk documentation and training for the base class libraries,
window systems, file systems, and graphics and process models. It also leverages a
corporations investment in standards training and documentation.
Although not essential, familiarity with the above standard interfaces is helpful to
understanding and using the IBM Smalltalk subsystems.
Platform support
IBM Smalltalk supports a wide variety of hardware and operating systems. In each
case, the implementation emphasizes those capabilities of the platform that are
standardized, efficiently implemented, or essential to achieving native look and
feel. For example, the native widgets provided by OSF/Motif are used as the
underlying implementation of the standard widgets on those platforms supporting
OSF/Motif. Similarly, Windows controls are used on the Microsoft Windows
platform.
All data types in the CLDT subsystem are concrete classes, that is, they are meant
to be instantiated and used directly in programs. To avoid constraining
implementations, no inheritance hierarchy has been defined for CLDT. However,
there is a set of basic messages that all the CLDT data types are assumed to
support. Because in all existing implementations this common behavior is
implemented in class Object, it will be referred to as Object behavior. It is
convenient to include a description of Object behavior in this section, even though
Object itself is not a CLDT data type because it is an abstract class.
Our aim in this section is to provide an overview of CLDT, assuming that readers
are generally familiar with Smalltalk.
Note: One of the primary motivations in the design of CLDT was to achieve
maximum functionality with minimum complexity. Most inexperienced
Smalltalk developers complain that the class library is too large, needlessly
complicated, and consequently difficult to learn. There is clearly a trade-off
between the utility and additional functionality provided by a large library,
and the steep learning curve that it entails. The methods in CLDT have been
chosen because they provide real added value, by which we mean that they
truly save developers both time and effort.
Porting tip: These tips compare and contrast CLDT with the base classes in the
unmodified versions of Smalltalk/V or Objectworks\Smalltalk as
shipped by the vendors. Differences between CLDT messages and
those supported in Smalltalk/V or Objectworks\Smalltalk are noted.
Many of these differences result from different strategies for error
handling and other implementation-dependent features. While CLDT
sometimes omits a method that is included in either Smalltalk/V or
Objectworks\Smalltalk, in most cases the omitted method is either
rarely used or has a trivial implementation. The preferred Smalltalk
programming style is to in-line trivial methods; accordingly we have
not cluttered the CLDT class libraries with methods that are seldom or
never used.
Note: For details on the dependents mechanism see Chapter 14 of the Blue Book.
The dependents mechanism is really a legacy from earlier Smalltalk graphics
implementations. It was used to force updates in cases where several
windows provided views on the same object or several closely related
objects. It is no longer used in modern GUI systems like Common Graphics
and Common Widgets. Consequently, we have included only the basic
protocol in CLDT.
Porting tip: All Smalltalk dialects use the same basic messages to implement the
dependents mechanism, with one exception: Smalltalk/V does not
support removeDependent:. Both Smalltalk/V and Objectworks\Smalltalk
provide a number of additional (and different) messages that extend
the basic mechanism.
Behavior testing
class, isKindOf:, isMemberOf:, isNil, notNil, respondsTo:
Porting tip: The class identity testing messages are all short (and often optimized)
forms of:
"self class = {NameOfClass}"
"self isKindOf: {NameOfClass}"
Each Smalltalk dialect has its own list of short forms; the choices are
usually driven by implementation details. Smalltalk/V has a large
number of such methods, while Objectworks\Smalltalk uses only a few.
In CLDT we have chosen a middle course. Note that Smalltalk/V uses
the spelling MetaClass rather than the Blue Book spelling Metaclass,
which is used in CLDT and Objectworks\Smalltalk.
Copying
copy
Note: Unlike other Smalltalk implementations, CLDT does not provide a public
default version of either deepCopy or shallowCopy in Object protocol. The
semantics of both these messages are normally specific to each class, and
often to an application.
Porting tip: You should avoid using these methods because they adversely affect
the packaging process. Refer to the guidelines in the packaging
materials of IBM Smalltalk Users Guide for more information.
Error handling
doesNotUnderstand:, error:, primitiveFailed, halt, halt:, shouldNotImplement,
subclassResponsibility
Porting tip: These methods provide only the most basic error handling.
Smalltalk/V uses implementedBySubclass n place of the Blue Book
method subclassResponsibility, and does not support shouldNotImplement
or halt:. All Smalltalk dialects provide additional error handling
methods that are system- and operating system-specific.
Primitive accessing
basicSize, instVarAt:, instVarAt:put:
Mutating
become:
Porting tip: The exact semantics of become: (and Array>>multiBecome:) are not
consistent across different Smalltalk platforms, particularly in the use
of the two-way become (in which the receiver and argument actually
switch identities) and the one-way become (in which the receiver is
de-referenced and garbage collected). IBM Smalltalk uses a one-way
become. See the Blue Book for more complete information on become:.
Boolean classes
The Boolean classes are True and False. Booleans are used in Smalltalk to
implement both standard and application-defined control constructs. Although it
might seem strange that Smalltalk does not provide any built-in mechanisms for
controlling program running, this turns out not to be a limitation. Indeed, it adds a
surprising degree of flexibility to the language.
Boolean messages
&, and:, eqv::, ifFalse:, ifFalse:ifTrue:, ifTrue:, ifTrue:ifFalse:, not, or:, xor:, |
Porting tip: All Smalltalk dialects implement the same set of Boolean messages.
Users should be aware that in some cases the compiler can optimize
by in-lining control operations rather than implementing them as
message sends. This is usually transparent unless there is an attempt
to subclass or modify one of the Boolean classes.
Note: Three seldom-used Blue Book collection classes have been excluded from
CLDT, namely LinkedList, MappedCollection, and RunArray. The design of
LinkedList betrays too much of its implementation and is not an
industrial-strength List abstract data type. RunArray was used mainly in the
original Smalltalk graphics system to store bitmaps. MappedCollection allows
for one level of indirection in Dictionary accesses and is seldom used in
practice. Moreover, in most cases, the Dictionary message at:ifPresent: can be
used to achieve the same effect.
A collection is a group of objects that can be manipulated and operated on either
individually or as a whole. The objects contained by a collection are called its
elements. The number of elements in a collection is its size. Most collections are
generic containers that can hold any kind of object, but five collections (ByteArray,
DBString, Interval, String, and Symbol) place restrictions on the class of their
elements. As a rule, developers should use care when defining recursive
collections, that is, collections that have themselves, directly or indirectly, as one of
their own elements. In a recursive collection there are operations, for example, =,
that can fail in some implementations.
Bag and Set are the simplest kinds of collections, because they are unordered and
do not support an indexing mechanism. Bags can contain duplicate elements, while
Sets cannot. Otherwise, their behavior is identical. Neither Bags nor Sets can
contain an instance of UndefinedObject (usually referred to as nil); that is, attempts
to add nil to a Bag or Set are ignored. The elements of a Bag or Set cannot be
individually accessed, because only group operations are supported.
LookupTable has the same protocols as Dictionary but does not contain any
associations. This makes LookupTable faster than Dictionary for everything except
associationsDo:. It also requires less space.
The elements of the ordered collections are all indexed by integer keys that begin
at 1 and increase. The ordered collections are Array, ByteArray, DBString,
OrderedCollection, SortedCollection, String, and Symbol. OrderedCollection offers
the most comprehensive protocol of any of the collection classes, and is
consequently the one used most often by developers. OrderedCollections do not
have a fixed size, but can grow as needed to hold additional elements. The
elements of an OrderedCollection can be any class of object. SortedCollection is similar
to OrderedCollection, except that the elements are held in sorted order. The sort
ordering is determined by the sort block associated with the SortedCollection. A sort
block is a block with two formal parameters, which answers a Boolean value when
provided with any two elements of the collection. If the answer is true then meter
should be sorted before the second, whereas false indicates that the opposite order
is correct.
The size of Array, ByteArray, DBString, String, and Symbol collections is fixed
when they are created, and cannot be changed. Fixed-size collections are normally
implemented as contiguous blocks of memory; consequently they can usually
support efficient accessing, copying, and traversing operations. While the elements
of an Array can be any object, the elements of a ByteArray can be integers between
0 and 255 (in other words, bytes). The elements of DBString, String and Symbol
must be characters. The elements of String must be characters ranging in value
from 0 to 255, whereas the elements of DBString can be characters in the range 0 to
65535, that is, double-byte characters. A Symbol is distinguished from a String in
that it is immutable, that is, a Symbol is a read-only object once it has been created.
Collection protocols
The collection classes support a very rich protocolso rich that at times it can be
confusing. To help structure the following section, the method names are grouped
in loosely defined functional categories. The protocol categories are described in
the following section, and the table shows a cross-reference mapping between
classes and the protocols they support. A difficulty arises in cases where two or
more classes support the same message name, but the semantics of the message are
not identical. Usually, one message is just a refinement of the other, that is, it
imposes additional restrictions on the implementation of the message. A more
Accessing
after:, at:, basicAt:, before:, findFirst:, findLast:, first, indexOf:, indexOf:ifAbsent:,
indexOfSubCollection:startingAt:, indexOfSubCollection:startingAt:ifAbsent:, last
Adding
add:, addAll:
Byte accessing
byteAt:, byteAt:put:
Converting
asArray, asBag, asByteArray, asOrderedCollection, asSet, asSortedCollection,
asSortedCollection:
Copying
,, copyFrom:to:, copyReplaceAll:with:, copyReplaceFrom:to:with:,
copyReplaceFrom:to:withObject:, copyReplacing:withObject:, copyWith:, copyWithout:,
reverse
Note: Several messages in this protocol have the same names as messages in other
protocols but have different semantics: add:, addAll:, at:, and at:put:. The use
of these names is well established in the Smalltalk community and has not
been changed in CLDT. The messages at:ifAbsentPut:, at:ifPresent:,
removeAllKeys:, and removeAllKeys:ifAbsent: are not in the Blue Book, but have
been included in CLDT because their use results in more compact and
readable code.
Dictionary enumerating
associationsDo:, keysAndValuesDo:, keysDo:
Enumerating
collect:, conform:, detect:, detect:ifNone:, do:, inject:into:, reject:, select:
Ordered enumerating
doWithIndex:, from:to:do:, from:to:doWithIndex:, reverseDo:, with:do:
Note: The semantics of both of these operations require that the receiver collection
be ordered.
Ordered removing
removeAtIndex:, removeFirst, removeLast
Rehashing
rehash
Removing
remove:, remove:ifAbsent:, removeAll:
Note: Implementations of the messages <, <=, >, and >= use the U.S. English
collation sequence and do not necessarily reflect the collation sequence used
by the underlying platform. The message is needed for practical string
searching.
Porting tip: Smalltalk/V does not support match: and sameAs:. It also uses
asLowerCase and asUpperCase rather than the Blue Book (and IBM
Smalltalk) spelling asLowercase and asUppercase. Neither Smalltalk/V
nor Objectworks\Smalltalk support indexOf:matchCase:startingAt:.
Collating sequences vary from platform to platform (for example, case
sensitivity) and from language to language (for example, some
languages treat certain combinations of letters as a single character).
String comparison results can therefore differ between platforms. The
messages <, <=, >, and >= perform case insensitive comparison, while
the = message is case sensitive. This means, for example, that <= is
not the logical or of < and =. Use sameAs: instead of = for case
insensitive equality comparison.
Storing
at:put:, atAll:put:, atAllPut:, basicAt:put:, replaceFrom:to:with:,
replaceFrom:to:with:startingAt:, replaceFrom:to:withObject:
Note: The message replaceFrom:to:withObject: is not in the Blue Book but has been
included in CLDT for symmetry and completeness.
Testing
includes:, isEmpty, notEmpty, occurrencesOf:, size
Array
multiBecome:
Note: This message is not in the Blue Book but has been included in CLDT in
order to provide an efficient means of mutating multiple objects.
Bag
add:withOccurrences:
Interval
increment
Interval class
from:to:, from:to:by:
SortedCollection
sortBlock, sortBlock:
SortedCollection class
sortBlock:
Note: These String and DBStringmessages are not in the Blue Book. They have
been added to IBM Smalltalk based on feedback from developers.
Porting tip: The only one of these String and DBString messages that is supported
by Smalltalk/V is trimBlanks. Smalltalk/V also provides asInteger and
asFloat which are similar to asNumber. Objectworks\Smalltalk supports
only asNumber, byteAt:, and byteAt:put:. Objectworks\Smalltalk counts
Null (ASCII 0) as a separator in trimSeparators, but IBM Smalltalk and
Smalltalk/V do not.
Symbol
argumentCount
Note: This method is not in the Blue Book, but is needed to extract the number of
expected arguments from a selector.
Magnitude classes
The CLDT magnitude classes are Association, Character, Date, Float, Fraction, Integer,
and Time. Magnitude classes represent objects to which a linear ordering can be
applied, such as characters, numbers, dates, and times. Magnitude classes support
the capability to compare, query, and determine ranges of these ordered objects.
Numbers are a major subset of the magnitude classes. The different number classes
(Float, Fraction, and Integer) provide concrete representations for integer, rational,
and real numbers. Numbers can only be created by the evaluation of an expression
or by literal declaration.
All of the magnitude classes support the basic magnitude protocol for comparing
values.
Association
An Association represents a binary relation between two objects, called the key and
the value.
Accessing
key, key:, key:value:, value, value:
Porting tip: Both IBM Smalltalk and Objectworks\Smalltalk define the = message in
terms of both the key and the value. Smalltalk/V defines = in terms of
the key only. Smalltalk/V does not provide the message key:value:.
Character
The class Character is an abstraction for character data. Characters have values
between 0 and 65535, and are unique objects that represent code points. A code
point is simply a numeric valuethe format and graphical appearance of a
character are defined by the currently selected character set. All vendors agree that
the characters with values from 0127 correspond to the ASCII standard character
set. However, there is no agreement on the interpretation of other characters.
Groups of characters can be composed into either a String or a DBString depending
upon the values of the characters being composed.
Accessing
value, digitValue
Porting tip: CLDT does not support the Blue Book method asciiValue because it is
not appropriate for characters whose value exceeds 127, the range of
values converted by the ASCII standard. Rather, the asciiValue message
is replaced by the more general message value. Objectworks\Smalltalk
does not support the message value, but uses asInteger as the
equivalent message. Smalltalk/V does not support the message value,
but uses asciiValue as the equivalent message.
Porting tip: Smalltalk/V uses the spelling asLowerCase and asUpperCase, and does
not support asSymbol. Character conversions outside the ASCII range
of values (0 to 127) vary from platform to platform and from language
to language. Character conversion results might differ between
platforms. Objectworks\Smalltalk does not support asString.
Note: The Blue Book specifies that class Character should support messages that
provide access to several of the standard ASCII nonprinting characters used
for text formatting: backspace, cr, esc, newPage, space, and tab.
Objectworks\Smalltalk adopts this approach, and extends it to support
several additional characters. The approach taken by IBM Smalltalk is to
supply all of the nonprinting ASCII characters in a pool dictionary called
CldtConstants as shown in Table 2.
Porting tip: Smalltalk/V uses the spelling isLowerCase and isUpperCase. Character
classifications outside the ASCII range of values (0 to 127) vary from
platform to platform and from language to language. Character
conversion results might differ between platforms. Neither
Objectworks\Smalltalk nor Smalltalk/V support isPunctuation for
Characters.
Date
Date represents an abstraction for a given day of a given year. The Blue Book
specifies the instance protocol of Date in functional form, but does not actually
define method names for most operations. Consequently, although all Smalltalk
vendors provide basically the same functionality, there is some variation between
the message names used. The general form is that Date provides protocol for
manipulating dates, while Date class supports instance creation and general queries.
Accessing
dayName, dayOfMonth, dayOfYear, dayIndex, monthIndex, monthName, year
Calculating
addDays:, asSeconds, daysFromBaseDay, daysInMonth, daysInYear, daysLeftInMonth,
daysLeftInYear, firstDayOfMonth, subtractDate:, subtractDays:
Creating Instances
dateAndTimeNow, fromDays:, newDay:month:year:, newDay:monthIndex:year:,
newDay:year:, today
Querying
| daysInMonth:forYear:, daysInYear:, indexOfDay:, indexOfMonth:, nameOfDay:,
nameOfMonth:
Time
Time represents an abstraction for a time of day, based on a 24-hour clock. In
contrast to Date, class Time is much more clearly specified in the Blue Book, and
consequently there is much less variation between the vendor implementations.
Smalltalk/V adds additional protocol to Time class to control the real-time clock on
a personal computer. As with Date, the form is that Time provides protocol for
manipulating Time instances, while Time class supports instance creation and
general queries.
Calculating
addTime:, asSeconds, subtractTime:
Creating instances
dateAndTimeNow, fromSeconds:, now
Querying
millisecondsPerDay, millisecondsToRun:, millisecondClockValue
Number
The number classes currently supported in CLDT are Integer, Fraction, and Float,
which provide abstractions for the mathematical concepts of integers, rational
numbers (fractions), and real numbers (floating point). The CLDT number classes
do not deal explicitly with precision or other properties of numbers that depend on
implementation. Another class, Decimal, deals explicitly with precision. Decimal
class is a subclass of Number and is defined in DecimalMath.
Arithmetic
*, +, -, /, //, \\, abs, negated, quo:, rem:
Converting
@, asFloat, asFraction, asInteger, degreesToRadians, radiansToDegrees
Testing
negative, positive, strictlyPositive
Generality
lessGeneralThan:, moreGeneralThan:
Math functions
arcCos, arcSin, arcTan, cos, exp, floorLog:, ln, log:, raisedTo:, raisedToInteger:, reciprocal,
sign, sin, sqrt, squared, tan
Porting tip: Smalltalk/V does not support floorLog:, and adds the message
timesTwoPower:, which is not included in IBM Smalltalk.
Integer
In addition to supporting the basic protocol, the class Integer supports the
following messages.
Manipulating bits
allMask:, anyMask:, bitAnd:, bitAt:, bitInvert, bitOr:, bitShift:, bitXor:, clearBit:, highBit,
isBitSet:, noMask:, setBit:
Note: The CLDT messages clearBit:, isBitSet:, and setBit: are not in the Blue Book
but have been added to make bit-level manipulation easier.
Enumerating
timesRepeat:
Testing
even, odd
Printing
printOn:base:, printOn:base:showRadix:, printStringRadix:, printStringRadix:padTo:,
printStringRadix:showRadix:
Converting
| asCharacter:, asScaledDecimal
Fraction
CLDT assumes that a Fraction is always reduced to its lowest common
denominator. Using unreduced instances of Fraction can cause errors with current
implementations.
Accessing
denominator, numerator
Creating instances
numerator:denominator:
Note: This message has been included to comply with the Blue Book and Red
Book, but its use is strongly discouraged. It is possible to create an
unreduced instance of Fraction with this method, which could lead to errors,
because reduced Fractions are expected. The recommended way to create a
Fraction is with the division message (/) or with conversion messages.
Converting
| asScaledDecimal
Float
Additional supported protocols for Float are:
Accessing
fractionPart, integerPart
Porting tip: The fractionPart and integerPart messages are not supported in
Smalltalk/V.
Creating instances
pi
Note: The irrational number is singled out because this message is in both the
Blue Book and Red Book, and is supported by all vendors.
Converting
| asScaledDecimal
Precisions and scales do not follow the same arithmetic rules as values. For
example:
| Precision = 3 Precision = 2 Precision = 4
| Scale = 2 Scale = 1 Scale = 2
| 9.99s + 2.3s = 12.29s
Converting
| asScaledDecimal, asFloat, asFraction, asInteger
Accessing
fractionPart, integerPart, scale, significantDigits
Printing
printOn:showDigits:Pad:
Creating instances
fromString:
| The DM application also provides the method asScaledDecimal to the class String.
| The following illustrates two ways of creating the same ScaledDecimal:
| '3.4' asScaledDecimal
| ScaledDecimal fromString: '3.4'
Graphical classes
The CLDT graphical classes are Point and Rectangle, which are abstractions
representing the plane geometrical concepts of point (two-dimensional) and
rectangle. They are described in the Blue Book. Point and Rectangle are heavily
used in the implementation of graphics routines, both in the Common Widgets and
Common Graphics subsystems, and in applications.
Point
In Smalltalk, Points are commonly written as A@B, where A and B are numbers.
Note: A CLDT convention is that the point 0@0 (the origin) is in the upper left
corner of the screen. The x-coordinate increases as a Point moves to the
right, and the y-coordinate increases as it moves down. In some cases this
does not conform to the usual conventions for the platform GUI (for
example, in OS/2 Presentation Manager the origin is the lower left corner).
CLDT imposes a uniform definition of origin to facilitate portability.
Accessing
x, x:, y, y:
Arithmetic
*, +, -, /, //, abs, negated
Porting tip: The arithmetic messages quo:, rem:, and \\ are defined in
Objectworks\Smalltalk, but they are not specified by the Blue Book,
and have not been included in IBM Smalltalk.
Creating instances
x:y:
Magnitude comparing
<, <=, =, >, >=, between:and:, max:, min:
Point functions
dotProduct:, dist:, normal, transpose
Note: While not often used, these messages have been included because they are
defined in the Blue Book. Objectworks\Smalltalk provides an even more
extensive library of such functions, and support for polar coordinates,
presumably to support complex computational geometry algorithms. Given
the tendency in modern systems to use platform graphics routines, or even
graphics processors, these functions have not been included in CLDT.
Creating rectangles
corner:, extent:
Rectangle
Rectangle supports an extensive accessing protocol for getting and setting points on
the boundary of a rectangle. Most of the other messages are provided to facilitate
graphics calculations.
Geometric functions
amountToTranslateWithin:, area, areasOutside:, contains:, containsPoint:, expandBy:,
insetBy:, insetOriginBy:cornerBy:, intersect:, intersects:, merge:, moveBy:, moveTo:,
scaleBy:, translateBy:
Stream classes
The stream classes are ReadStream, ReadWriteStream, and WriteStream. Described in
Chapter 12 of the Blue Book, streams provide an abstraction that enables store and
retrieve operations to be performed on the elements of an underlying composite
data structure, usually an indexed collection. In particular, a stream remembers the
position of the last element in the data structure to be read or written. Streams are
positionable, that is, the current read/write position can be changed using the
streams public interface. This allows elements to be accessed in either a sequential
or nonsequential fashion.
It is implicit in stream semantics that the elements of the underlying data structure
are sequentially ordered. With respect to the CLDT classes, a ReadWriteStream or
WriteStream can stream over a String, DBString, ByteArray, or Array. A ReadStream
can stream over the same set of classes plus Interval, OrderedCollection,
SortedCollection, or Symbol.
Note: One significant change has been made to Blue Book streamseach CLDT
stream has an instance variable that holds a string called a line delimiter. By
far the most common use of streams in practice is to read or write String
Accessing
close, lineDelimiter, lineDelimiter:, size, contents
Porting tip: The messages lineDelimiter and lineDelimiter: are not supported by
either Objectworks\Smalltalk or Smalltalk/V.
Copying
copyFrom:to:
Note: This is not a Blue Book message. However, it provides an efficient way to
copy a subset of elements from a stream without being forced to create an
intermediate collection.
Enumerating
do:
Reading
next, next:, nextLine, nextMatchFor:, contents, peek:, peekFor:, skipTo:, skipToAll:, upTo:,
upToAll:, upToEnd
Note: The messages skipToAll:, upToAll:, and upToEnd are not in the Blue Book but
provide needed functionality.
Porting tip: The messages skipToAll:, upToAll:, and upToEnd are not supported in
Smalltalk/V.
Testing
atEnd, isEmpty
Truncating
truncate
Note: This is not a Blue Book message but has been added because it is needed in
practice.
Writing
flush, nextPut:, nextPutAll:, next:put:, cr, space, tab
Porting tip: The Blue Book methods crTab: and crTab: were omitted because they
can be easily composed using cr and tab. They are not included in the
Red Book. The message flush is included to support polymorphic
programming using CLDT streams and CFS file streams. Sending flush
to a CLDT stream has no effect.
Creating instances
ReadStream class supports the Blue Book instance creation messages on: and
on:from:to:. WriteStream class supports the Blue Book messages on:, with:, and
with:from:to:. Instances of ReadWriteStream can be created by sending any of the
above messages to ReadWriteStream class.
Support classes
The support classes are UndefinedObject, Message, DirectedMessage, and Block.
UndefinedObject
The class UndefinedObject is discussed in Chapter 14 of the Blue Book. It describes
the behavior of a special object called nil, which is used to represent the value of
an unassigned variable or an undefined result. There is only a single instance of
UndefinedObject in the system. The messages isNil and notNil, inherited from Object
protocol, are redefined in UndefinedObject to always return the values true and false.
In addition, nil supports a protocol to create new Smalltalk classes, and to permit
the creation of new classes that have no superclass.
Block
A Block represents a lexical closure, that is, a piece of compiled code that can be
run on demand and need not be associated with a message in any class. Instances
of Block support two messages, whileTrue: and whileFalse:, which are used to
construct while loops. Additional Block protocol is defined in Exception handling
classes, Block evaluation methods on page 48, and Process-related block
methods on page 49.
ExceptionalEvent
Instances of this class represent an exceptional condition. Instances are signaled to
cause an exception handler to be invoked.
Signal
Instances of this class represent a signaled exception and are passed as an
argument to the exception handler.
Tip: As with the goto: statement of conventional languages, this can create
problems with code maintenance, and it is therefore recommended that
exception handling only be used for handling exceptional circumstances or
error conditions.
Signaling an exception
Exceptions are signaled by sending one of the following methods to the
appropriate instance of ExceptionalEvent:
signal Invokes the exception with no arguments.
signalWith:
Invokes the exception with a single argument.
signalWith:with:
Invokes the exception with two arguments.
signalWithArguments:
Invokes the exception with anArray of arguments
Handling of exceptions
You construct exception handlers by sending the message when:do: to a block. You
can install multiple handlers simultaneously by using repeated forms of when:do:
(that is, when:do:when:do:, when:do:when:do:when:do:, and so on) for up to five
When an exception is signaled, the most recent when:do: matching the exception is
evaluated first. When a when:do: message is found with either the exception or a
more general exception (one of the exceptions ancestors) as the when argument, its
handlerBlock is run.
Within the handler block, the following methods can be sent to an instance of
Signal:
argument
Answers the first argument that was passed to the signal message, or nil if
none.
arguments
Answers a collection containing any arguments that were passed to the
signal message.
exitWith:
Immediately returns an object instance as the value of the when:do:
exception.
resumeWith:
Returns key and value as the result of the signal message.
signal Indicates that the current handler is finished handling, or is not going to
handle, the exception and that a search for another handler should begin.
Notice that this is not the same semantics as sending signal to an instance
of ExceptionalEvent.
signalWith:
Same as signal, but modifies the arguments which are passed on.
signalWith:with:
Same as signal, but modifies the arguments which are passed on.
signalWithArguments:
Same as signal, but modifies the arguments which are passed on.
retry Runs the block that the when:do: message was sent to. All handlers are
reinstated.
handlesByDefault
Runs the default handler for the exception.
description
Answers a description of the exception. Returns the string an exception
has occurred if no description was set in the ExceptionalEvent.
exception
Answers the exception that was signaled. Access to the exception is not
normally required by handlers. However, this method is provided for
special purpose applications.
Note: The application has determined that an exceptional condition has occurred
and signals an exception.
anyMethod
"The application can then do something like the following.
Answer true if thatMethod completes without exception,
and false otherwise."
|[ self thatMethod ]
when: ThatException
do: [:signal |
"Handle the exception in some way: increment a counter,
print a message, fix a problem, ... and then exit the handler."
signal exitWith: false].
Note that dropping off the end of a handler block is equivalent to sending
resumeWith: (Association
key: #resume
value: valueOfTheBlock
Note: Neither the handler block nor the block that receives when:do: can contain a
return (|) expression. Although this is not a significant limitation, it should
be kept in mind when automatic code generation techniques are used,
because arbitrary code fragments cannot be wrapped with exception
handlers without first checking for return expressions.
v It is possible to signal another exception within the body of the handler.
v The handler block should not contain a return (|) expression. Returning
from an exception handler can cause unpredictable results.
ExceptionalEvent collections
Occasionally, it is desirable to use a single handler for several nonhierarchically
related exceptions. To handle this situation, the when:do: syntax has been extended
to allow an ExceptionalEvent collection as the when argument. ExceptionalEvent
collections are created by sending the vertical bar (|) message to an instance of
ExceptionalEvent as follows:
In class ExceptionalEvent:
| anExceptionalEvent
"Answer a new ExceptionalEvent collection
containing the receiver and anExceptionalEvent."
Within the body of a handler block, sending exception to the passed-in instance of
Signal always answers the particular exception that was signaled.
Completion blocks
The syntax codeBlock whenExceptionDo: completionBlock runs the completion block if
any exception occurs during the running of the code block. The completion block
is not considered to have handled the exception. That is, a further exception handler
is looked for after the completion block is run.
codeBlock whenExceptionDo: completionBlock
is equivalent to:
codeBlock
when: ExAll
do: [ :signal |
completionBlock value.
signal resumeBlock: nil
signal signal ].
is equivalent to:
| cleanedUp |
cleanedUp := false.
codeBlock
when: ExAll
do: [ :signal |
completionBlock value.
cleanedUp := true.
signal resumable: false.
signal signal ].
cleanedUp ifFalse: [ completionBlock value ]
Note: After a completion block has been run because of an exception, the
exception (actually the instance of Signal) is marked as being nonresumable
before another handler is searched for. This prevents the completion block
from being run multiple times.
If desired, the default handler block can be run from inside of a handler block by
sending handlesByDefault to the handlers signal argument.
Note: The exitWith: and retry messages cannot be used in the default handler block
unless the expression that caused the exception was run inside the receiver
block of a when:do: expression.
System exceptions
Globally accessible exceptions are stored in the pool dictionary named
SystemExceptions. By convention, the names of the exceptions in this pool begin
with Ex. SystemExceptions is a pool dictionary belonging to Object and is therefore
accessible by all subclasses of Object.
The following table lists system exceptions in the SystemExceptions pool dictionary.
Table 5. SystemExceptions pool dictionary
Pool variable name Description
ExAll The most general exception.
ExError The exception signaled by Object>>error:. This exception is
signaled with the string passed to error:.
ExHalt The exception signaled by Object>>halt. This exception is
signaled with the string passed to halt:. Note that Object>>halt
sends halt:.
ExUserBreak The exception signaled by the system default break handler.
The system exceptions ExError, ExHalt, and ExUserBreak use the first argument for
the error string when opening a debugger. If you create a child of these exceptions,
you must follow the same convention. Alternatively, you can provide a
defaultHandler in the child, which sends error: with an appropriate error message.
The defaultHandler for the system exceptions uses the description of the exception
as the error string if the first argument is not a string. If you are using exceptions
to return non-string values in the arguments of the signal (a reasonable use) then
you should provide a default handler for your exceptions that knows that the
arguments will not be strings. For example:
| anErrorException |
(anErrorException := ExError newChild)
description: 'a demonstration error occurred';
defaultHandler: [:sig |
self error: 'My error occurred with arguments: ',
sig arguments printString].
[ anObject printString ]
when: ExError
do: [:signal |
signal
exitWith: 'Exception: ', signal argument, 'while printing
a ', anObject class name ].
[1 error: 'demonstration' ]
when: ExError
do: [:signal |
aDifferentException signal].
[ [ true ] whileTrue: [
"Body of loop. Can only be exited by saying
'loopExitException signal'."
(System confirm: 'Signal the loopExitException')
ifTrue: [loopExitException signal].
"Create a doesNotUnderstandException."
1 error: 'This is for demonstration purposes'.
]
]
when: loopExitException
do: [ :signal |
signal exitWith: 'bye' ]
when: ExAll
do: [ :signal |
System message: 'An Exception has occurred: ', signal description.
signal retry ].
[ [ aStream atEnd ]
whileFalse: [ Transcript nextPut: aStream next. ]
]
atEndOrWhenExceptionDo: [
Transcript show: '...Closing the stream'.
aStream close ].
Unless you are already an experienced Smalltalk programmer, both the idea of
metaclasses and the role that they play in the language are probably unclear. The
Blue Book contains a discussion that might help to sort out the subject. The good
news is that most developers, even those who write very sophisticated Smalltalk
programs, never have to deal explicitly with metaclasses. They exist only in the
background, part of the hidden machinery that makes things work.
Behavior
ClassDescription
Class
Metaclass
CLIM does not provide a complete definition of Class or Metaclass. What it does do
is define a number of common class messages that are supported by all classes in
IBM Smalltalk. As might be guessed, CLIM has rather specific objectives. It is
intended to support:
v Browsing
v Creating or modifying class structures
v Compilation
v Adding and deleting methods to classes (with some restrictions)
It is expected that developers who want to add, modify, or delete methods to IBM
Smalltalk classes will normally do so using the source code management facilities.
However, there are cases where it might be necessary to add or delete methods
outside the control of the source code manager. An example might be behavior that
Behavior messages
The messages described in this section provide access to the properties and
attributes of a class, including the methods it defines. Behavior messages are
mainly intended to support browsing by allowing developers to perform queries
over class structures.
Class queries
allSubclasses, allSuperclasses, inheritsFrom:, name, subclasses, superclass, symbol,
whichClassIncludesSelector:, withAllSubclasses, withAllSuperclasses
Note: The Blue Book defines all of these messages except symbol and
withAllSuperclasses. We added symbol to provide a platform-independent
message that returned a symbol equal to the class name, and
withAllSuperclasses because it is the obvious complement of withAllSubclasses.
Compiling
compiler
Note: This message returns the compiler that is appropriate for a particular class.
There might be more than one compiler supported by the system, and each
class needs a way to find its own.
Creating instances
basicNew, basicNew:
Note: The standard messages used in Smalltalk to create new instances of classes
are new, basicNew, new:, and basicNew:. These messages are defined in
Behavior in the Blue Book, but this causes them to be inherited by many
Enumeration
allMethodsDo:, methodsDo:, allSubclassesBreadthFirstDo:, allSubclassesDepthFirstDo:,
allSubclassesDo:, allSuperclassesDo:, withAllSubclassesBreadthFirstDo:,
withAllSubclassesDepthFirstDo:, withAllSubclassesDo:, withAllSuperclassesDo:
Note: The Blue Book defines only the messages allSubclassesDo: and
allSuperclassesDo:. These messages do not provide any control over the order
in which classes are traversed, which is important for some applications.
The messages prefixed by with include the receiver class in the classes that
are being enumerated.
Instance accessing
allInstances, basicAllInstances
Method accessing
>>, allSelectors, compiledMethodAt:, compiledMethodAt:ifAbsent:, methodDictionary,
selectors, sourceCodeAt:, sourceCodeAt:ifAbsent:
Method queries
allMethodsNamed:, allMethodsReferencingInstVarName:, allMethodsReferencingLiteral:,
allMethodsSending:, allMethodsSendingAll:, canUnderstand:, hasMethods,
includesSelector:, whichMethodsReferenceInstVarName:, whichMethodsReferenceLiteral:,
whichMethodsSend:, whichMethodsSendAll:, whichMethodsReferenceInstVarName:,
whichMethodsReferenceLiteral:, whichMethodsSend:, whichMethodsSendAll:
Note: The Blue Book only defines the messages canUnderstand:, hasMethods, and
includesSelector:. We decided to provide a richer set of method query
messages so that it would be possible to hide implementation details
without losing functionality.
Class messages
The class Class describes the representation and behavior of objects.
Note: These are all Blue Book messages except setClassPool:. The message
setClassPool: is a basic accessor that works outside the source code manager.
Note: These are all Blue Book messages. Note that it is not possible to add
instance variables to variable byte classes.
Tip: Using setSharedPoolNames: will make the image inconsistent with the source
code management system. This method should only be sent by experienced
developers who are certain of the outcome.
Class accessing
comment, comment:, definitionString, setClassName:
Note: The message setClassName: is a basic accessor that works outside the source
code manager. No renaming messages aside from setClassName: are included
in this release of CLIM. Under normal circumstances renaming should be
under the control of the source code manager.
Tip: Using setClassName: will make the image inconsistent with the source code
management system. This method should only be sent by experienced
developers who are certain of the outcome.
Note: The message removeFromSystem is integrated with the source code manager,
and therefore safe to use.
Superclass accessing
connectToSuper, disconnectFromSuper
Note: These messages both operate outside the scope of the source code manager.
Metaclass messages
The class Metaclass provides protocol for accessing the metaclasss primary
instance.
Accessing
primaryInstance, primaryInstance:
Note: Needed to access the class that is the primary instance of the metaclass.
A few restrictions apply when creating new classes. Any subclass of a variable
class, that is, a class like Array that has indexed instance variables, must also be a
variable class. Similarly, any subclass of a variable byte class, that is, a class like
String that has indexed instance variables that are bytes, must also be a variable
byte class. Variable byte classes cannot have named instance variables, and any
class with a named instance variable cannot have variable byte subclasses.
We refer to a class that has no indexed instance variables, that is, only named
instance variables, as a fixed class.
Porting tip: Smalltalk/V supports the second message, but not the first, while
Objectworks\Smalltalk supports neither. Neither Smalltalk/V nor
Objectworks\Smalltalk provide Class messages for creating classes with
class instance variables, although it is possible to build such classes.
Objectworks\Smalltalk uses different subclass creation messages that
also specify the category.
Note: These messages can only be sent to a fixed class or another variable class.
Porting tip: Smalltalk/V supports the second message, but not the first, while
Objectworks\Smalltalk supports neither. Neither Smalltalk/V nor
Objectworks\Smalltalk provide Class messages for creating classes with
class instance variables, although it is possible to build such classes.
Objectworks\Smalltalk uses different subclass creation messages that
also specify the category.
Note: These messages can only be sent to another variable byte class.
Porting tip: Smalltalk/V supports the second message, but not the first, while
Objectworks\Smalltalk supports neither. Neither Smalltalk/V nor
Objectworks\Smalltalk provide Class messages for creating classes with
class instance variables, although it is possible to build such classes.
Objectworks\Smalltalk uses different subclass creation messages that
also specify the category.
Extended classes
CLIM extends the classes Array, String, and DBstring.
basicMultiAllInstances, MultiAllInstances
Note: Neither method is in the Blue Book, but they have been included in CLIM
as an efficient way of accessing multiple instances.
String converting
The following methods are String and DBString instance methods.
Note: The messages asClassPoolKey, asGlobalKey, and asPoolKey are not in the Blue
Book. They have been added to IBM Smalltalk to provide developers with a
way to ensure that class variables, global variables, and pool dictionary keys
have the class that is correct for the platform (usually either String or
Symbol). IBM Smalltalk will not accept class variables, global variables, or
pool dictionary keys containing characters whose value exceeds 255;
consequently asClassPoolKey, asGlobalKey, and asPoolKey messages will not be
useful if sent to a DBString that contains these characters.
A key question is how to handle compile errors. CLIM defines an object that
encapsulates all of the error information that a compiler might return. A
CompilerError is required to support the following accessor messages, each of which
returns one of the attributes of the error object:
context Answers the class in whose context the source code was compiled
message
Answers the compiler error or warning message
sourceString
Answers the source code string that was compiled
startPosition
Answers the position in the source code string of the first character of the
token at which the error or warning was detected
Note that CLIM does not define how the CompilerError is created, or how its
attributes are set. These are assumed to be platform-specific operations. This
mechanism was chosen because it provides a fairly general way to handle
warnings and errors, but does not require a complex runtime infrastructure.
Compiling
compile:inClass:, compile:inClass:ifFail, compile:inClass:warningLevel:onWarning:ifFail:
Note: Each of these messages compiles the source code provided in the first
parameter in the context of the second parameter, which must be a class. To
compile in the context of a class means that the compiler will try to interpret
variable references as instance, class, or pool variables of the designated
class. These messages return a CompiledMethod if they are successful. The
compile:inClass: message uses default error handling that returns nil if a
warning or error is detected. The compile:inClass:ifFail: message accepts a fail
block as the third parameter, and answers the result of evaluating the fail
block if a warning or error is detected. The
compile:inClass:warningLevel:onWarning:ifFail: message accepts a warning
level, warning block, and fail block as parameters. If an error is detected, the
result of evaluating the fail block as answered. If a warning is detected, the
warning block is evaluated with an instance of CompilerError. If that answers
true, the compile fails, and the CompilerError is answered. Otherwise the
compile continues.
Evaluating
evaluate:, evaluate:for:, evaluate:for:ifFail:, evaluate:for:warningLevel:onWarning:ifFail:
Note: Each of these messages evaluates source code in the context of an object. To
evaluate in the context of an object means that the compiler will try to interpret
variable references as instance, class, or pool variables of the designated
objects class, and that instance variable references is bound to the values of
the objects instance variables when the compiled code is run. The first
parameter of each of these messages is always the source code to be
evaluated. The message evaluate: evaluates in the context of the special object
CompiledMethod
A CompiledMethod is an abstraction for compiled code. CompiledMethod is described
in the Blue Book, but no protocol is actually defined for them. The exact
representation of a compiled method is, of necessity, platform-specific. CLIM
defines only those basic accessing and testing messages that do not need to make
any assumptions about structure or representation.
Accessing
methodClass, methodClass:, selector, selector:, sourceString, symbolLiterals
Testing
equals:, isPrimitive, isUserPrimitive, referencesLiteral:, sendsSelector:, getsInstVar:,
setsInstVar:, referencesInstVar:
EmSystemConfiguration
Each IBM Smalltalk system includes a single instance of the class
EmSystemConfiguration, accessed through the global variable System. System serves
two functions: It provides access to useful information about how the image is
configured, and it also accepts messages to save the image and shut down the
system.
The current system configuration can be obtained by sending System the message
configuredSubsystems. This returns a dictionary showing which variant of each IBM
Smalltalk subsystem is configured, keyed by the subsystem name. The keys must
include one of the following strings: CLDT, CLIM, CPM, CFS, CG, CW, or
CP.
Typical values for each subsystem key are shown in the following table. This table
might not be complete. Other values might be supported in subsequent releases.
Table 6. Typical values for subsystem keys
Key Typical values
CLDT ES
CLIM ES
If a platform is not supported, the value for its key is UNSUPPORTED. If any
subsystem is not configured, the value for its key is nil. This might be the case, for
example, for a packaged Smalltalk system that has no file subsystem loaded. The
message setSubsystemType:to: can be used to set or change the system configuration.
The first parameter is the name of the subsystem, which must be one of the
subsystem key strings listed above. The second parameter is one of the allowed
values for the subsystem, or nil, in which case the image has no configuration for
that subsystem.
Other accessing messages supported are copyright and vmType. The message
copyright answers the systems copyright message, while vmType answers the
virtual machine type for the system.
Please refer to The user interface process model on page 225 (in this book) for a
discussion of the input event processing model on which the Common Widgets
user interface is based.
CPM supports the running of multiple independent processes using four classes
named Process, ProcessorScheduler, Delay, and Semaphore. An instance of Process
represents a sequence of message sends that has an independent thread of
execution. A unique instance of the class ProcessorScheduler, the global variable
Processor, is responsible for scheduling these processes for execution by the virtual
machine. Instances of Semaphore class provide a mechanism for process
synchronization and (indirectly) communication. The class Delay enables processes
to synchronize with the real-time clock and to control their execution with fixed
time delays.
Creating a process
A process is normally created by sending the fork or forkAt: message to a block as
shown in the following example. This process is immediately scheduled for
execution. The fork message schedules the new process at the same priority as the
process running the fork, while forkAt: allows the priority to be explicitly specified.
When the new process runs, it evaluates the block. A reference to the new process
can be retained by assigning it to an instance or temporary variable.
| process |
process := [Transcript show: 'The rain in Spain'; cr] fork.
Protocol also exists for creating a process and passing its creation block a set of
arguments as shown in the following example. Here a string value is passed to a
one-argument block. The number of arguments passed to the block must match the
number of formal block parameters.
| process |
process := [:value | Transcript show: value; cr]
newProcessWith: #('The rain in Spain').
process resume.
At that point, the scheduling algorithm ensures that the highest priority process
that is ready to run next. If there is more than one process satisfying this criterion,
then the process that has been waiting longest is selected for execution.
Consequently, the order in which processes run can be influenced by changing the
priority assigned to a process. There are seven preassigned priority levels. The
priority constants in increasing order are as follows:
1. systemBackgroundPriority
The priority of a system background process
2. userBackgroundPriority
The priority of a user background process
3. userSchedulingPriority
The priority of the user interface process (also the default priority of any
process forked by the user interface process)
4. userInterruptPriority
The priority of any process forked by the user interface that should be run
immediately
5. lowIOPriority
The usual priority for input/output processes
6. highIOPriority
The priority of the process monitoring the local network devices
7. timingPriority
The priority of the process monitoring the real-time clock
On many platforms the priorities are actually mapped to the integers 1 through 7;
however, it is poor programming style to reference the priorities directly as integral
values. The preferred approach is to use the priority accessors provided by
ProcessorScheduler. You can query a process priority and modify its value as
required. In the next example, the priority and priority: messages are used to query
and modify the priority of the process. Note, however, that if a priority is changed,
the change has no effect on scheduling until the next process switch.
aSemaphore signal.
output nextPutAll: 'After First signal, count = ', count printString; cr.
aSemaphore signal.
output nextPutAll: 'After Second signal, count = ', count printString; cr.
Transcript show: output contents
Transcript Output:
Instances of class Delay are used to effect synchronization with the real-time clock,
and to postpone process execution for a specified time period. Postponement can
be specified in milliseconds, seconds, or until a specific time. Each Delay has a
resumption time specified in milliseconds since midnight. When the Delay is sent the
wait message, the process running the wait operation is suspended until the
real-time clock advances to the resumption time specified by the Delay.
To illustrate the use of Delay, evaluate the following code fragment. A message is
displayed on the Transcript after about 15 seconds.
[(Delay forSeconds: 15) wait.
Transcript show: 'Executing delay example'; cr] fork.
Protocol synopsis
The principle classes and methods in CPM are summarized below.
Tip: These additional Block methods allow a block to be evaluated, with or without
arguments, and the result of the last statement answered. anObject,
anotherObject, and thirdObject parameters refer to the first, second, and third
parameters of the method.
Process methods
The following instance methods pertain to processes:
priority
Answers the receivers priority.
priority:
Sets the receivers priority to be the specified value.
ProcessorScheduler methods
These messages are sent to the Processor global variable.
Stream protocols
The Common File System also extends POSIX.1 to provide file stream protocols
that conform to and extend those specified by the Blue Book. These protocols
provide simplified buffered input and output, and are designed to cooperate with
and complement the basic file protocols. In fact, the stream protocols themselves
are implemented portably by using the base file protocols.
Portability protocols
The Common File System also provides protocols to deal with unavoidable
platform-specific attributes, such as line delimiters, path separators, file system
roots, and startup directory, in a portable manner. Extensions to POSIX.1 are also
provided for the following:
v Copying
v Suppressing platform error dialogs
v Obtaining volume information: caseSensitive, preservesCase, maximumFileLength,
volumeName, and volumeType.
Basic classes
CfsVolumeInfo
The CfsVolumeInfo class enables you to obtain information about a volume.
CfsDirectoryDescriptor
The CfsDirectoryDescriptor class provides services for creating, removing, and
querying directories.
CfsDirectoryEntry
The CfsDirectoryEntry class represents the information stored in the directory entry
for a particular file.
CfsFileDescriptor
The CfsFileDescriptor class provides services for creating, removing, querying,
reading, and writing files. This includes position control and locking services.
CfsStat
The CfsStat class provides a mechanism for obtaining statistical information (file
size, file type, modification date) about files and directories.
CfsError
The CfsError class provides a mechanism for obtaining error information pertaining
to file stream creation, file, directory, and file stat operations.
The CfsVolumeInfo class can be used to portably determine file name length and
case sensitivity for a specific volume. See Obtaining volume information on
page 57.
All CFS protocols support the use of DBStrings as file names in locales that
support the use of double-byte characters (characters whose values range from 0 to
65535). The limitations on file name length imposed by many platforms are often
determined by the number of bytes in the file name rather than the number of
characters. Because each character in a DBString can require either one or two
bytes of storage, a DBStringcontaining eight characters is not necessarily a valid file
name in any given operating system. The use of file names containing double-byte
characters is not portable.
Path separators
The path separator used for specifying file names is dependent on the underlying
file system. Applications can obtain the path separator by sending either the
pathSeparator or pathSeparatorString messages to CfsDirectoryDescriptor class, which
answers the path separator as a character, and as a string, respectively. Both have
their uses, as demonstrated here:
"Check if a path ends with a path separator"
|'/usr/local/bin/' last = CfsDirectoryDescriptor pathSeparator.
Note: Deleting the current working directory can result in unrecoverable errors
due to platform operating system behavior. Platforms with drives demand
special caution, because they usually maintain a separate working directory
for each drive. Changing the current working directory to another drive
before deleting the old working directory will not necessarily prevent an
error from occurring, because the current working directory associated with
the original drive can remain unchanged. Instead, change the current
working directory to the root of the drive containing the directory you want
to delete.
Deleting files
Delete unwanted files by sending the remove: message to the CfsFileDescriptor class,
as in the following example:
CfsFileDescriptor remove: 'unwanted.fil'.
Renaming files
Rename a file by sending the rename:new: message to the CfsFileDescriptor class, as
in the following example:
CfsFileDescriptor rename: 'oldname.txt' new: 'newname.txt'.
Note: If supported by the platform file system, rename:new: can also be used to
rename directories or move files between directories on the same drive or
device.
Copying files
A file can be copied by sending the copy:new: message to the CfsFileDescriptor class.
An example is shown below.
CfsFileDescriptor copy: 'filename.txt' new: 'filename.bak'.
Tip: Wherever possible, the operating system is only queried if and when a
message is sent, and the answered value is cached for later queries to the
same instance.
File names should be composed of the POSIX.1 portable file name characters,
which are listed in Portable file names on page 54.
The following example forces a file name to conform to the file name length and
case of the volume specified by path. For further information on the
systemErrorDialog: method, see Suppressing system error dialogs on page 73.
"Modify the file name for the file about to be saved to path,
ignoring all errors, including drive not ready."
| filename path state volumeInfo newFilename |
filename := 'aVeryLongFilenameWhichWontFitOnSomeSystems.Text'.
path := 'a:\workdir'.
Searching directories
The CfsDirectoryDescriptor class provides common protocols for reading the
contents of file system directories. It also supports pattern matching by name and
type.
The second argument is a pattern for matching entry names. The $* wildcard
character matches 0 or more characters, the $? wildcard character matches any
single character. A pattern of * always matches all files, regardless of platform.
The third argument specifies the type of files to match. It is a logical inclusive-OR
of one or more of the following constants, which are defined in the CfsConstants
Using readdir
Sending the readdir message to a CfsDirectoryDescriptor instance causes the instance
to answer a new CfsDirectoryEntry instance representing the next directory entry
matching the specified pattern and type. A CfsDirectoryEntry instance understands
the same messages as a CfsStat instance, plus the dName message, which answers
the name of the file represented by the directory entry as a string. See Testing
existence and obtaining other file properties on page 74 for a description of the
messages understood by a CfsStat instance.
When there are no more directory entries to be read, the readdir message answers
nil. An example of the use of readdir follows:
"Answer a collection containing directory entries for all regular files
and directories in 'c:\'"
| collection dd de |
Using readdir:
The readdir: message is identical in behavior to readdir, except that instead of
creating a new CfsDirectoryEntry instance, it stores the directory information in the
CfsDirectoryEntry instance supplied as an argument. This technique provides better
performance than creating and discarding a directory entry for each item in the
directory. An example follows:
"Print all regular files and directories in the current working
directory to the transcript"
| dd de |
Using readdirName
Sending the readdirName message to a CfsDirectoryDescriptor instance answers a
string that is the name of the next directory entry matching the specified pattern
and type. If there are no more entries, it answers nil.
If only the file names are required, using readdirName is more efficient than other
techniques. Two examples are shown below.
"Print the names of all files in the current working directory to the
Transcript"
| dd name |
(dd := CfsDirectoryDescriptor
opendir: '.'
pattern: '*'
mode: FREG | FDIR | FSPECIAL) isCfsError
ifTrue: [|self error: dd message].
[(name := dd readdirName) isNil]
whileFalse: [Transcript cr; show: name].
dd closedir.
"Count the number of files ending in '.new' in the subdirectory called
'work' that is located under the current working directory and
answer the result"
| dd count |
(dd := CfsDirectoryDescriptor
Tip: For maximum efficiency when using readdirName, ensure that the mode
argument is the inclusive-OR of FREG, FDIR and FSPECIAL. Specifying that
all file types are to be matched eliminates an operating system call to
determine the file type, which would otherwise be needed on some platforms.
CfsReadFileStream
This file stream class provides input services only and corresponds to the POSIX.1
ORDONLY access mode.
CfsWriteFileStream
This file stream class provides output services only and corresponds to the POSIX.1
OWRONLY access mode.
CfsReadWriteFileStream
This file stream class provides both input and output services, and corresponds to
the POSIX.1 ORDWR access mode.
CfsFileStream
This is an abstract class used to create streams on existing open file descriptors,
and to enable you to specify POSIX.1 open modes and flags directly when creating
a new file stream. These operations are descripted in detail in Mixing streams and
file descriptors on page 67.
Generally, open: is used when reading files and openEmpty: is used when writing
new files or overwriting existing files. Some examples follow.
| file |
"Opens an existing file for reading and writing, creates the file if it
does not exist."
(file := CfsReadWriteFileStream open: 'existing.txt') isCfsError
ifTrue: [|self error: file message].
"...Use the file stream for reading and/or writing..."
file close.
| file |
"Opens an existing file for reading only, fails if it does not exist"
(file := CfsReadFileStream open: 'existing.txt') isCfsError
ifTrue: [|self error: file message].
"...Use the file stream for reading..."
file close. "When done, close the file stream, which closes the file."
| file |
"Opens a new file for writing only, truncates it to size 0 if it already exists."
(file := CfsWriteFileStream openEmpty: 'new.txt') isCfsError
ifTrue: [|self error: file message].
"...Use the file stream for writing..."
file close.
Once all desired operations have been performed, the file stream instance must be
closed by sending it the close message before it is discarded. This closes the file, by
deallocating any operating system resources associated with the file stream, and
flushing any cached data to disk.
In these cases, it is important to use the isCfsError message to test the result of the
open: and openEmpty: operations rather than testing the class of the returned object
(for example, returnedObject class == CfsReadFileStream).
When used with file streams, the copyFrom:to:, position, position:, size, and
skip: messages operate based upon byte indexes and sizes, not character and
sizes. The other file stream operations operate in the same manner as the
CLDT Stream protocols. For portability, use the next and next: protocols for
stream positioning.
Additionally, you can change the type of objects answered by file stream
operations such as contents, ext, nextLine, upTo:, and upToAll: by using the isBytes:
and isCharacters: messages to specify the type of data that is being streamed over,
as shown in the following example:
"Open a text file for reading"
| file contents |
file := CfsReadFileStream open: 'readme.txt'.
file isCharacters: true.
"We are streaming over characters, answer file contents as characters"
contents := file contents.
file close.
|contents
"Open a bitmap image file for reading"
| file contents |
file := CfsReadFileStream open: 'looknice.bmp'.
file isBytes: true.
"We are streaming over bytes, answer file contents as bytes"
contents := file contents.
file close.
|contents
The data type being used by a stream can be determined using the Boolean
messages isBytes and isCharacters. These messages are opposites; exactly one
returns true at any given time.
Tip: Care must be taken when using Stream methods position, position:, size, and
copyFrom:to: with CfsFileStreams on a double-byte locale, because these are
answered in bytes and not in characters.
Line delimiters
By default, a stream uses the platform file systems line delimiter for operations
such as cr and nextLine. A string representing the current line delimiter can be
obtained by sending the lineDelimiter message to a file stream instance, and can be
changed to an arbitrary string by sending the lineDelimiter: message. This makes it
The following example demonstrates the use of the lineDelimiter: message and line
delimiter constants, as well as their effect on the cr message:
| file |
file := CfsReadWriteFileStream openEmpty: 'testing.txt'.
Tip: Use upToAll: and skipToAll: to scan up to various special character sequences.
This is better than changing the line delimiter to the special sequence with
lineDelimiter: and then using nextLine.
Opening files
A file is opened by sending the open:oflag: message to the CfsFileDescriptor class,
which answers a new CfsFileDescriptor instance to act as the receiver for I/O
operations to that file. The first argument is a String or DBString specifying the
path of the file to be opened; the second is an integer specifying the inclusive-OR
of exactly one access mode and zero or more open flags. Access modes and flags
are defined in the CfsConstants pool dictionary.
Some examples of the use of access modes and open flags follow:
| fd |
"Opens an existing file for reading, fails if it does not exist"
(fd := CfsFileDescriptor
open: 'exists.txt'
oflag: ORDONLY) isCfsError
ifTrue: [|self error: fd message].
fd close.
| fd |
"Opens a new file for writing, fails if it exists"
(fd := CfsFileDescriptor
open: 'new.txt'
oflag: OWRONLY | OCREAT | OEXCL) isCfsError
ifTrue: [|self error: fd message].
fd close.
| fd |
"Opens an existing file for reading and writing,
or creates it if it does not exist"
(fd := CfsFileDescriptor
open: 'log.txt'
oflag: ORDWR | OCREAT) isCfsError
ifTrue: [|self error: fd message].
fd close.
| fd |
"Ensures that the opened file is empty: creates if it does not exist,
truncate to size 0 if it does."
(fd := CfsFileDescriptor
open: 'empty.txt'
oflag: ORDWR | OCREAT | OTRUNC) isCfsError
ifTrue: [|self error: fd message].
fd close.
Note: The startingAt parameter refers to the position in the buffer, not in the file.
To change the position in the file (the file offset) use the lseek:whence: message.
"Example of read/write: low level file copy using buffers"
| source target buf bytesRead bufSize |
(source := CfsFileDescriptor
open: 'example.txt'
oflag: ORDONLY) isCfsError
ifTrue: [|self error: source message].
(target := CfsFileDescriptor
open: 'example.bak'
oflag: OWRONLY | OCREAT | OTRUNC) isCfsError
ifTrue: [|self error: target message].
"Choose a reasonable size"
bufSize := 4096.
"Could also use a ByteArray"
buf := String new: bufSize.
[(bytesRead := source read: buf startingAt: 1 nbyte: bufSize) > 0]
whileTrue: [
bytesRead > (target write: buf startingAt: 1 nbyte: bytesRead)
ifTrue: [
"Fewer bytes written than requested - might indicate full
file system"
source close.
target close.
|self error: 'Unable to copy file. File system could be full.']].
source close.
target close.
The on: message is also useful if the file is to be opened with a special share mode,
using the open:oflag:share: message, which was discussed in File locking on page
68. An example follows:
| file fd |
(fd := CfsFileDescriptor
open: 'a.fil'
oflag: ORDWR
share: ODENYRDWR) isCfsError
ifTrue: [|self error: fd message].
file := CfsFileStream on: fd.
file close.
"Unlock it"
fileStream fileDescriptor unlock: FMDLOCK start: 10 len: 6.
File locking
There are two general locking schemes, advisory locking and mandatory locking.
Advisory locks have no effect on clients that do not explicitly check for a lock.
Mandatory locks do not require clients to explicitly check for locks, but represent a
security risk because it is possible to interfere with the normal operation of a
system by placing mandatory locks on essential files. For this reason mandatory
The locking constants in the CfsConstants pool dictionary appear in the following
table:
Table 12. File locking constants
Pool variable Description
FRDLOCK Specifies a shared (read) advisory lock. A shared advisory lock prevents
any other client from setting an exclusive advisory lock on any portion
of the protected area. Noncooperating clients can read or write in
protected areas.
FWRLOCK Specifies an exclusive (write) advisory lock. An exclusive advisory lock
prevents any other client from setting a shared or exclusive advisory
lock on any portion of the protected area. Noncooperating clients can
read or write in protected areas.
FMDLOCK Specifies an exclusive mandatory lock. An exclusive mandatory lock
prevents any other client from reading, writing, or locking any portion
of the protected area.
Tip: Locking might have no effect unless the file is being accessed by what the
operating system considers to be distinct processes. With some network
software, locking can only be effective if the file is being accessed from
different machines. These are limitations in the services provided by the
operating system or network.
When releasing a lock, the value of the lock type, start, and length arguments must
be exactly the same as those used to set the lock. Otherwise the unlock operation
can fail or have unpredictable behavior. Using the size message when releasing a
lock as shown in the previous example is dangerous, because the size might have
changed since the lock was set. Instead, save the parameters used when locking
and reuse these values when releasing the lock. An example follows:
| fd len |
(fd := CfsFileDescriptor
open: 'lockable.fil'
oflag: ORDWR | OCREAT | OTRUNC) isCfsError
ifTrue: [|self error: fd message].
fd write: 'This is a TEST LOCK area' startingAt: 1 nbyte: 24.
fd lock: FMDLOCK start: 0 len: (len := fd size).
fd unlock: FMDLOCK start: 0 len: len.
fd close.
The effect of locking overlapping regions or releasing locks that have not been set
is platform-specific. It is recommended that all locks be explicitly released before
closing the file.
Share modes
The Common File System supports four share modes for opening files. The
constants used to specify these modes appear in the following table. These
constants are defined in the CfsConstants pool dictionary.
Table 13. Share mode constants
Pool variable Description
ODENYNONE Other processes can open the file for any access: read-only, write-only,
or read-write.
ODENYRD Other processes can open the file for write-only access, but they cannot
open it for read-only or read-write access.
ODENYWR Other processes can open the file for read-only access, but they cannot
open it for write-only or read-write access.
ODENYRDWR The current process has exclusive access to the file. Other processes
cannot open the file. It is unspecified whether the file can be opened by
the current process.
Tip: Like locking, share modes might have no effect unless the file is being
accessed by what the operating system considers to be a distinct process. With
some network software, it can only be effective if the file is being accessed
from a different machine.
Handling errors
The CfsError class provides a common mechanism for obtaining error information
pertaining to file system operations. If an error prevents the successful completion
of a Common File System operation, the affected method will answer an instance
of CfsError, rather than its normal return value. The CfsError instance contains
information pertaining to the cause of the error.
Errors are detected by sending the isCfsError message to the result returned by any
low-level file system operation, CfsStat operation, or file stream class method such
as open: or openEmpty:. Instances of CfsError are the only objects that answer true to
this message, all other objects answer false.
File stream instance methods in Common File System handle errors in the same
manner as the Stream class in CLDT. If errors are expected when streaming over a
file, set up an exception handler as described in the section on Handling of
exceptions on page 26.
Printing a CfsError instance using the printString or printOn: message will display
all of the information described above.
The specific errors that can be returned by a method are listed in the method
comment. No other errors are returned. Following is an example of testing for a
specific error that prompts to overwrite existing file:
"Open a new file, prompt for overwrite if it exists"
| fd |
When testing for a specific error, compare the CfsError instances errno against the
error constants in the CfsConstants pool dictionary. All application code and
documentation should refer to errors by identifier, for example, ENOENT, rather
than by number, because error constant values can vary from platform to platform.
Tip: To obtain the exact size of a file, send size to an open file descriptor rather
than using stat:, because CfsFileDescriptor>>#size reflects the true size of the
file based on the end-of-file position as of last write, while CfsStat>>#size
obtains the amount of storage space allocated to the file by reading the files
directory entry. For this reason, the size obtained by sending size to a CfsState
or CfsDirectoryEntry might not match the logical size of the file, and might not
be up-to-date if the file is currently open, even if a flush was just performed
on the file descriptor.
The following messages can be sent to a CfsStat instance. The messages always
answer a meaningful value, because all platforms support the information
associated with them.
isBlk Answers true if the receiver is reporting statistics for a block special file.
Otherwise answers false.
isChr Answers true if the receiver is reporting statistics for a character special
file. Otherwise answers false.
isDir Answers true if the receiver is reporting statistics for a directory. Otherwise
answers false.
isFifo Answers true if the receiver is reporting statistics for a pipe or for a FIFO
special file. Otherwise answers false.
isReg Answers true if the receiver is reporting statistics for a regular file.
Otherwise answers false.
isSpecial
Answers true if the receiver is reporting statistics for a special file.
Otherwise answers false. A special file is any file that is neither a regular
file nor a directory.
stat: Obtains statistics for the specified file and stores them in the receiver.
By combining the stat: message with error checking, it is possible to test for file
existence and other special properties. Some examples follow.
"Check file existence: Answer true if path specifies an existing file of any type"
|(CfsStat stat: path) isCfsError not
"Check if a path name is already being used"
"Answer true if path specifies an existing file of any type"
"Customized to consider inaccessible entries to be existing files"
| result |
result := CfsStat stat: path.
result isCfsError
ifTrue: [
result errno = EACCES ifTrue: [|true].
|false]
ifFalse: [|true].
Tip: Do not use stat: to try to prevent errors by testing whether a file exists before
opening it. It is better to catch the fail case from the open operation, because
the file can be deleted in the time between the existence test and opening the
file.
Tip: Mixing Common File System and platform-specific operations should only be
done as a last resort when no satisfactory solution exists using portable
Common File System operations. Use these facilities with extreme caution.
These operations should only be used by experts, because there are hidden pitfalls.
Some platforms can have multiple valid file descriptor representations, thus some
calls might not be compatible with the particular representation chosen by the
CfsFileDescriptor. Furthermore, on such platforms, the possibility exists that the
underlying representation might change.
Some platforms can have more than one type of file handle. Ensure that the
platform-specific file descriptor specified as the argument to on: is of the same type
as that used by the Common File System itself.
This document first describes the core Common Graphics classes and later
describes the Image Support classes.
The core Common Graphics classes and methods enable programmers to do the
following:
v Define drawing attributes, such as line widths or styles, through the use of
graphics contexts
v Perform drawing operations, such as drawing lines, arcs, text strings, and
polygons
v Manipulate fonts
v Manipulate two-color bitmaps and multicolor pixmaps
This chapter describes how the core system is based on the X Window Systems
graphics library, shows the class hierarchy, and explains the basic process for
building an application. It summarizes resource management, describing when and
how to free graphics resources that have been allocated.
Classes in the Common Graphics hierarchy are prefixed by the letters Cg; for
example, CgRGBColor or CgFont.
This section will be of interest to developers familiar with Xlib graphic operations,
as it describes the two main strategies that were used to convert the standard Xlib
C calls to Smalltalk classes and methods. By understanding these two principles,
experienced Xlib software engineers should be able to use the Common Graphics
subsystem relatively quickly.
First, objects have been created for all C data types. These data types include items
such as Display, Drawable, GC, and Font. These objects were named by prefixing
the Xlib data type name with Cg and removing any leading X. For example, the
Xlib type Display becomes the Smalltalk class CgDisplay.
In the Common Graphics subsystem, this function has been converted to the
following method:
CgDrawable>>#drawRectangle: gc x: x y: y width: width height: height.
The following figure illustrates the most frequently used core Common Graphics
classes.
A CgWindow object represents an area of the screen, usually the visible area of a
user interface widget. Widgets are created by the application using the Common
Widgets subsystem (see the Common Widgets chapter). Each widget has a
corresponding window, which is obtained by sending the window message to the
widget. In addition to the windows of widgets, each screen has a root window
covering the entire area of the screen. All other windows are layered on top of the
Note: It is important to note that CgWindow forms a link between the Common
Graphics and Common Widgets subsystems of IBM Smalltalk. A CwWidget
cannot be drawn on directly. However, every CwWidget has a corresponding
CgWindow that can be used for drawing. That is, CgWindows are also
CgDrawables. This is obtained by sending the window message to the
CwWidget. For further information, see Drawing area widgets on page 164
in the Common Widgets chapter.
A CgPixmap object represents an off-screen monochrome or color bitmapped image
created by your application. The format of pixmap memory is device dependent
and is not directly accessible to the application. Pixmaps are used to specify stipple
and tile pattern drawing attributes. They are also often used to cache the results of
drawing for high speed display. A pixmap is displayed by copying a rectangular
area of its contents to a window. Your application is responsible for creating,
manipulating, and freeing pixmap objects. These operations are described in
Using pixmaps on page 106.
A CgGC object (also called a GC or Graphics Context) is a data type that contains
drawing attribute information. The GC manages attributes such as the current
foreground color, line width, and line style. Requests to change the value of an
attribute are made to the GC. Your application is responsible for creating,
manipulating, and freeing GC objects. In general, applications will create their own
GCs. However, each screen has a default GC that is useful for testing purposes.
You can obtain the default GC of the default screen using CgGC default.
Each of these objects plays an important role in the Common Graphics subsystem.
Before drawing
v Load any required fonts.
v Create one or more graphics contexts.
v Create any required pixmaps.
During drawing
v Set attributes in the graphics context; for example, line width, font.
v Issue the drawing operation request; for example, drawString, fillRectangle.
After drawing
v Unload any fonts that were loaded.
v Free any graphics contexts that were created.
v Free any pixmaps that were created.
The above process does not address the use of color. The sections on the Image
Support classes discuss the use of color. The remaining sections and examples for
the core classes assume drawing is done using black and white.
Normally, an application creates and uses at least one GC. Your application can
create and use many graphics contexts, however. You should use multiple graphics
contexts carefully as they are relatively expensive to build and maintain.
For optimum performance, you should draw as much as possible with the same
GC, minimizing the number of times you change its components. The cost of
changing GC components relative to using different GCs depends on the specific
platform because some servers cache a limited number of GCs in the display
hardware.
In the following figure, a drawable (the root window of the default screen) receives
a message to draw a 100x100 filled rectangle at 20@20. The programmer provides a
graphics context called aGC. The drawable references aGC to find out how to
display the rectangle.
The graphics context called aGC Smalltalk message sent by your application
lineWidth 2 pixels
lineStyle LineSolid
foreground black
color
background white
color
fillStyle FillSolid Draws rectangle
. .
. .
. .
In the following example, a CgGCValues object is created, the function: message sets
the desired raster operation (GXhighlight), and the lineWidth: message sets a line
width value (2 pixels) in the CgGCValues object. The CgGCValues object is then
passed to the drawable using the createGC:values: message. The valuemasks
GCFunction and GCLineWidth are ORed together to create a mask that identifies
which attributes are being modified. The gcValues variable contains the new values
for the attributes.
| gc gcValues |
gcValues := CgGCValues new
function: GXhighlight;
lineWidth: 2.
gc := CgWindow default
createGC: GCFunction | GCLineWidth
values: gcValues.
LineDoubleDash (fore-
and background color)
GCCapStyle specifies how a CapNotLast CapButt
line should be ended CapButt
CapRound
CapProjecting
CGJoinStyle specifies how two JoinMiter JoinMiter
lines should be joined
JoinRound
JoinBevel
The following table lists frequently used convenience methods that can be sent to a
graphics context.
Table 17. Graphics context convenience (set) methods
Method Values modified Example
setBackground: Background color gc setBackground:
CgWindow default whitePixel
setDashes: Dash offset and pattern gc setDashes: 0 dashList: #(100 20)
dashList:
setForeground: Foreground color gc setForeground:
CgWindow default blackPixel
setFunction: Raster operation gc setFunction: GXhighlight
setLineAttributes: Width, style, endcap style, and gc
lineStyle: join style of a line, all at once setLineAttributes: 2
capStyle: lineStyle: LineOnOffDash
joinStyle: capStyle: CapButt
joinStyle: JoinMiter
setState: Foreground and background gc
background: colors, the raster operation setState: CgWindow default blackPixel
function: function, and the planeMask all background: CgWindow default whitePixel
planeMask: at once function: GXcopy
planeMask: nil
gc freeGC.
Tip: Do not attempt to free the default GC. This can cause unpredictable behavior.
Drawing operations
Common Graphics includes methods for drawing points, lines, rectangles,
polygons, arcs, and strings. All drawing methods are sent to a CgDrawableeither
a window or a pixmap. The first keyword in the method describes the function of
the method. The first argument passed to the method is always a graphics context.
The first keywords of the CG drawing methods follow:
drawArc:
drawArcs:
Because the drawString:, drawImageString:, and drawText: methods require the use of
fonts, they are described in the subsection Using fonts on page 93. The other
drawing methods are covered in the following subsections:
Drawing points
Two methods are used to draw points. A point is a single pixel in a drawable.
drawPoint:x:y:
Draws a single point
drawPoints:points:mode:
Draws multiple points
The following example draws a point at 20@20 on the default root window using
the default GC.
CgWindow default
drawPoint: CgGC default
x: 20
y: 20.
CgWindow default
drawPoints: CgGC default
points: points
mode: CoordModeOrigin.
Drawing lines
Three methods are used to draw lines. Lines provided in the argument list are
drawn using the attributes (such as color, line style, line width) contained in the
GC provided in the argument list.
drawLine:x1:y1:x2:y2:
Draws a single line.
The following example draws a single line from 20@20 to 50@50 on the default
root window using the default GC.
CgWindow default
drawLine: CgGC default +
x1: 20 +
y1: 20
x2: 50
y2: 50.
The following example draws a series of lines on the root window beginning with
the first point and ending at the last point in the array.
| points |
(points := Array new: 6) +
at: 1 put: 89 @ 100;
at: 2 put: 12 @ 45; + +
at: 3 put: 108 @ 45;
at: 4 put: 31 @ 100; + +
at: 5 put: 60 @ 10;
at: 6 put: 89 @ 100.
CgWindow default
drawLines: CgGC default
points: points
mode: CoordModeOrigin
Drawing rectangles
The drawRectangle:x:y:width:height:, fillRectangle:x:y:width:height:,
drawRectangles:rectangles: and fillRectangles:rectangles: messages are used to draw
outlined and filled rectangles. The fillRectangle: and fillRectangles: messages are
shown in the following example:
CgWindow default
fillRectangle: CgGC default
x: 10 +
y: 10
width: 50 +
height: 50.
CgWindow default
fillRectangles: CgGC default
rectangles: rects.
| points | +
points := Array with: 10@10 with: 10@60
with: 60@35.
CgWindow default +
fillPolygon: CgGC default
points: points
shape: Convex +
mode: CoordModeOrigin.
The mode argument can be set to CoordModeOrigin, so that all points are drawn
relative to the origin, as in the previous example, or CoordModePrevious, so that all
points are drawn relative to the previous point in the collection, as in the following
example:
| points | +
points := Array with: 10@10
with: 10@60 with: 60@35.
CgWindow default
fillPolygon: CgGC default
points: points
+
shape: Convex
mode: CoordModePrevious +
The following example draws an arc bounded by the rectangle (20@20 corner:
60@60). The arc starts at an angle of 45 degrees and extends for another 270
degrees, ending at 315 degrees.
| gc |
gc := CgGC default.
gc setArcMode: ArcPieSlice. +
CgWindow default
fillArc: gc
x: 20
y: 20 +
width: 40
height: 40
angle1: 64 * 45
angle2: 64 * 270.
| gc |
gc := CgGC default.
gc setArcMode: ArcChord. +
CgWindow default
fillArc: gc
x: 20
y: 20 +
width: 40
height: 40
angle1: 64 * 45
angle2: 64 * 270.
Using fonts
This section gives an overview of the Common Graphics font system. For more
details, refer to the X11R4 reference manual. For further information on scalable
fonts, refer to the X11R5 reference manual. The following classes comprise the font
system:
CgFont
Represents a loaded font with a given size and style
CgFontStruct
Describes a CgFont. Contains information such as the number of characters
in the font, font height, average character width, ascender height,
descender height, whether the font is monospaced or proportional, and the
dimensions of each character in the font
CgFont
Handle to Your Application
aCgGC
the font
structure aCgWindow
Font: aCgFont drawString: aCgGC
x: 20
y: 100
string: Times
CgCharStruct
Individual
character
definition
CgFontStruct
aCgWindow
Font
attributes:
CgFontProp height, Times
XLFD standard width, #chrs,
and so on
font name
Using fonts
1. Query the system for available fonts (CgDisplay).
2. Load the requested font(s) into memory (CgDisplay).
3. Assign the font for use in drawing operations (CgGC).
4. Perform the drawing operations (CgDrawable).
5. Free the font(s) from memory (CgFont).
The following example illustrates the complete font process described in the steps
above. Each step is described in detail in the subsections following the example.
| fontNames font gc |
"Query the system for the string names of two available fonts"
fontNames := CgDisplay default
listFonts: '*'
maxnames: 2.
"Use the font string name to load a font"
font := CgDisplay default loadFont: (fontNames at: 2).
gc := CgCGdefault
CgWindow default
"Perform a string drawing operation"
drawString: gc
x: 10
y: 100
string: 'hello world'.
"Free the font"
font unloadFont.
Pattern matching
The Common Graphics subsystem defines font names using the X Logical Font
Description (XLFD) convention. This convention is described in detail in the X11R4
and R5 reference manuals. Valid search patterns can be created using any
combination of wildcard (* or ?) or literal values. The * represents a string of
zero or more arbitrary characters. The ? represents a single arbitrary character.
The - is used as a separator between fields in the XLFD name. The following
example shows the meaning of each field in the XLFD name:
-adobe-helvetica-bold-i-normal-sans serif-12-120-75-75-p-70-iso8859-1
The following table shows the sample values for the character set and
registry/encoding fields.
Table 19. Example platform charset_registry/encoding names
Family Name Charset Registry/Encoding Description
courier iso8859 1 courier matches iso8859
specification
symbol microsoft fontspecific microsoft symbol font
@system microsoft @shiftjis microsoft system double-byte
font, double-byte characters
rotated 90 counter-clockwise
terminal microsoft shiftjis microsoft terminal double-byte
font double-byte characters not
rotated
courier ibm 437 ibm courier for code page 437
courier ibm 932.sbcs ibm courier single-byte font for
code page 932
mincho ibm 932 ibm mincho double-byte font
for code page 932 (zero
indicates a scalable font)
The following example finds a maximum of two font strings of any description (*):
| fontNames |
fontNames := CgDisplay default
listFonts: '*'
maxnames: 2.
Tip: The listFonts: method is not required to include scalable fonts in the returned
list unless all XLFD fields are specified in the pattern. To ensure that scalable
fonts are included in the list, specify all 14 XLFD fields as in the previous
example rather than using a single wildcard to match across several fields.
As a general rule, it is best to specify the fonts family name, weight, slant,
and point size fields explicitly.
It is better to match the point size field and leave the pixel field as a wildcard
because this allows the most appropriate match for the resolution of the
display.
Loading fonts
Common Graphics supports both scalable and nonscalable fonts. Scalable fonts are
a variable font definition that can be scaled to different sized fonts. Nonscalable
fonts cannot be scaled and, when loaded, are always the same size. Scalable fonts
are indicated by zeros in the pixel size, point size, and average width fields of the
font name.
To load a scalable font successfully, you must specify the size of the font by
changing the pixel size or the point size field in the font name prior to issuing one
of the font loading methods. Because a nonscalable font has a predetermined size,
its font name is passed directly to the font loading methods without any
preprocessing.
CgLogicalFontDescription class can be used to parse and modify an XLFD name. The
following example determines whether or not a font is scalable by first creating a
CgLogicalFontDescription object for the font.
| fontName description |
"Ask the system for any font name."
fontName := (CgDisplay default
listFonts: '-*-*-*-*-*-*-*-*-*-*-*-*-*-*'
maxnames: 1) first.
Once the scalable font name has been modified to specify a fixed size, it is then
referred to as a scaled font name. It can then be passed to one of the font loading
methods that follow. Fonts are loaded into memory by sending the loadFont: or
loadQueryFont: messages to a CgDisplay object. The loadFont: method takes a font
name and returns a CgFont object. The loadQueryFont: method takes a font name
and returns a CgFontStruct object.
Loading a CgFont
| fontName font |
fontName := '8x13'.
font := CgDisplay default
loadFont: fontName.
Loading a CgFontStruct
| fontName fontStruct |
fontName := '8x13'.
fontStruct := CgDisplay default
loadQueryFont: fontName.
rbearing
lbearing bearing
CgFontStruct CgFontStruct
ascent
CgCharStruct for A
ascent
CgCharStruct for j
ascent
baseline
descent
descent = 0
descent
character
origins
Note that the origin of each character is on the baseline, and not in the upper left.
A CgCharStructs origin is the leftmost pixel along its baseline; therefore, it is
actually at a point that is 0 @ ascent with respect to the upper left corner of the
CgCharStruct. This is useful to know when drawing a string inside of a rectangle
drawn at x @ y. The string must be drawn at x @ (y + ascent). This is in keeping
with typographic convention. The ascent, descent, and width of a CgFontStruct are
equal to the largest CgCharStruct ascent, descent, and width in the font. The
lbearing, rbearing, and bearing metrics are only shown for A. Additional
CgCharStruct instance methods follow:
ascent Returns the number of pixels between the baseline and the top edge of the
raster.
descent Returns the number of pixels between the baseline and the bottom edge of
the raster.
height Returns the number of pixels between the top edge of the raster and the
bottom edge of the raster.
extent Returns a point in which the x-coordinate is the characters height, and the
y-coordinate is the characters width.
bearing Returns the number of pixels between the left edge of the raster and the
right edge of the raster.
lbearing
Returns the number of pixels between the origin and the left edge of the
raster.
rbearing
Returns the number of pixels between the origin and the right edge of the
raster.
width Returns the number of pixels to advance to the next characters origin.
In all string and text drawing methods the y-coordinate specifies the coordinate
where the baseline of the string will be drawn, not the top of the strings bounding
box.
Drawing a string
The drawString:x:y:string: method draws a string at a given point in the foreground
color. Only the pixels for the characters are modified. Pixels between characters are
left unchanged.
CgWindow default
drawString: CgGC default
x: 10 +
y: 100
string: 'Going global'.
CgWindow default
drawImageString: CgGC default
x: 10 +
y: 100
string: 'Hello world'.
CgWindow default
drawText: CgGC default
x: 10
y: 100
items: (Array with: item1 with: item2 with: item3).
Tip: Every font loaded with loadFont: must be unloaded using unloadFont. Every
fontStruct loaded with queryFont: or loadQueryFont: must be freed using
freeFont. The freeFont method also unloads the CgFont object associated with
the CgFontStruct.
Tip: Cursors can only be defined in the window of a widget, not the root window
of the screen. The widget can be any widget and is not restricted to the shell
widget of the application.
The cursor is reset to the default cursor by sending undefineCursor to the window.
When the application is finished using the cursor, it should be released from
memory by sending freeCursor to the cursor. This process is illustrated by the
following code. (This example and the following cursor examples assume that the
shell method answers a CwTopLevelShell widget.)
| window watch |
window := self shell window.
Font cursors
Font cursors are easy to use. They are specified by a constant from the CgConstants
pool dictionary. The previous example used a font cursor called watch:. In the
following example, a coffee mug cursor appears on the screen for 5 seconds:
| window mug |
window := self shell window.
mug := window display createFontCursor: XCCoffeeMug.
The following table lists the available font cursors specified in the CgConstants pool
dictionary:
Table 20. Font cursors
XCArrow XCBasedArrowDown XCBasedArrowUp XCBoat
XCBogosity XCBottomLeftCorner XCBottomRightCorner XCBottomSide
XCBottomTee XCBoxSpiral XCCenterPtr XCCircle
XCClock XCCoffeeMug XCCross XCCrosshair
XCCrossReverse XCDiamondCross XCDot XCDotbox
XCDoubleArrow XCDraftLarge XCDraftSmall XCDrapedBox
XCExchange XCFleur XCGobbler XCGumby
XCHand1 XCHand2 XCHeart XCIcon
XCIronCross XCLeftbutton XCLeftPtr XCLeftSide
XCLeftTee XCLlAngle XCLrAngle XCMan
XCMiddlebutton XCMouse XCNumGlyphs XCPencil
XCPirate XCPlus XCQuestionArrow XCRightbutton
XCRightPtr XCRightSide XCRightTee XCRtlLogo
XCSailboat XCSbDownArrow XCSbHDoubleArrow XCSbLeftArrow
XCSbRightArrow XCSbUpArrow XCSbVDoubleArrow XCShuttle
XCSizing XCSpider XCSpraycan XCStar
XCTarget XCTcross XCTopLeftArrow XCTopLeftCorner
XCTopRightCorner XCTopSide XCTopTee XCTrek
XCUlAngle XCUmbrella XCUrAngle XCWatch
XCXCursor XCXterm
Glyph cursors
Glyph cursors are created by using a character from an application-specified font
as a cursor. To create a glyph cursor, the application must have loaded a CgFont.
Then a cursor object is created by sending the
createGlyphCursor:sourceChar:maskChar: or
createGlyphCursor:sourceChar:maskChar:foregroundColor:backgroundColor: message to
the CgFont object (the source font). In the following example, the same font and
character are used for the mask font and the mask character. The following code
creates and displays the @ sign as a cursor.
The following code creates and displays a pixmap cursor with the shape of a small
black rectangle with a transparent interior. The cursors hot spot is in the center of
the rectangle. Note that, in this case, the same pixmap is used both for the mask
and to define the shape.
Note: Some platforms, such as OS/2 PM and Windows, do not support color
cursors.
Platform cursors
You can map font cursors to correspond to a platforms built-in cursors by sending
mapFontCursor:platformCursor: to a CgDisplay object. In the following example, the
symbol #wait is defined as a key that maps to the current platforms wait cursor.
You can use any object as a key. The cursor is created using createFontCursor: and
displayed in a window. Note that constants from the PlatformConstants pool
dictionary are used.
| window platform key cursor |
window := self shell window.
"Determine what the current platform is and set the constant name
specifying the platform's wait cursor."
platform := System subsystemType: 'CG'.
(#('WIN' 'WIN32s' 'WIN-NT') includes: platform)
ifTrue: [key := 'IdcWait'].
platform = 'PM' ifTrue: [key := 'SptrWait'].
platform = 'X' ifTrue: [key := 'XCWatch'].
"Look up the constant and define a font cursor that maps to the
platform's wait cursor."
window display
mapFontCursor: #wait
platformCursor: (PlatformConstants at: key asPoolKey).
Using pixmaps
A pixmap, represented by class CgPixmap, is an off-screen rectangular area of
pixels, located in graphics server memory. The depth, or number of bits per pixel,
of a pixmap is specified when it is created. The depth is usually either 1 bit or the
depth of the window on which it will be displayed. Pixmaps with a depth of 1,
representing monochrome graphics, are called bitmaps, but are also represented by
class CgPixmap. The memory representation of pixmaps is device dependent and is
hidden from the application programmer. A pixmap is a kind of drawable,
Freeing pixmaps
The application is responsible for releasing pixmaps (including bitmaps) from
memory when they are no longer required. Depending on their dimensions, these
objects can take up large amounts of memory. Pixmaps are freed by sending the
freePixmap message to a CgPixmap object.
pixmap freePixmap
How the bits are displayed is dependent on the raster operation function, either
GXcopy or GXhighlight, that has been set in the graphics context. The raster
operation specifies a binary function that is applied at each pixel, using the current
pixel values of the source and the destination to determine the new pixel value of
the destination.
GXcopy sets the destination pixel value to the source pixel value; the current
destination pixel value is ignored.
GXhighlight functions differently when copying areas than when drawing using the
foreground or background CgGC components. In the latter case the foreground and
background colors are exchanged in the destination. When copying areas,
GXhighlight performs a binary exclusive-OR on the current source and destination
pixel values and sets the destination pixel value to the result. The example in the
following subsection illustrates copying a pixmap.
There are also convenience methods, width, height, and depth, for querying the
geometry of drawables.
Tip: The bitmap file format only supports any CgPixmap of depth one. It is an
error to try to write a CgPixmap with a different depth.
The bitmap is written in X11 bitmap file format, which looks like C source code for
a structure initialization. Parameters passed to the method include the file name,
the width, the height, and the hot spot coordinates within the bitmap. A hot spot is
useful for cursor specifications. The write operation returns zero if successful, or an
error value if unsuccessful.
| bits bitmap error |
bits := #[
2r11111111 2r00000001 2r00000001 2r00000001
2r11111111 2r00010000 2r00010000 2r00010000].
bitmap := CgWindow default
createBitmapFromData: bits
width: 8
height: 8.
error := bitmap
writeBitmapFile: 'brick'
width: 8
height: 8
xHot: 0
yHot: 0.
error = 0
ifTrue: [Transcript cr; show: 'File write successful.']
ifFalse: [Transcript cr; show: 'File write error: ',
error printString].
bitmap freePixmap.
Displaying bitmaps
To display a bitmap, the copyPlane:gc:srcX:srcY:width:height:destX:destY:plane: method
is used instead of copyArea:, which is used to copy between drawables having the
same depth. Like copyArea:, copyPlane: specifies the destination drawable, the
source rectangle, and the destination point. It has an extra argument that specifies
which plane of the source drawable to use. When displaying bitmaps, this
argument should have a value of 1.
Unlike copyArea: which copies pixel values directly between the source and
destination drawables, copyPlane: expands the monochrome source bitmap to color
before performing the copy. Each pixel is copied as follows: If the source pixel
value is 1, the foreground pixel value in the GC is combined with the destination
pixel value using the GC function, and the resulting pixel value is set in the
destination. If the source pixel value is 0, the background pixel value in the GC is
combined with the destination pixel value using the GC function, and the resulting
pixel value is set in the destination.
The following example creates a brick pattern bitmap and copies it to the root
window using black as the foreground color and white as the background color:
| gc window bits bitmap |
gc := CgGC default.
window := CgWindow default.
bits := #[
2r11111111
2r00000001
2r00000001
2r00000001
2r11111111
2r00010000
2r00010000
2r00010000].
Common Graphics Image Support provides higher level abstractions for handling
images, icons, colors, and palettes of colors. In keeping with the design philosophy
of IBM Smalltalk, these abstractions are implemented using the highest level
capabilities applicable on each platform. Image Support provides the following
additional capabilities:
v A color class, which allows the application to describe a color
v Palette classes, which allow the application to describe the set of colors it
requires or the set of colors associated with an image
v A device-independent representation of images, which allows the application to
create, modify, or display an image
Specifying colors
To specify colors, Common Graphics defines the following class:
CgRGBColor
Specifies a color using integer values in the range 065535 giving the
intensities of the color primaries red, green, and blue.
The intensity: class method of CgRGBColor creates a gray-scale color, where the red,
green, and blue intensities have the same value. In the following example, color1
and color2 are equivalent:
| color1 color2 |
color1 := CgRGBColor intensity: 40000.
color2 := CgRGBColor red: 40000 green: 40000 blue: 40000.
The red, green, and blue components of a CgRGBColor can be retrieved using the
red, green, and blue methods.
Tip: The black and white class methods of CgRGBColor answer preexisting instances
of CgRGBColor representing black (all intensities = 0) and white (all intensities
= 65535), respectively.
The RGB values of a named color can be looked up in the color database by
sending the lookupColor: message to a CgScreen. The method is passed the color
name to look up. The result is the corresponding CgRGBColor, if the color name
was found in the database, or nil if the name was not found.
| color |
color := CgScreen default lookupColor: 'orange'.
Transcript cr; show: color printString.
Using palettes
A palette defines a mapping from pixel values to colors. Palettes have two
common uses:
Some platforms, such as OS/2 and Windows, provide an operating system default
palette containing some or all of these colors. On these platforms, the colors in the
Common Graphics default palette use the same color intensities as the
corresponding colors in the operating system default palette in order to minimize
color resource allocation.
To find the color corresponding to a given pixel value, the at: message is sent to
the palette with the pixel value as the argument. In the following example, the
palette has three colors and so defines colors for pixel values between 0 and 2
inclusive:
| colors palette |
colors := Array
with: (CgRGBColor red: 0 green: 0 blue: 0)
with: (CgRGBColor red: 16rFFFF green: 0 blue: 0)
with: (CgRGBColor red: 16rFFFF green: 16rFFFF blue: 16rFFFF).
palette := CgIndexedPalette colors: colors.
palette at: 0
==> aCgRGBColor(16r0000, 16r0000, 16r0000)
palette at: 2
==> aCgRGBColor(16rFFFF, 16rFFFF, 16rFFFF)
palette at: 3
==> aCgRGBColor(16r0000, 16r0000, 16r0000)
The last expression illustrates that if the palette does not define a color for a given
pixel value, the at: method answers black, that is, a CgRGBColor with intensities of
0.
It is also possible to find the pixel value corresponding to a given color in a palette
by using the nearestPixelValue: message. This message answers the pixel value for
the color in the palette that most closely matches the given color, as the following
example illustrates. The exact algorithm used for matching colors is described in
the specification for CgPalette>>nearestPixelValue:.
| colors palette |
colors := Array
with: (CgRGBColor red: 0 green: 0 blue: 0)
with: (CgRGBColor red: 16rFFFF green: 0 blue: 0)
with: (CgRGBColor red: 16rFFFF green: 16rFFFF blue: 16rFFFF).
palette := CgIndexedPalette colors: colors.
The nearestColor: message answers the color in the palette that most closely
matches the given color. It is equivalent to looking up the color in the palette using
at: after getting the nearest pixel value using nearestPixelValue:.
| colors palette |
colors := Array
with: (CgRGBColor red: 0 green: 0 blue: 0)
with: (CgRGBColor red: 16rFFFF green: 0 blue: 0)
with: (CgRGBColor red: 16rFFFF green: 16rFFFF blue: 16rFFFF).
palette := CgIndexedPalette colors: colors.
palette nearestColor: (CgRGBColor red: 0 green: 0 blue: 0)
==> (CgRGBColor red: 0 green: 0 blue: 0)
If the default palette is to be used, the setPalette: message is not required, because it
is selected by default when windows are created. Remember that CgIndexedPalettes
can be selected in windows, not CgDirectPalettes.
The setPalette: message can be sent only to the window of a shell widget or to a
pixmap. It is an error to send setPalette: to other windows. The shell windows
palette is inherited by all child windows of the shell window.
Tip: The size of selected palettes should be kept as small as possible, to minimize
color resource allocation. This gives other applications a better chance of
drawing with their preferred colors.
The following example illustrates how to select a palette. This assumes that the
shell message answers the CwTopLevelShell widget of the application (see Shell
widgets on page 158 for more information on shell widgets).
| names colors color palette |
names := #('black' 'white' 'red' 'green' 'blue' 'brown' 'aquamarine' 'pink').
colors := Array new: names size.
1 to: names size do: [:i |
color := self shell screen lookupColor: (names at: i).
color isNil ifTrue: [self error: 'Color not found.'].
colors at: i put: color].
palette := CgIndexedPalette colors: colors.
self shell window setPalette: palette
Alternatively, the getPalette and nearestPixelValue: methods can be used to find the
appropriate pixel value.
The blackPixel and whitePixel methods of CgDrawable answer the pixel value of the
color closest to black and white, respectively, in the drawables selected palette.
When a CgPixmap is first created using createPixmap:, the default palette is selected
in the pixmap, not the palette of receiver of the createPixmap: message. The receiver
is only used to specify the screen and compatible depths of the pixmap. Because
the receiver might not be the intended destination of a copy operation using the
pixmap, the receivers palette is not selected in the pixmap.
The palette of the window in which the pixmap is to be displayed must be selected
in the pixmap before drawing is done. This is required so that the
pixel-value-to-color mapping of the pixmap is the same as that of the destination
window. If this is not done, the pixel-value-to-color mappings for the pixmap and
the destination window will be different, and the results will not be what is
expected when the pixmap is copied to the window.
The following code shows the correct way to draw in a pixmap using a palette
other than the default palette. The code assumes that the shell method answers the
CwTopLevelShell widget and that the drawingArea method answers the
CwDrawingArea widget in which the pixmap will be displayed.
| shellWindow destWindow colors color palette pixmap gc |
shellWindow := self shell window.
destWindow := self drawingArea window.
"Create the palette."
colors := Array new: 16.
0 to: 15 do: [:i |
color := CgRGBColor red: 65535 * i // 15 green: 0 blue: 0.
colors at: i + 1 put: color].
palette := CgIndexedPalette colors: colors.
"Select the palette in the shell window."
shellWindow setPalette: palette.
Direct palettes
Direct palettes, represented by the class CgDirectPalette, specify a
pixel-value-to-color mapping for pixel values that directly encode colors. They are
used primarily in bitmapped images with 16 or more bits per pixel. A direct palette
specifies which bits of a pixel value correspond to each of the color primaries. This
correspondence is defined by three nonoverlapping bit masks.
For example, an image with 24 bits per pixel directly encodes each pixels color in
the corresponding pixel value with 8 bits for each of the primary color (red, green,
and blue). The following code creates a CgDirectPalette where the low-order byte of
a 24-bit pixel value gives the intensity of red, the next highest byte gives the
intensity of green, and the high byte gives the intensity of blue.
| palette |
palette := CgDirectPalette
redMask: 16rFF
greenMask: 16rFF00
blueMask: 16rFF0000
The at:, nearestPixelValue:, and nearestColor: methods described for indexed palettes
are also applicable to CgDirectPalettes. The following example illustrates the use of
at: on the palette created above:
palette at: 16r000000
==> aCgRGBColor(16r0000, 16r0000, 16r0000)
palette at: 16rFFFFFF
==> aCgRGBColor(16rFFFF, 16rFFFF, 16rFFFF)
palette at: 16r1F3F7F
==> aCgRGBColor(16r7F7F, 16r3F3F, 16r1F1F)
Note: The pixel values in this case use 8 bits (0255) each for red, green, and blue,
whereas CgRGBColors use 16 bits (065535). The red component of the pixel
value in the last example is 16r7F. To convert this to 16 bits it is multiplied
by 65535 and divided by 255 giving 16r7F7F.
Device-independent images
Conceptually, an image is a two-dimensional array of colors. For storage efficiency,
colors in an image are usually encoded as discrete pixel values with a fixed
number of bits for each pixel value. There are two common ways in which colors
are encoded as pixel values. In the first kind of encoding, the image is restricted to
a small set of colors (256 or less). This set is ordered and the pixel values are
treated as indices into the list of colors. This encoding requires 8 bits or fewer per
pixel. In the second kind of encoding, colors are encoded directly in the pixel
values. For example, the red, green, and blue intensities of a color can be encoded
as values in the range 0 to 255, requiring 8 bits for each primary and therefore 24
bits for the whole pixel value. For both encodings a palette is required to define
the pixel-value-to-color mapping.
The image attributes can be retrieved using the width, height, depth, palette, and data
methods of CgDeviceIndependentImage. The extent method answers the width and
height as a point.
To modify a single pixel value, use the putPixel:y:pixelValue: method. This method
takes the x- and y- coordinates of the pixel and stores the new pixel value at that
location in the image.
Tip: As with the drawing methods described in previous sections, the x-and
y-coordinates within images start at 0. The point 0@0 is at the top left corner
of the image, with the x-coordinate increasing from left to right, and the
y-coordinate increasing from top to bottom.
Continuing the previous example, the following code modifies the image data one
pixel at a time. The resulting image has adjacent vertical stripes increasing in
intensity from black at the left to white at the right.
Image data can be retrieved one pixel at a time using the getPixel:y: method. A
horizontal line of pixels can be retrieved using getPixels:y:width:pixels:startIndex:. A
new image can be copied from an area of an existing image using the getSubImage:
method and passing it a rectangle. The getPixel:... and putPixel: methods have
getColor: and putColor: equivalents that answer and accept CgRGBColor objects
rather than pixel values and use the images palette to convert between colors and
pixel values.
Displaying images
The putDeviceIndependentImage:image:srcRect:destRect: method of CgDrawable is used
to display images. As with all drawing operations, the first argument is a CgGC
specifying the drawing attributes. The next argument is the image to display. The
last two arguments are a source rectangle, specifying the area of the image to
display, and a destination rectangle, specifying the area of the drawable in which
to display the source area. If the source and destination rectangles do not have the
same extent, the source area of the image is stretched or shrunk to fit in the
destination area. The image can be flipped by specifying a negative width or
height in the destination or source rectangles.
The following code displays the image created in the previous example, stretched
by a factor of two. To ensure that the required colors are available in the video
hardware, the images palette is first selected in the window of the shell widget.
This code assumes that the shell method answers the shell widget and that the
drawable method answers the window of the drawing area.
Tip: The putDeviceIndependentImage: method supports 1-, 4-, and 8-bit per pixel
images on all platforms. Twenty-four-bit images are supported on Windows
and OS/2 PM only. 16- and 32-bit images are not supported. In the 24-bit
case, the images palette must be a CgDirectPalette with the same masks as
used in the example above. Note also that the display of 24-bit images on
Windows is very slow unless it is directly supported by the video driver.
The following example gets the contents of the entire default screen as an image:
| screen rect image |
screen := CgScreen default.
rect := 0 @ 0 extent: screen width @ screen height.
image := screen rootWindow getDeviceIndependentImage: rect
Creating icons
The fromImage:maskImage: class method creates a CgIcon given a shape image and a
mask image. Wherever there is a 1 in the mask image, the icon is opaque and the
corresponding pixel in the shape image is displayed. Wherever there is a 0 in the
mask image, the icon is transparent and the corresponding pixel in the shape
image is ignored. Specifying nil as the mask image is equivalent to specifying a
mask image of all 1s (entirely opaque).
The following example creates an 8-by-8 monochrome icon of a white cross with a
black border. The icon is transparent outside of the white border. The icon is not
freed because it will be used in the next example.
| palette shapeImage maskImage icon |
palette := CgIndexedPalette colors:
(Array
with: CgRGBColor black "0s are black"
with: CgRGBColor white). "1s are white"
shapeImage := CgDeviceIndependentImage
width: 8
height: 8
depth: 1
palette: palette
scanlinePad: 1
data: #[
2r00111100
2r00100100
2r11100111
2r10000001
2r10000001
2r11100111
2r00100100
2r00111100].
maskImage := CgDeviceIndependentImage
width: 8
height: 8
depth: 1
palette: palette "The mask image palette is not used."
scanlinePad: 1
data: #[
2r00111100
2r00111100
2r11111111
2r11111111
Drawing icons
The drawIcon:x:y:icon: method of CgDrawable is used to draw CgIcon objects. The
following code draws the icon, created in the previous example, at the top left
corner of the screen. The icon is freed because it is no longer required.
CgWindow default
drawIcon: CgGC default
x: 0
y: 0
icon: icon.
icon freeIcon
The best icon size to use can be obtained by sending queryBestIconSize to the
CgScreen. On Window and OS/2 PM this size is the maximum displayable icon
size. On X this size is only a suggestion, not a hard limit.
CgIcons are displayed using only the default palette colors. It is not necessary to
select a palette in the destination drawable. The drawIcon: method ignores the
selected palette.
The following code illustrates how to load an icon resource from the VM
executable on the Windows platform. Do this using the fromResource: class method:
| icon |
icon := CgIcon fromResource: 'APP_ICON'.
CgWindow default
drawIcon: CgGC default x: 0 y: 0 icon: icon.
icon freeIcon
To load an icon resource from a file other than the VM executable, use the
fromResource:fileName: class method of CgIcon. The following examples load an icon
from the VM executable, as above, but the file name is explicitly specified:
"Windows"
| icon |
icon := CgIcon
fromResource: 'APP_ICON' fileName: 'abt.exe'.
CgWindow default
drawIcon: CgGC default x: 0 y: 0 icon: icon.
icon freeIcon
"OS/2"
| icon |
icon := CgIcon
fromResource: 1 fileName: 'abt.exe'.
CgWindow default
drawIcon: CgGC default x: 0 y: 0 icon: icon.
icon freeIcon
The following table shows the class hierarchy that implements the file format
framework of Common Graphics. This framework provides methods to load and
unload images and icons in these formats, to test whether a file has a given format,
and to detect what the format of a given file is.
Table 25. File format class hierarchy
Class Description.
CgFileFormat Abstract file format class
CgIconFileFormat Abstract icon file format class
CgPMICOFileFormat PM ICO file format
CgWinICOFileFormat Windows ICO file format
CgImageFileFormat Abstract image file format class
CgPCXFileFormat PCX file format
CgPMBMPFileFormat PM BMP file format
CgTIFFFileFormat TIFF file format
CgWinBMPFileFormat Windows BMP file format
In addition to loading from files and unloading into files, Common Graphics
provides methods to load from ByteArrays and unload into ByteArrays. Error
handling methods are also provided. These methods are described in the following
sections.
The following example illustrates how to load a PCX image from a file named
my-image.pcx:
| format file image |
format := CgPCXFileFormat new.
file := CfsFileDescriptor open: 'my-image.pcx' oflag: ORDONLY.
image := format loadFromFileHandle: file atOffset: 0.
file close.
|image
The loadFromFile: method takes a file name directly, saving the application from
having to explicitly open and close the file. The following code has the same result
as the previous example:
| format image |
format := CgPCXFileFormat new.
image := format loadFromFile: 'my-image.pcx'.
|image
Handling errors
If an error occurs during a load, the loadFromFileHandle:atOffset: method answers
nil. Another way to detect an error is to send the hasErrorOccurred message to the
The following example adds error handling to the previous example of loading a
PCX file:
| format image |
format := CgPCXFileFormat new.
image := format
loadFromFile: 'my-image.pcx'.
format hasErrorOccurred
ifTrue: [self error: format currentErrorString]
ifFalse: [|image]
The following code unloads an image captured from the screen into a file named
img-out.pcx using the PCX format:
| screen rect image file format success |
screen := CgScreen default.
rect := 0 @ 0 extent: screen width @ screen height.
image := screen rootWindow
getDeviceIndependentImage: rect.
format := CgPCXFileFormat new.
file := CfsFileDescriptor
open: 'img-out.pcx'
oflag: OCREAT | OTRUNC | OWRONLY.
success := format
unload: image
intoFileHandle: file atOffset: 0.
file close.
success
ifFalse: [self error: 'Error unloading image']
The unload:intoFile: method takes a file name as an argument. It creates the file if it
does not exist, or overwrites the file if it does exist. The following code has the
same effect as the previous example.
| screen rect image format |
screen := CgScreen default.
rect := 0 @ 0 extent: screen width @ screen height.
image := screen rootWindow
getDeviceIndependentImage: rect.
format := CgPCXFileFormat new.
(format unload: image intoFile: 'img-out.pcx')
ifFalse: [self error: 'Error unloading image']
The following code unloads a single icon into a file in the Windows ICO format. It
assumes that the iconToUnload method answers the CgIcon to unload.
| icons format |
icons := Array with: self iconToUnload.
format := CgWinICOFileFormat new.
(format unload: icons intoFile: 'icon-out.ico')
ifFalse: [self error: 'Error unloading icon']
The following code illustrates how to unload an image into an array of ByteArrays
using the PCX format. Each ByteArray is limited to a maximum size of 64K. To
illustrate how space is reserved for a database record header, 32 bytes are skipped
at the beginning of each ByteArray.
| image format headerSize maxSize imageSize numByteArrays byteArrays offsets |
image := self imageToUnload.
format := CgPCXFileFormat new.
headerSize := 32.
maxSize := 65536. "64K"
imageSize := format totalSizeBeforeUnload: image.
"Determine total size."
numByteArrays := imageSize // (maxSize - headerSize) + 1.
byteArrays := OrderedCollection new.
1 to: numByteArrays do: [:i|
byteArrays add: (ByteArray new: maxSize)].
byteArrays := byteArrays asArray.
offsets := Array new: byteArrays size.
offsets atAllPut: headerSize.
(format
unload: image
intoByteObjects: byteArrays
offsetsIntoByteObjects: offsets)
ifFalse: [self error: format currentErrorString]
Icons are unloaded into ByteArrays in a similar fashion. The only difference is that
the totalSizeBeforeUnload: and unload:intoByteObjects:offsetsIntoByteObjects: methods
for the icon file formats take an array of CgIcons rather than an image.
The following code illustrates how to load a PCX format image from an array of
ByteArrays. It assumes that the imageStorage method answers the array of
ByteArrays created in the previous example.
| format headerSize byteArrays offsets image |
format := CgPCXFileFormat new.
headerSize := 32.
byteArrays := self imageStorage.
offsets := Array new: byteArrays size.
offsets atAllPut: headerSize.
image := format
loadFromByteObjects: byteArrays
offsetsIntoByteObjects: offsets.
image isNil
ifTrue: [self error: format currentErrorString]
PCX
The following methods provide extra information preserved and used by the
CgPCXFileFormat class:
hRes Horizontal resolution of device
PM BMP
The following methods provide extra information preserved and used by the
CgPMBMPFileFormat class:
colorEncode
Color encoding scheme
colorsUsed
Number of colors used by the image
compression
Data compression algorithm used
ident Application specific identifier
importantColors
Number of colors important for displaying image
recording
Recording algorithm used
rendering
Halftoning algorithm used
resUnits
Units of measure for xRes and yRes
size1 Halftoning correction factor
size2 Halftoning correction factor
xRes Horizontal resolution of device
yRes Vertical resolution of device
TIFF
The following methods provide extra information preserved and used by the
CgTIFFFileFormat class:
colorScheme
Color scheme (gray scale, palette, RGB)
compression
Data compression algorithm used
predictor
Prediction method, used with some types of compression
Windows BMP
The following methods provide extra information preserved and used by the
CgWinBMPFileFormat class:
compression
Data compression algorithm used
importantColors
Number of colors important for displaying image
pelsPerMeter
Horizontal and vertical resolution of device
Example
The following code illustrates how to unload an image in Windows BMP format,
using run-length encoding of the data if the image has a depth of 4 or 8. The
device resolution and number of important colors are also specified.
| image format |
image := self imageToUnload.
format := CgWinBMPFileFormat new.
format compression: 0. "No compression by default."
image depth = 8
ifTrue: [format compression: 1]. "RLE8 compression."
The following table summarizes when and how to free graphics objects allocated
by the application:
Table 27. Graphics object freeing reference
Graphics object Summary
CgCursor Obtained using: CgDisplay>>createFontCursor
CgPixmap>>createPixmapCursor
CgFont>>createGlyphCursor
Freed using: CgCursor>>freeCursor
When to free: When it will no longer be set as the cursor for
any window (using
CgWindow>>setWindowCursor:). Cursors are
normally allocated when an application is
opened and freed when it is closed.
CgDeviceIndepen- Obtained using: CgDeviceIndependentImage class>>
dentImage width:height:depth:palette:
width:height:depth:palette:scanlinePad:data:
Freed using: <None>
When to free: Images do not need to be explicitly freed.
CgDirectPalette Obtained using: CgDirectPalette class>>
redMask:greenMask:blueMask:
Freed using: <None>
When to free: Direct palettes do not need to be explicitly
freed.
CgGC Obtained using: CgWindow>>createGC:
Freed using: CgGC>>freeGC
When to free: When it is no longer required for graphics
operations on the drawable for which it was
created. It is typically freed when a window is
closed by hooking the destroy callback.
These capabilities are described later in this chapter. In addition, this chapter
explains how the system is based on OSF/Motif, gives an overview of the
Common Widgets class hierarchy, and describes the basic approach for building an
application.
OSF/Motif compatibility
The Common Widgets subsystem is based on the OSF/Motif C programming
interface standard. This section is of interest to developers familiar with Motif. It
describes the strategy used to translate Motif C types and functions to Smalltalk
classes and methods. Experienced Motif developers will be able to apply their
knowledge of the C programming interface directly when programming Common
Widgets.
Smalltalk classes have been created for most C data types. These classes are named
by prefixing the Motif data structure name with Cw (after first removing any X,
Xt, or Xm prefix). For example, the Motif data structure Widget is represented by
the Smalltalk class CwWidget.
In the Common Widgets subsystem, the XmListSelectItem call has been mapped to
an instance method of the class CwList:
selectItem: item notify: notify
The C type Widget, in this case, is mapped to the Smalltalk class CwList because
the XmListSelectItem function applies only to list widgets. The XmList prefix has
been stripped off, because such C-specific prefixing is unnecessary in Smalltalk.
Where C types have appropriate corresponding Smalltalk base classes, C types are
mapped to these. For example, the C type XmString is mapped to the Smalltalk
class String, and the C type Boolean is mapped to the Smalltalk class Boolean.
Composite widgets can have zero or more child widgets. A composite widgets
children can include other composite widgets, primitive widgets, or both. Different
composite widgets provide various kinds of layout capabilities for arranging
children. The following table briefly explains the CwComposite class and its
subclasses. Classes in italics are abstract classes.
Shell widgets provide the protocol between the application interface and the
window manager. The following table provides brief descriptions of the CwShell
class and its subclasses. Classes in italics are abstract classes.
Table 30. Shell hierarchy
Class hierarchy Responsibility
CwWidget Defines common behavior for all widgets
CwBasicWidget Defines common behavior for widgets implemented
directly by the platform
CwShell Defines common behavior for the top-level widgets of
windows and dialogs
CwOverrideShell A pop-up window that bypasses window management,
and normally appears on top of all other widgets
CwWMShell Defines common behavior for shell widgets that do not
bypass window management
CwTopLevelShell Provides a normal window with standard appearance and
decorations
CwTransientShell A pop-up dialog window that does not bypass window
management
CwDialogShell A pop-up window used to implement modal and
modeless dialog windows
The next sections explain how widgets are created and configured, and how
callbacks and event handlers allow the application to react to user actions.
In the following example, the widget tree for a graphics application window is
shown. A CwTopLevelShell is created to interact with the window manager. A
CwMainWindow is created as the single child of the shell. A CwForm is created as
the child of the main window. The CwForm is required to position a
CwDrawingArea and a CwRowColumn, which are created as children of the form.
Three CwPushButtons are created as children of the row-column widget.
Widget tree
CwTopLevelShell
decorations and title bar
CwMainWindow
menu bar and work area
CwForm
positions side by side
CwDrawingArea
for drawing
positions in column
CwRowColumn
Appearance on screen
a button
CwPushButton
a button
CwPushButton
a button
CwPushButton
1. Creating a widget
The first step is to create the widget. When a widget is created, it is given a name,
and its parent-child relationship is established. A new widget adopts default
settings for any of its resources that are not explicitly set when the widget is
created. Widget resources are data that define how a widget appears and behaves.
Resources are described in Widget resources and functions on page 141.
Tip: If you plan to use widget names to set initial resource values through X
resource files, such as .Xdefaults, then you must take care to use names which
follow the X resource file naming conventions.
When a widget is created, it does not immediately appear on the screen. In the
following illustration, a label widget named CwLabel 3 has been created, along with
two others, in a widget tree. Its parent is a row-column widget named
CwRowColumn. A row-column widget lays out its children horizontally, vertically,
or both. The row-columns parent is a shell widget named CwTopLevelShell.
Widget tree is created in memory only; Nothing is visible on the
no windows are associated with it screen at this point
CwTopLevelShell
CwRowColumn
2. Managing a widget
To manage a widget, you specify that its size and position will be managed by its
parent widget. This process is called geometry management. Any changes in the
size or position of the parent widget will be recursively propagated to managed
child widgets. The application must specify that a widget is to be managed by
sending the manageChild message to the widget.
Managing or unmanaging a widget does not affect the managed state of child
widgets. However, if a widget is unmanaged then neither it nor any of its children
will participate in geometry management. By default a widget is not managed
when it is created.
3. Mapping a widget
To map a widget, you specify that it is to appear on the display after it has been
realized. Realizing a widget is described in 4. Realizing a widget. By default, a
widget is mapped automatically when it is managed. This is controlled by the
setting of the mappedWhenManaged widget resource, which defaults to true.
Unmanaging a widget also unmaps it.
It is possible for widgets to be created, managed, and realized, but left unmapped.
The widget remains invisible until it is mapped. This technique is useful for
quickly displaying frequently used dialog boxes.
Widgets are mapped using the mapWidget method, and unmapped using the
unmapWidget method. Mapping or unmapping a widget maps or unmaps all child
widgets.
4. Realizing a widget
Until a widget is realized, it is invisible. A widget can be created, managed in the
tree, and mapped, but it will not appear on the screen until it is realized. During
realization, all widgets assume their initial geometry and create their visual
In the following example, the CwTopLevelShell widget has been realized, and the
CwRowColumn has positioned its three label children.
Widget tree is created, managed, and mapped The widget tree is displayed
and is now realized on the screen
CwTopLevelShell
CwLabel 1
CwRowColumn
CwLabel 2
5. Destroying a widget
When a widget is no longer required, it is destroyed. A widget can be implicitly
destroyed by the user, for example by clicking on a close box. Alternatively, a
widget can be destroyed under application control by sending it the destroyWidget
message. The widget is removed from the display and released from memory.
Destroying a widget recursively destroys all child widgets.
In the following example, assume that the widget tree is created, managed and
realized, but the second CwLabel is subsequently unmapped. Notice that the label
is removed from the screen, but its parent still reserves its position in the
CwTopLevelShell
CwLabel 1
CwRowColumn
In this example, assume that the widget tree is created, managed and realized, but
CwLabel 2 is subsequently unmanaged. Not only is the button removed from the
screen, or unmapped, but it also loses its position in the row-column widget.
The widget tree is in memory How the widget tree
and CwLabel 2 is unmanaged appears on the display
CwTopLevelShell
CwLabel 1
CwRowColumn
CwLabel 3
Resources
Resources are somewhat analogous to Smalltalk instance variables, and resource
set and get methods are similar to instance variable accessor methods. However,
there are several important differences:
v Resources might or might not be implemented using instance variables, and the
implementation varies from platform to platform.
v Changes to values in widget resources are immediately reflected in the
appearance of the widget.
All widgets have a core set of resources. For example, all widgets have width and
height resources. A widget can also have resources specific to its behavior. For
example, the items resource of the CwList widget defines what items are displayed
in the widget. Default values are provided for all of a widgets resources.
The following table illustrates the resources that are available for a CwPushButton
widget. Many of the resources are provided by CwWidget, and these are available
to all widgets.
Table 31. Widget resources for the CwPushButton class
CwWidget CwPrimitive CwLabel CwPushButton.
ancestorSensitive accelerator
borderWidth backgroundColor acceleratorText activateCallback
depth alignment armCallback
destroyCallback foregroundColor fontList
height helpCallback labelIcon disarmCallback
labelInsensitiveIcon
mappedWhenManaged navigationType showAsDefault
resizable traversalOn labelInsensitivePixmap
resizeCallback labelPixmap
sensitive labelString
userData labelType
width marginBottom
x marginHeight
y marginLeft
marginRight
(plus 16 attachment- marginTop
related resources) marginWidth
mnemonic
recomputeSize
Note: Appendix A. Widget resources and callbacks on page 469 provides a table
for each class in the CwWidget hierarchy.
A widgets resources are set or retrieved using the set and get accessor methods of
the widget, which are the names that correspond directly to the associated resource
name. Resource setting methods have a special property that can be sent to a
widget to set initial state during widget creation, from inside a create argBlock.
Not all widget resources can be modified or retrieved at any time. Widget
resources have a resource access designator that indicates when the resource can be
set or retrieved. Resources are tagged with the letters C, S, or G to indicate when
the resource can be modified or retrieved, as follows:
v The application can set the resource at creation time only (C).
v The application can set the resource at any time (S).
v The application can retrieve, or get, the resource at any time after the widget is
created (G).
Create-only (C) resources can only be set using an argBlock at widget creation time.
An argBlock is a single argument Smalltalk block of code that is evaluated with the
widget being created as its argument. Resources with an (S) designation can also
be set in the argBlock. The argBlock is evaluated before the widget is fully created,
that is, while it is still under construction, but after a Smalltalk object has been
created to represent the widget. If argBlock is not required, nil can be used for the
argBlock argument, rather than unnecessarily creating an empty block.
Tip: Always set resources in the create argBlock wherever possible. Also, unless
names are needed for X resource files, use the widgets name to establish a
default resource value as described in the widget creation section on page 1.
Creating a widget on page 138. If the system has more information available
at the time of widget creation, it can perform more optimization. On some
platforms a significant performance advantage is achieved by setting
resources in the create argBlock rather than immediately after creation, which
might cause default widget configuration to have to be undone.
In the following example, the width and height resources for a drawing area are
explicitly set in an argBlock when the drawing area widget is created. These specify
the size in pixels of the drawing area widget. The size of the shell widget is
calculated based on the size of the drawing area widget. In general, when the size
of a widget is not explicitly specified, it is calculated based on the size of its
children, recursively. The string arguments in the creation messages specify the
names of the widgets. By default, the name of the top-level shell appears as the
window title.
| shell drawingArea |
shell := CwTopLevelShell
createApplicationShell: 'ShellName'
argBlock: nil.
drawingArea := shell
createDrawingArea: 'draw'
argBlock: [:w | w width: 100; height: 100].
drawingArea manageChild.
shell realizeWidget
Resources with set (S) and get (G) designations can be set and retrieved,
respectively, after widget creation using the appropriate set and get methods.
Multiple resources with set (S) designation can also be set simultaneously after the
widget is created using the setValuesBlock: message, which takes an argBlock as
argument. The setValuesBlock: method is the recommended way of setting multiple
resources for a widget after the widget is created. Normally, after a widget has
been created and a resource is modified, which changes a widgets appearance, the
widget is redisplayed to show the change. Using a setValuesBlock is more efficient
than setting the resources outside the block because the widget can then optimize
updates together, even if several of them change the widgets appearance. The
block passed to setValuesBlock: has the same format as the argBlock used when
creating a widget.
In the following example, the geometry of a shell widget is changed. Assume that
the variable shell is a top-level shell that has been created and realized.
Tip: Some resources change their values when the value of a different resource in
the same widget is changed. To avoid this push-down-here-pop-up-there
effect, such resources must be set simultaneously using an argBlock, either on
creation or after creation using setValuesBlock:.This situation occurs with
left/right and top/bottom CwForm attachment resources, which should
always be set in pairs.
Function methods
Widget methods that are not resource set or get methods are widget function
methods. Unlike resource setting messages, function messages can only be sent to
widgets after they have been created. While resource methods are used to access or
change widget state, function methods typically perform more complex operations,
and in some cases modify resource values. While resource get and set methods
uniformly require zero arguments and one argument respectively, widget function
methods take varying numbers of arguments, depending on the particular
function. The manageChild method is an example of a widget function.
Functions often alter the resource values of a widget as a side effect. For example,
the setString: function for text widgets alters the value resource of the widget. In
some cases it is possible to achieve the same effect using either a resource method
or a function method.
Tip: Do not call function methods from inside a create argBlock. Because the
widget is not fully created when the create argBlock is evaluated, invoking
widget functions results in errors.
Tip: Form constraints are described in detail in Form widgets on page 167.
When this code is run, a window titled Graphics Example appears on the screen.
The widgets behave as expected, but the application is not notified when a button
is pressed or when the mouse is moved. For the application to be notified of the
users interaction with the widgets, event handlers and callbacks are required.
Graphics Example
1
2
3
A callback is the mechanism that notifies the application when some higher level
action is performed on a widget. For example, the XmNactivateCallback is used to
inform the application that a CwPushButton has been pressed and released. As
another example, all widgets support the XmNdestroyCallback that is invoked just
before a widget is destroyed.
The following example illustrates how callbacks and event handlers are defined.
For detailed information on callbacks and event handlers, see Callbacks on page
150 and Event handlers on page 154.
When the push-button widget is pressed (that is, when the user clicks mouse
button 1 while the mouse pointer is over the push-button widget), the
pressed:clientData:callData: method is run. When the mouse is moved in the drawing
area with button 1 held down, the button1Move:clientData:event: method is run.
form := main
createForm: 'form'
argBlock: nil.
form manageChild.
drawArea := form
createDrawingArea: 'drawing'
argBlock: [:w |
w
width: 300;
height: 300;
borderWidth: 1].
"After the drawing area is created, an event handler is added to the widget
The method specifies that the application shall be notified of Button1Motion
events (button 1 is down and the mouse is moving) within the drawing area.
When mouse events occur, the button1Move:clientData:event message is sent
to self (the application)."
drawArea
addEventHandler: Button1MotionMask
receiver:self
selector: #button1Move:clientData:event:
clientData:nil.
drawArea manageChild.
button := form
createPushButton: '1'
argBlock: nil.
"Here a callback is added to the newly created push-button widget.
The method specifies that the application shall be notified of
activate callback (the button is pressed and released). When the
button is activated, the pressed:clientData:callData: message is
sent to self."
button
addCallback:XmNactivateCallback
receiver: self
selector: #pressed:clientData:callData:
clientData:nil.
button manageChild.
drawArea
setValuesBlock: [ :w |
w
topAttachment: XmATTACHFORM;
topOffset: 2;
Note: These widgets have many resources and callbacks. This section discusses
only the most commonly used resources and callbacks. For a complete list,
see Appendix A. Widget resources and callbacks on page 469.
Because convenience methods do not automatically manage the widgets that they
create, the new widgets must be explicitly managed after they are created. In
addition, because some convenience functions actually create composite widget
subtrees, the widget returned might not be a direct child of the widget the creation
message was sent to. For an example of this, see Scrolled lists on page 188.
Tip: Widgets can also be created by sending messages directly to the widget
classes. However, widgets created in this way bypass normal initialization
and configuration, and might have different behavior than widgets created
using convenience functions. Always use the convenience function to create a
widget, if one is available.
The code fragment below illustrates how to create a push-button widget using a
convenience method. Top level shells are created by sending a message directly to
the CwTopLevelShell class.
| shell button |
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: nil.
button := shell
createPushButton: 'Button1'
argBlock: nil.
button manageChild.
shell realizeWidget.
Some convenience methods do more than create a widget. For example, the
createScrolledList:argBlock: method creates a pseudowidget that appears, in many
respects, to be a scrolled window, scroll bars, and a list widget. The relationship
between the scrolled window, the scroll bars, and the list is defined such that the
list can be scrolled using the scroll bars of the scrolled window. The create
operation returns the list widget, and is treated by the application as any other list
widget. However, the widget system created is tightly coupled and on some
platforms might be implemented using a single widget, to maximize performance
and platform look and feel. For these reasons, the application should not attempt
to reconfigure the hidden parts of such pseudowidgets.
Following is a list of the shell widget creation convenience methods that are class
methods of CwShell and its subclasses:
createPopupShell:parent:argBlock:
createApplicationShell:argBlock:
Following is a list of the widget creation convenience methods that are instance
methods of CwWidget:
createArrowButton:argBlock:
createBulletinBoard:argBlock:
Callbacks
Actions performed on widgets by the user must be communicated back to the
application. One mechanism used for this communication is a callback. A callback
method defines actions to perform in response to some occurrence in a widget.
Callbacks are normally registered just after widgets are created. For example, when
a push-button widget is created, the application usually registers an activate
callback that is run when the button is activated by the user clicking on it.
Although it is not necessary for the application to register callbacks, without them
the application is unable to take action based on the users interaction with the
widgets.
Tip: The argBlock argument of a widget creation message can only be used to set
widget resources. The addCallback: message cannot be used within the create
argBlock. Callbacks are usually registered immediately after the widget has
been created, and before it is realized.
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: nil.
button := shell
createPushButton: 'OK'
argBlock: nil.
button
"Name of the callback to add"
addCallback: XmNactivateCallback
"Receiver of the callback message, usually self"
receiver:self
"Name of method to execute"
selector: #pressed:clientData:callData:
"Data to be passed unmodified to the callback method"
clientData: 'Test data'.
button manageChild.
shell realizeWidget.
When an activate callback occurs due to the button being pressed, the
pressed:clientData:callData: method, shown below, is run. The method prints the
string Test data on the Transcript Window. The widget issuing the callback is
passed as the widget parameter. In this case, this is the push-button widget. The
string Test data, specified as the client data when the callback was added, is
passed as the clientData parameter. Callback-specific data is passed as the callData
parameter. For the activate callback of push-button widgets, however, the call data
provides no new information.
The following table describes the class hierarchy and data accessor method names
for call data objects. All classes are concrete classes. A description of each accessor
method can be found in the source or comment for each method.
Table 32. Call data class hierarchy
Class hierarchy Responsibility Data accessor methods
CwAnyCallbackData Provides call data for most reason (a constant,
callbacks. prefixed by XmCR)
CwComboBoxCallbackData Provides call data for combo item
box singleSelectionCallback. itemPosition
CwConfirmationCallbackData Provides call data for doit
callbacks such as the shell doit:
windowCloseCallback. This
callback can be canceled by
the application.
CwTextVerifyCallbackData Provides call data for text currInsert
and combo-box endPos
modifyVerifyCallback. These startPos
callbacks can be canceled by text
the application. text:
CwDrawingCallbackData Provides call data for event
callbacks such as composite window
expose and interceptExpose
callbacks, drawing area input
callbacks, and drawn-button
activate and expose
callbacks.
CwListCallbackData Provides call data for item
callbacks such as list itemPosition
browseSelect, singleSelect, selectedItemCount
multipleSelect,
extendedSelect, and selectedItemPositions
defaultAction callbacks. selectedItems
CwRowColumnCallbackData Provides call data for widget
callbacks such as data
row-column entryCallback. callbackData
CwToggleButtonCallbackData Provides call data for set
callbacks such as the
toggle-button
valueChangedCallback.
CwValueCallbackData Provides call data for value
callbacks such as scale and
scroll bar drag and
valueChangedCallback, and
scroll bar decrement,
increment, pageDecrement,
pageIncrement, toBottom,
and toTop callbacks.
Tip: Call data objects are only valid during the callback. Do not store a call data
object in a callback method and attempt to reference it later.
Event handlers
Event handlers are another mechanism used to inform the application of input
actions by the user. While callbacks notify the application of high level interactions
such as the selection of items in a list widget, event handlers notify the application
of low level interactions, including the following:
v Mouse pointer motion
v Mouse button presses and releases
v Individual key presses and releases
Tip: The argBlock argument of a widget-creation message can only be used to set
widget resources. The addEventHandler: message cannot be used within the
create argBlock. Event handlers are usually registered immediately after the
widget has been created, and before it is realized.
The eventMask is specified as the bitwise-OR of one or more of the bit masks
described in the following table.
Table 34. Common widgets event masks
Event masks Description
KeyPressMask Keyboard key down events
KeyReleaseMask Keyboard key up events
ButtonPressMask Mouse button down events
ButtonReleaseMask Mouse button up events
PointerMotionMask All pointer motion events
Button1MotionMask Pointer motion events while button 1 is down
The following table describes the class hierarchy for event objects. Classes in italics
are abstract classes.
Table 35. Event class hierarchy
Class hierarchy Responsibility.
CwEvent Defines common behavior for event data in event
handlers.
CwExposeEvent Provides event data for expose events in expose
callbacks (see Note below).
CwInputEvent Defines common behavior for button, key, and
motion event objects.
CwButtonEvent Provides event data for mouse button-press/release
events.
CwKeyEvent Provides event data for key-press/release events.
CwMotionEvent Provides event data for mouse motion events.
The following messages can be sent to the event object to retrieve information
about the event. The methods for all events (CwEvent) include the following:
type The type of event that occurred. This has one of the following values:
ButtonPress, ButtonRelease, Expose, KeyPress, KeyRelease, and MotionNotify.
window
The CgWindow associated with the widget for which the event was
generated.
display The CgDisplay associated with the event.
There are two common uses of event handlers. The first is for handling input in a
drawing area widget. For example, in a graphical drawing application a drawing
area widget would be used to display the drawing under construction. Event
handlers would be registered to notify the application of pointer motion, mouse
button, and key press events, allowing text strings to be edited and graphical
objects to be positioned and changed using the mouse.
The second common use is for handling pop-up menus. An event handler is added
for the ButtonMenuMask event. When the event handler is called, the application
pops the menu up.
Mouse button 3 is used as the menu button. However, some platforms trigger the
button menu event when the button is pressed, and others when the button is
released. The ButtonMenuMask event hides this difference. It should be used, rather
than the other button events, to support pop-up menus in a platform-independent
manner.
When an event occurs, the following method is run. Information about the event is
determined from the event argument and is displayed in the label widgets.
eventHandler: widget clientData: clientData event: event
"Handle an input event."
CwOverrideShell widgets are used for pop-up windows that bypass window
management and appear in front of all other windows. They do not have a
window frame, and cannot be moved, resized, or iconified by the user. Create
CwOverrideShell widgets using the CwShell class method
createPopupShell:parent:argBlock:. Make a CwOverrideShell visible by sending it the
popup message.
The root of a widget tree must be a CwTopLevelShell. A top level shell widget has
no parent. Top-level shells are created using the createApplicationShell:argBlock:
method, which is sent to the CwTopLevelShell class. A top-level shell widget must
have a child widget before it can be realized.
Tip: A common programming error is to attempt to realize a top level shell that
has no child widget, or whose child widget computes an initial size with zero
width or height. On a Motif platform this normally causes the application to
exit with the message: Error: Shell widget has zero width or height. IBM
Smalltalk detects most common cases and prevents the application from
exiting.
The following example creates a top-level shell with a main window widget as its
child. In this example, the main windows width and height are explicitly set.
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Shell Example 1'].
mainWindow := shell
createMainWindow: 'main'
argBlock: [:w | w width: 100; height: 100].
mainWindow manageChild.
shell realizeWidget.
Tip: The top-level shell decorations settings indicate the preferred configuration
for the window. The window manager can alter or ignore the settings if
particular combinations are not supported by the platform.
scroll := shell
createScrolledWindow: 'scroll'
"The default scrollBarDisplayPolicy."
argBlock: [:w | w scrollingPolicy: XmAUTOMATIC].
buttons := scroll
"Creates a row-column that will contain the buttons to be scrolled."
createRowColumn: 'buttons'
argBlock: nil.
buttons manageChild.
(Collection withAllSubclasses collect: [:class | class name])
asSortedCollection do: [:name |
(buttons
"Creates the push buttons using the Collection class hierarchy names."
createPushButton: name
argBlock: nil)
manageChild].
scroll manageChild.
shell realizeWidget.
Main-window widgets
The main-window widget (CwMainWindow) is used to organize the applications
menu bar and the widgets that define the applications work region. As a subclass
of CwScrolledWindow, CwMainWindow class also includes all of the functionality
provided by the CwScrolledWindow class and can provide scroll bars for scrolling
the work region. If a main window is used, it must be the immediate child of a
top-level or dialog shell.
In order to manage its children correctly, a main-window widget must know which
widget is the menu bar, which widget is the work region, and which widgets are
the scroll bars. The setAreas:horizontalScrollbar:verticalScrollbar:workRegion: message
explicitly tells the main window which of its child widgets are to be used for these
purposes. In the following example, an empty menu bar and a drawing area
widget are created as children of the main window. The setAreas: message is sent to
the main window to explicitly set menuBar as the main windows menu bar, and
drawingArea as the main windows work region. Because no scroll bars are being
defined by the application, nil is passed in for the scroll bar arguments. For more
information on menus and menu bars, see Menus on page 178.
| shell main menuBar drawingArea |
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: nil.
main := shell
createMainWindow: 'main'
argBlock: nil.
main manageChild.
shell realizeWidget.
Text widgets
The text widget (CwText) provides text viewing and editing capabilities to the
application. Create text widgets using the createText:argBlock: and
createScrolledText:argBlock: convenience methods. The latter method makes the text
scrollable, but otherwise provides basically the same functionality.
Set and retrieve the entire contents of the text widget using the setString: and
getString methods.
Two of the text widgets resources are editMode and wordWrap. The editMode
resource specifies whether the widget supports single-line or multiline editing of
text. It can be set to XmSINGLELINEEDIT (the default) or XmMULTILINEEDIT.
The wordWrap resource specifies whether lines are to be broken at word breaks so
that text does not go beyond the right edge of the window. The default setting for
wordWrap is false.
Tip: Word wrap and horizontal scrolling are incompatible. In order for word wrap
to work, the text widget must be configured without a horizontal scroll bar by
setting the scrollHorizontal resource to false.
The following example creates a scrollable, multiline text widget with word wrap
on.
text := shell
"A scrollable text widget is created and managed."
createScrolledText: 'text'
argBlock: [:w |
w
CwText widgets also have resources to control the initial number of rows and
columns they contain, the position of the insertion point, the width of the tab
character, and whether or not the CwText is editable. For a complete list of CwTexts
resources and callbacks, see Appendix A. Widget resources and callbacks on
page 469. CwText widgets can also set, get, cut, copy, and paste a selection, scroll to
a given line, and insert or replace text at a given position.
A CwText widget has input focus when it can accept keyboard input. A CwText
usually provides some visual indication that the widget has focus, such as
displaying the insertion position as a flashing I-beam or drawing a thicker border.
Application developers can add a focusCallback or a losingFocusCallback to a CwText
if additional behaviour is required when the widget either gains or loses focus. For
further discussion on the concept of focus, see Shell widgets on page 158. You
can see an example of a losingFocusCallback with a single-line text widget in
Example: a primitive extended widget on page 203.
open
| shell text |
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Text Widget Example'].
text := shell
createText: 'text'
argBlock: [:w | w columns: 18].
text
addCallback: XmNmodifyVerifyCallback
receiver: self
selector: #modifyVerify:clientData:callData:
clientData: nil.
text manageChild.
shell realizeWidget.
modifyVerify: widget clientData: clientData callData: callData
"Update the stored version of the string in the callData, so that the
text widget inserts capital letters instead of the real text typed or
pasted by the user."
The resize callback is called when the drawing area changes size, usually due to a
change in the size of a parent widget. If an expose callback is triggered as a result
of a resize, the resize callback is always sent before the expose callback. It is
possible for the resize callback to be run before the window has been realized. The
resize callback handler should handle the case where the window message returns
nil.
The input callback is called when a mouse button is pressed or released inside the
widget or a key on the keyboard has been pressed or released. The destroy
callback, run when the widget is about to be destroyed, is a good place to free any
graphics resources that have been allocated for drawing.
Object subclass: #DrawingAreaExample
instanceVariableNames: 'gc radius segments '
classVariableNames: ''
poolDictionaries: 'CwConstants CgConstants '
example1
"Open the drawing area example."
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Drawing Area Example'].
draw := shell
"Create a drawing area widget, with its width and height set to the
diameter of the mandala"
createDrawingArea: 'draw'
argBlock: [:w |
"Add a destroy callback that is run when the drawing area is destroyed.
The destroy callback is a good place to free any allocated graphics
resources."
addCallback: XmNdestroyCallback
receiver: self
selector: #destroy:clientData:callData:
clientData: nil.
gc isNil ifTrue : [
gc := widget window
"On the first expose, create a graphics context with a foreground color
of black."
createGC: None
values: nil.
gc setForeground: widget window blackPixel].
widget window
"Draw the line segments."
drawSegments: gc
segments: segments].
recalculateSegments: widget
"Recalculate the coordinates of the mandala's line segments."
| n points x y |
n := 20.
quit := false.
event := callData event.
"Shift-click"
(event type = ButtonPress and: [event button = 1])
ifTrue: [
quit := (event state & ShiftMask) = ShiftMask].
clickStartTime := 0.
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Double-click test'].
(drawingArea := shell
createDrawingArea: 'draw'
argBlock: [:w |
w
width: 100;
height: 100])
manageChild.
drawingArea
addEventHandler: ButtonPressMask
receiver: self
selector: #buttonPress:clientData:event:
clientData: nil.
shell realizeWidget.
buttonPress: widget clientData: clientData event: event
"Detect double click by checking whether the time between successive
presses of the left mouse button is less than the system-defined
double-click time."
event button = 1
ifTrue: [
event time - clickStartTime < widget display doubleClickInterval
ifTrue: [
clickStartTime := 0.
Transcript cr; show: 'DOUBLE CLICK' ]
ifFalse: [
clickStartTime := event time ]].
Tip: Adding a mouse down event handler to a widget that processes mouse events
internally, such as a CwPushButton, might result in unpredictable behaviour.
To detect double-clicks in a CwList, use the defaultAction callback.
Layout widgets
The form (CwForm) and row-column (CwRowColumn) widgets are composite
widgets that allow the application to specify how child widgets of the composite
should be laid out relative to each other and relative to the composite.
Form widgets
Create form widgets using the createForm:argBlock: convenience method. Position
form widget children by attaching their sides to other objects. Specify attachments
by setting each childs leftAttachment, rightAttachment, topAttachment and
bottomAttachment resources. A side can be attached either to a given position, to
another widget, or to the edge of the form. The attachment types are listed below.
The first four types are the most commonly used. All are described in terms of the
leftAttachment, but the same attachment types apply to the other sides, with
corresponding behavior.
XmATTACHNONE
Default. Do not attach this side.
Tip: The results are undefined if an offset setting is used with an attachment type
of XmATTACHPOSITION.
If attachments have been set on all sides of a widget, the size of the widget is
completely determined by the form and the other child widgets. However, if a side
is left unattached, the widget will use its preferred size in the corresponding
dimension. This is useful for allowing widgets to size themselves automatically
based on their font size, contents, and other attributes.
Some convenience methods, such as those used to create a scrolled list or a scrolled
text, actually create a widget subtree, but instead of returning the root of the
subtree, the child is returned. In these cases, the form attachments must be set on
the returned widgets parent, rather than on the widget itself. For an example, see
Scrolled lists on page 188.
The following diagram illustrates a form containing a drawing area and a text
widget. The right side of the drawing area is attached to a position two-thirds (67
per cent) of the way from left to right. The left side of the text widget is attached
to the right side of the drawing area. The remaining sides of the text and drawing
area widgets are attached to the form. The widgets are offset from each other by
two pixels. (Offsets in the diagram have been exaggerated to show the
Form Example
Shell widget
To form To form
To drawing area
To form To form
The following code example creates the widget tree illustrated above:
| shell form drawing text |
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Form Example'].
form := shell
createForm: 'form'
argBlock: nil.
form manageChild.
drawing := form
createDrawingArea: 'drawing'
argBlock: [:w |
w
borderWidth: 1;
width: 200;
height: 200;
leftAttachment: XmATTACHFORM;
leftOffset: 2;
rightAttachment: XmATTACHPOSITION;
rightPosition: 67;
topAttachment: XmATTACHFORM;
topOffset: 2;
bottomAttachment: XmATTACHFORM;
bottomOffset: 2].
drawing manageChild.
text := form
createText: 'text'
argBlock: [:w |
w
leftAttachment: XmATTACHWIDGET;
leftWidget: drawing;
leftOffset: 2;
rightAttachment: XmATTACHFORM;
rightOffset: 2;
topAttachment: XmATTACHFORM;
topOffset: 2;
bottomAttachment: XmATTACHFORM;
bottomOffset: 2].
text manageChild.
shell realizeWidget.
Row-column widgets
The row-column widget (CwRowColumn) positions its children in rows or columns.
CwRowColumn widgets are frequently used to lay out groups of buttons, including
In the following illustration, the buttons on the left are organized in a row-column
widget. The row-column and the drawing area are contained in a form, similar to
the previous example.
RowColumn Example
Kitchen
Dining Room
Living Room
Washroom
Bedroom
Workshop
form := shell
createForm: 'form'
argBlock: nil.
form manageChild.
rowColumn := form
createRowColumn: 'rooms'
argBlock: [:w |
w
orientation: XmVERTICAL;
marginWidth: 10;
marginHeight: 10;
spacing: 20;
leftAttachment: XmATTACHFORM;
topAttachment: XmATTACHFORM;
bottomAttachment: XmATTACHFORM].
rowColumn manageChild.
#('Kitchen' 'Dining Room' 'Living Room' 'Washroom' 'Bedroom' 'Workshop')
do: [:room |
(rowColumn
createPushButton: room
argBlock: nil)
manageChild].
Buttons and labels can display either strings, pixmaps or icons as their contents,
depending on the value of the labelType resource. See Icon and pixmap label and
button widgets on page 176 for more detail on labelType.
The following resources define the visual appearance of labels and buttons: x, y,
height, width, marginTop, marginBottom, marginHeight, marginWidth, marginLeft, and
marginRight. These are illustrated in the following figure:
(x,y) width
marginHeight
marginTop
height
Widget Label
marginBottom
marginHeight
marginLeft
marginRight
marginWidth
marginWidth
Tip: The margin resource settings indicate the preferred appearance of the widget.
They might be ignored if they are not supported by the platform or conflict
with the platforms look and feel.
By default, the name given in a label or button creation message is used as the
widgets labelString. The contents of a label or button widget are changed using the
labelString: resource method.
The acceleratorText resource describes the string that is displayed beside the button
in the menu.
Note: On some platforms, accelerators are case sensitive. A keysym value of XKn
only fires with a lowercase accelerator key press.
| shell label |
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Label Example']. This is a label.
label := shell
createLabel: 'label'
argBlock: nil.
Push-button widgets
Create push-button widgets (CwPushButton) using the createPushButton:argBlock:
convenience method.
Push
Push buttons call their activate callback when they are pressed and released.
Toggle-button widgets
Create toggle-button widgets (CwToggleButton) using the createToggleButton:argBlock:
convenience method.
Toggle buttons have two states: on and off. The state of a toggle button can be
queried and changed using the getState and setState:notify: messages, respectively.
Toggle buttons call their valueChanged callback when their state is changed.
Toggle buttons are typically used to create radio-button and check-box groups
using row column convenience methods described in the next sections. The
toggle-button indicatorType resource controls whether the toggle button has a
radio-button or a check-box appearance. When the resource value is set to
XmONEOFMANY, the button has a radio-button appearance. When the value is set
to XmNOFMANY, the button has a check-box appearance.
Tip: On some platforms, toggle buttons turn on when they are given focus.
Hello
Bonjour
Hola
When a button is selected in this mode, any other selected buttons in the group are
automatically deselected, leaving only one button selected at any time. The
radioBehavior resource of the CwRowColumn widget controls this behavior.
Tip: The valueChanged callback is run when a button is deselected as well as when
it is selected. The state of the widget should be checked in the callback using
the getState method, or by checking the set field of the callback data.
The valueChanged callback used by the code is shown below. The selected
language is indicated by the clientData argument. A message is written to the
transcript whenever a new language is chosen.
language: widget clientData:clientData callData: callData
"A toggle button has changed state."
callData set
ifTrue: [Transcript cr; show: 'The selected language is now ',
clientData, '.'].
Check boxes
Check boxes consist of several toggle buttons that present a set of options to the
user. The user can choose none, all, or any combination of the buttons. A
CwRowColumn widget can be used to contain the buttons. When the row-columns
radioBehavior resource is false, its default, more than one toggle button can be
selected at a time.
The code that follows creates the toggle-button group shown on the right.
The valueChanged callback used by the code is shown below. A message is written
to the transcript whenever a button is selected or deselected.
valueChanged: widget clientData:clientData callData: callData
"A toggle button has changed state."
Transcript cr; show: widget labelString, ' has been '.
callData set
ifTrue: [Transcript show: 'selected.']
ifFalse: [Transcript show: 'deselected.'].
?
When the labelType resource is XmSTRING, the labelString resource specifies the
string to display. The default type is string. When labelType is XmICON, labelIcon
specifies the icon to use, and when labelType is XmPIXMAP, labelPixmap specifies
the pixmap to display. Consult Using pixmaps on page 106 for more information
on using pixmaps and icons.
The code below creates a widget tree containing a pixmap button. Icon buttons
and labels are created in a similar manner. Note that pixmap is an instance variable.
| shell button questionMark pixmap |
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Pixmap Button Example'].
button := shell
"Create the push button."
createPushButton: 'button'
argBlock: nil.
button
addCallback: XmNdestroyCallback
receiver: self
selector: #destroy:clientData:callData:
clientData: nil.
questionMark :=
"Initialize the data for the pixmap."
#(0 0 0 0 255 0 192 255 3 224 255 7
240 255 15 248 255 31 248 255 31 252 255 63
252 227 63 252 193 63 252 193 63 252 193 63
248 224 63 248 240 31 0 248 31 0 252 15
0 252 7 0 254 3 0 254 3 0 254 1
0 254 1 0 252 0 0 252 0 0 0 0
0 252 0 0 254 1 0 254 1 0 254 1
0 254 1 0 252 0 0 0 0 0 0 0).
button
setValuesBlock: [:w |
w
shell mapWidget.
pixmap freePixmap.
Application-drawn buttons
Application-drawn button widgets (CwDrawnButton) enable the application to
draw arbitrary graphics on a button. Drawn buttons behave like push buttons
except that they can be drawn on like drawing area widgets. See the example
below.
As with the push-button widget, the application can add an activate callback to be
run when the button is pressed. As with the drawing area widget, expose and resize
callbacks can be added to notify the application when the button requires
redrawing and when it has changed size. Consult Drawing operations on page
89 chapter for more information on drawing graphics.
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Drawn Button Example'].
button := shell
createDrawnButton: 'button'
argBlock: nil.
button
addCallback: XmNactivateCallback
receiver: self
selector: #button:clientData:callData:
clientData: nil;
addCallback: XmNexposeCallback
receiver: self
selector: #expose:clientData:callData:
clientData: nil;
addCallback: XmNdestroyCallback
receiver: self
selector: #destroy:clientData:callData:
clientData: nil.
button manageChild.
shell realizeWidget.
Menus
Menus are used extensively in most graphical user interfaces. Common Widgets
provides protocol for pop-up menus, option menus and menu bars containing
pull-down menus. Menu bars and menus are built using CwRowColumn widgets.
Items in a menu bar are cascade buttons, which are buttons that drop down a
menu when selected. Pop-up menus and pull-down menus within a menu bar are
also represented by CwRowColumn widgets. Items within a menu are separators,
labels, push buttons, toggle buttons, or cascade buttons for submenus. There are
two ways to create menus: using simple menu convenience methods and using
non-simple widget creation methods.
You can query the state of a button by sending it the message sensitive.
The following example creates two buttons, then deactivates one of them. You can
execute this example in a Workspace or the Transcript.
| shell button1 button2 rowColumn |
shell := CwTopLevelShell
createApplicationShell: 'Shell'
argBlock: nil.
rowColumn := shell
createRowColumn: 'buttons'
argBlock: nil.
rowColumn manageChild.
button1 := rowColumn
button2 := rowColumn
createPushButton: 'Two'
argBlock: nil.
button2 manageChild.
shell realizeWidget.
button2 sensitive: false.
Create a simple menu or menu bar using one of the following simple menu
convenience methods:
createSimpleMenuBar:argBlock:
Creates a simple menu bar
createSimplePulldownMenu:argBlock:
Creates a simple pull-down menu
createSimplePopupMenu:argBlock:
Creates a simple pop-up menu
The menu or menu bar is entirely defined by the creation message and resources
set in the argBlock. The resources that can be set to define a simple menu or
simple menu bar are listed in the following table. The first three resources are
arrays whose elements each represent a single menu bar item. These arrays must
contain the same number of items, and must describe corresponding items in the
same sequence, in order for the menu or menu bar to be correctly defined.
Table 37. Simple Menu and Simple Menu Bar Resources
Resource Name Description Value(s)
buttons An array of strings specifying the String
item labels. v String is ignored for
separators, but must be
present.
shell realizeWidget.
fileMenu: widget clientData: clientData callData: callData
"Execute the desired operation."
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w |
w title: 'Secondary Menu Example'].
main := shell
createMainWindow: 'main'
argBlock: nil.
main manageChild.
menuBar := main
createSimpleMenuBar: 'bar'
argBlock: [:w |
w
"File defaults to a cascade button because it is in a menu bar."
buttons: #('File');
buttonMnemonics: #($F)].
menuBar manageChild.
fileMenu := menuBar
createSimplePulldownMenu: 'file'
argBlock: [:w |
w
buttons: #('Open' 'separator' 'Close');
"Open must be explicitly created as a cascade button, because
buttons in a menu default to push buttons."
buttonType: (Array
with: XmCASCADEBUTTON
with: XmSEPARATOR
with: XmPUSHBUTTON);
buttonMnemonics: (Array
with: $O
with: 0 asCharacter
with: $C);
postFromButton: 0].
fileMenu
addCallback: XmNsimpleCallback
receiver: self
selector: #fileMenu:clientData:callData:
clientData: nil.
openMenu := fileMenu
"The secondary pull-down menu is created as a child of fileMenu."
createSimplePulldownMenu: 'open'
argBlock: [:w |
w
buttons: #('Read only' 'Write only' 'Read write');
"The secondary menu is activated when the 0th item from fileMenu is selected
(Open)."
postFromButton: 0].
openMenu
addCallback: XmNsimpleCallback
receiver: self
selector: #openMenu:clientData:callData:
clientData: nil.
main
setAreas: menuBar
horizontalScrollbar: nil
verticalScrollbar: nil
workRegion: nil.
shell realizeWidget
Tip: Some platforms cause pop-up menus to appear on a mouse up event, and
some on a mouse down event. The correct event for the platform must be
used, or unpredictable behavior might result. To ensure that the correct event
is used, specify ButtonMenuMask as the event mask when adding the event
handler.
The open method of the class below opens a window containing a label widget.
When a menu is requested over the label, a pop-up menu (shown at right)
appears. The pop-up menu is created using a simple menu creation method. The
menu is popped up by managing the row-column widget that represents the
menu. It is automatically unmanaged when the menu is canceled by the user.
open
"Open the pop-up menu example."
| shell label |
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Popup Menu Example'].
label:= shell
createLabel: 'main'
argBlock: [:w | w labelString:
'Push the menu button over this message'].
label
"An event handler is added to the label widget. ButtonMenuMask label
indicates that the event handler should be activate when the menu
button on the mouse is pressed or released."
addEventHandler: ButtonMenuMask
receiver: self
selector: #button:clientData:event:
clientData: nil.
label manageChild.
"The pop-up menu is created. Note that the menu is not managed at this
stage. It will be managed when the event handler pops the menu up."
menu := label
createSimplePopupMenu: 'menu'
main := shell
createMainWindow: 'main'
argBlock: nil.
main manageChild.
menuBar := main
"First, a menu bar is created as a child of the main window."
createMenuBar: 'bar'
argBlock: nil.
menuBar manageChild.
shell realizeWidget.
List widgets
List widgets (CwList) present a list of items and allow the user to select one or
more items from the list. List widgets are created using thecreateList:argBlock:and
createScrolledList:argBlock: convenience methods. The latter method makes the list
scrollable, but otherwise provides basically the same functionality. Scrolled lists are
discussed on Scrolled lists on page 188.
The items in the list and the selected items are specified by the items and
selectedItems resources, respectively. The selectionPolicy resource specifies the policy
for selecting items. It has four possible settings:
Tip: On some platforms, browse select and single select work the same way.
List widgets provide several methods for adding, deleting and replacing items and
selected items in the list.
The call data of the selection callback specifies the item or items that were selected,
and the position(s) of the selected item(s) in the list. Item positions in the list are
numbered starting from one.
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: nil.
list := shell
createList: 'list'
argBlock: [:w |
w
selectionPolicy: XmSINGLESELECT;
items: items].
list
addCallback: XmNsingleSelectionCallback
receiver: self
selector: #singleSelect:clientData:callData:
clientData: nil.
list manageChild.
shell realizeWidget.
The call data of the singleSelection callback specifies the item that was selected. The
callback method below prints the entire callback data on the transcript. All
components of the call data can be retrieved using the corresponding accessor
method.
singleSelect: widget clientData: clientData callData: callData
"Print the call data."
Transcript cr; show: 'Single selection call data: ',
callData printString
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: nil.
list := shell
createList: 'list'
argBlock: [:w |
w
selectionPolicy: XmMULTIPLESELECT;
The call data of the multipleSelection callback specifies the items that were selected.
The callback method below prints the entire callback data on the transcript. All
components of the call data can be retrieved using the corresponding accessor
method.
multipleSelect: widget clientData: clientData callData: callData
"Print the call data."
Transcript cr; show: 'Multiple selection call data: ',
callData printString
If Item 2 and Item 3 were selected in order, as in the illustration, the transcript
output would be:
Multiple selection call data: CwListCallbackData(
reason -> 24
item -> 'Item 3'
itemPosition -> 3
selectedItems -> OrderedCollection ('Item 2' 'Item 3')
selectedItemCount -> 2
selectedItemPositions -> OrderedCollection(2 3))
Scrolled lists
A scrolled list is a CwList widget with scrolling capability. All resources and
callbacks associated with lists can be applied to scrolled lists. The scrolling
mechanism is handled automatically.
Item 1
Item 2
Item 3
Item 4
Item 5
Creating a scrolled list inserts a CwScrolledWindow parent between the list and the
receiver of the creation message. In other words, createScrolledList:argBlock: returns
an instance of CwList (the child of a CwScrolledWindow); however, the
CwScrolledWindow is the child of the form. Form attachment messages must
therefore be sent to the CwLists parent.
In the following example, a scrolled list widget is created as a child of a form. The
list selection policy is set to XmSINGLESELECT. A singleSelection callback is added,
corresponding to the selection policy.
| items shell form list |
items := OrderedCollection new.
"20 items are initialized as the list contents."
1 to: 20 do: [:i |
items add: 'Item ', i printString].
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Scrolled List Example'].
Tip: If other children of a form are to be attached to a scrolled list, which is a child
of the form, using XmATTACHWIDGET, they must be attached to the scrolled
lists parent.
Combo-box widgets
Like list widgets, combo-box widgets (CwComboBox) enable the user to select from
a list of available items. A combo box also displays the last selected item in a text
box above the list. Combo-box widgets can only have one item selected at a time.
Combo-box widgets are created using the createComboBox:argBlock: convenience
method.
There are two styles of combo boxes, specified by the comboBoxType resource:
XmDROPDOWN
The list is displayed only when dropped down by pressing a button beside
the text box. When a selection is made, the list disappears (default).
XmSIMPLE
The list is always displayed.
As with the list widget, the items in the combo box are specified by the items
resource. The application can add a singleSelection callback to be run whenever the
selection changes. Several methods are provided for adding, deleting, and
replacing items in the list.
The following example creates the drop down combo box shown at right. Its items
are set, the contents of the text box are initialized to the first item, and a
singleSelection callback is added.
combo := shell
createComboBox: 'combo'
argBlock: [:w |
w
comboBoxType: XmDROPDOWN;
items: items].
combo setString: items first.
combo
addCallback: XmNsingleSelectionCallback
receiver: self
selector: #singleSelect:clientData:callData:
clientData: nil.
combo manageChild.
shell realizeWidget.
| The call data of the singleSelection callback specifies the item that was selected. The
| callback method below prints the entire callback data on the transcript. All
| components of the call data can be retrieved using the corresponding accessor
| method.
| singleSelect: widget clientData: clientData callData: callData
| "Print the call data."
| Transcript cr; show: 'Single selection call data: ',
| callData printString
Composite-box widgets
There are two types of composite-box widgets: CwMessageBox and CwSelectionBox.
Both are subclasses of CwCompositeBox. CwCompositeBox is itself a subclass of
CwBulletinBoard, which provides useful dialog-related resources such as
autoUnmanage, dialogStyle, and dialogTitle.
A message box is usually created in a dialog shell to notify the user of some event.
Message-box widgets are created using the createMessageBox:argBlock: and
createMessageDialog:argBlock: convenience methods. The latter method is similar to
the first, but also creates a CwDialogShell as the parent of the CwMessageBox so that
it can be popped up separately from the main widget tree. As with pop-up menus,
the message-box/dialog shell combination (message dialog) can be created and left
unmanaged until it is to be presented to the user. It is popped up by sending the
message box the manageChild message. The default behavior is to remain open until
either the OK or Cancel button is pressed, or the dialogs close box is
double-clicked. The application can explicitly close the dialog by sending
unmanageChild to the message box.
CwMessageBox Example
Warning: This is a
warning message.
OK Cancel Help
The dialogStyle resource specifies the input mode while the dialog is active, and can
have one of the following values:
XmDIALOGAPPLICATIONMODAL
Used for dialogs that must be responded to before some other interactions
If a platform does not support the specified prompter style, the style is promoted
to the next most restrictive style. If the next most restrictive style is not supported,
the style is demoted to the next less restrictive style.
The widgets comprising the message box can be retrieved using the getChild:
method. If a button or other widget is not required by the application, it can be
retrieved and unmanaged. Possible arguments for the getChild: method are:
v XmDIALOGCANCELBUTTON
v XmDIALOGDEFAULTBUTTON
v XmDIALOGHELPBUTTON
v XmDIALOGMESSAGELABEL
v XmDIALOGOKBUTTON
v XmDIALOGSEPARATOR
v XmDIALOGSYMBOLLABEL
The okCallback, cancelCallback, and helpCallback of the message box are called when
the corresponding buttons are pressed. If a callback is not added, the
corresponding button will still appear, but nothing will happen when it is pressed.
If the message box is in a dialog shell, it is unmanaged when the OK or Cancel
button is pressed, unless the autoUnmanage resource is set to false.
The following code creates an application modal information message dialog that is
popped up when a button is pressed. Only the OK button of the message dialog is
shown. The remaining ones are hidden (unmanaged). A callback is added for the
OK button. Because the dialog is application modal, the user must close the dialog
before the application can receive further input.
open
| shell button | OK
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Message Dialog Example'].
button := shell
createPushButton: 'Push me for dialog'
argBlock: nil.
button
addCallback: XmNactivateCallback
receiver: self
selector: #button:clientData:callData:
clientData: nil.
button manageChild.
messageBox := shell
createMessageDialog: 'message'
argBlock: [:w |
w
dialogTitle: 'Message Dialog';
dialogStyle: XmDIALOGFULLAPPLICATIONMODAL;
messageString: 'This is a message dialog.';
dialogType: XmDIALOGINFORMATION].
messageBox
addCallback: XmNokCallback
receiver: self
selector: #ok:clientData:callData:
clientData: nil.
(messageBox getChild: XmDIALOGCANCELBUTTON) unmanageChild.
(messageBox getChild: XmDIALOGHELPBUTTON) unmanageChild.
shell realizeWidget
button: widget clientData: clientData callData: callData
"The button has been pressed. Pop up the message dialog."
messageBox manageChild
ok: widget clientData: clientData callData: callData
Transcript
cr; show: 'The OK button in the dialog has been pressed.';
cr; show: 'The dialog is about to disappear.'.
SelectionBox widgets
Selection-box widgets (CwSelectionBox) are used to obtain a selection from a list of
alternatives provided to the user. A selection box can contain some or all of the
following components: a scrollable list of alternatives, an editable text field for the
selected alternative, labels for the list and text field, and four buttons. By default,
the buttons are labelled OK, Apply, Cancel and Help.
A selection box is usually created in a dialog shell to prompt the user to select a
list item. Selection-box widgets are created using the createSelectionBox:argBlock: and
createSelectionDialog:argBlock: convenience methods. The latter method is similar to
the first, but also creates a CwDialogShell as the parent of the CwSelectionBox so that
it can be popped up separately from the main widget tree. As with pop-up menus
a selection dialog can be created and left unmanaged until it is to be presented to
the user. It is popped up by sending the selection box the manageChild message.
When the selection box is in a dialog shell, the dialogTitle resource specifies the title
of the dialog shell, and the dialogStyle resource specifies the input mode while the
dialog is active.
The labels for the list widget and the text widget are set using the
selectionLabelString: and textLabelString: methods respectively.
CwSelectionBox Example
Select a bitmap file:
stars.bmp
stripes.bmp
cats.bmp
dogs.bmp
Desktop background:
cats.bmp
The list contents can be set or retrieved using the listItems resource. The text
contents can be set or retrieved using the textString resource.
The widgets comprising the selection box can be retrieved using the getChild:
method. If a button or other widget is not required by the application, it can be
retrieved and unmanaged. Possible arguments to the getChild: method are:
v XmDIALOGAPPLYBUTTON
v XmDIALOGCANCELBUTTON
v XmDIALOGDEFAULTBUTTON
v XmDIALOGHELPBUTTON
v XmDIALOGLIST
v XmDIALOGLISTLABEL
v XmDIALOGOKBUTTON
v XmDIALOGSELECTIONLABEL
v XmDIALOGSEPARATOR
v XmDIALOGTEXT
The okCallback, applyCallback, cancelCallback, and helpCallback of the selection box are
run when the corresponding buttons are pressed. If a callback is not added, the
corresponding button will still appear, but nothing will happen when it is pressed.
When the selection box is in a dialog shell, it is unmanaged when the OK or
Cancel button is pressed, unless the autoUnmanage resource is set to false.
Using the mustMatch resource, the selection box can be configured to test whether
the text typed in the text widget matches any item in the list. If mustMatch is true
and the text does not match any item in the list when the OK button is pressed,
the noMatch callback is run, otherwise the ok callback is run. Note that if the
noMatch callback is run, a selection dialog will not be unmanaged.
The code below creates the following modeless selection dialog that is popped up
when a button is pressed. The name of the Apply button has been changed to
Show, and the Help button is hidden.
Selection Dialog
Items:
Item 1
Item 2
Item 3
Item 4
Item:
Item 2
OK Show Cancel
The mustMatch resource and noMatch callback are used to test when the text does
not match any item in the list. Because the dialog is modeless, the user can click or
type in any other widget without having to close the dialog first.
Object subclass: #SelectionDialogExample
instanceVariableNames: 'selectionBox '
classVariableNames: ''
poolDictionaries: 'CwConstants'
open
| items shell button |
addCallback: XmNapplyCallback
receiver: self
selector: #show:clientData:callData:
clientData: nil;
addCallback: XmNcancelCallback
receiver: self
selector: #cancel:clientData:callData:
clientData: nil;
addCallback: XmNnoMatchCallback
receiver: self
selector: #noMatch:clientData:callData:
clientData: nil.
(selectionBox getChild: XmDIALOGHELPBUTTON) unmanageChild.
shell realizeWidget
button: widget clientData: clientData callData: callData
"The button has been pressed. Pop up the selection dialog."
selectionBox manageChild
ok: widget clientData: clientData callData: callData
"The OK button in the dialog has been pressed.
The dialog will be automatically unmanaged."
Transcript cr; show: 'The final selection is: ' , widget textString
show: widget clientData: clientData callData: callData
"The Show button in the dialog has been pressed.
The dialog will NOT be automatically unmanaged."
Transcript cr; show: 'The selection does not match any item in the list.'
The modality of a prompter can be changed using the prompterStyle: method. Valid
prompter styles, in order of restrictiveness, are:
XmPRIMARYAPPLICATIONMODAL
The user must respond to the prompter before being able to resume
interaction with the receivers parent window. Interaction with other
windows is still possible.
XmFULLAPPLICATIONMODAL
The user must respond to the prompter before being able to resume
interaction with any window belonging to the receivers application.
Interaction with windows from other applications is still possible.
XmSYSTEMMODAL
The user cannot interact with the windows of any application until
responding to the prompter.
If a platform does not support the specified prompter style, the style is promoted
to the next most restrictive style. If no more restrictive style exists, the style is
demoted to the next less restrictive style.
The title of the prompter can be set using the title: method.
A prompter is popped up using the prompt method. Control is not returned to the
application until the user responds to the prompter. The value returned by the
prompt method is nil if the user cancels the prompter. If the user replies to the
prompter without cancelling, the value returned depends on the type of prompter.
Message prompter
A CwMessagePrompter displays a message to the user and waits for a button to be
pressed. A message prompter from the OS/2 platform is illustrated below.
The buttons displayed by the prompter can be changed using the buttonType:
method. Valid values are:
XmOK
OK button only
The button selected by default if the user presses the return key can be changed
using the defaultButtonType:method. Valid values are:
XmDEFAULTBUTTON1
The first button is the default (default setting)
XmDEFAULTBUTTON2
The second button is the default
XmDEFAULTBUTTON3
The third button is the default
The icon displayed in the message box can be changed using the iconType: method.
Valid values are:
XmNOICON
No icon
XmICONINFO
Info icon (default setting)
XmICONWARNING
Warning icon
XmICONERROR
Error icon
XmICONQUESTION
Question icon
The prompt method returns one of the following values, depending on which
button was pressed:
true The OK, Yes, or Retry button was pressed
false The No or Abort button was pressed
nil The Cancel or Ignore button was pressed
The message displayed by a text prompter is set using the messageString: method.
The initial contents of the text box for the users answer can be set using the
answerString: method.
The prompt method returns nil if the prompter was cancelled, or the contents of the
text box if the user pressed the return key or the OK button.
File: Directory:
AUTOEXEC.BAT C:\
COMMAND.COM BIN
CONFIG.001 COMPLIB
CONFIG.SYS DOS5
OK Cancel
The prompt method returns nil if the prompter is cancelled. If a file is properly
selected, a string specifying the full path name of the file is returned.
The file selection prompter shown above is created by the following code.
| reply |
reply :=
CwFileSelectionPrompter new
title: 'File Selection Prompter Example';
searchPath: 'c:\';
searchMask: '*.*';
prompt.
Extended widgets
Common Widgets provides a framework for developing custom widgets based on
existing widgets. These are called extended widgets. If the IBM Smalltalk portable
API is used to develop an extended widget, it will be portable between all
platforms supported by IBM Smalltalk. Extended widgets are often implemented
using a CwDrawingArea, with its visual appearance drawn using Common
Graphics calls, and with user input processed using event handlers.
CwWidget
CwBasicWidget
CwComposite
CwPrimitive
CwShell
CwExtendedWidget
CwExtendedComposite
CwExtendedPrimitive
After the subclass has been created, it should define an instance variable for each
resource and callback provided by the extended widget, as well as instance
variables required for any other aspects of the widgets implementation.
Initialization
Three methods can be overridden to initialize the state of the widget. The initialize
method is run as the first step in extended widget creation. It is useful for
initializing the internal state of the widget, except for resources. The
initializeResources method initializes the instance variables representing resources.
Both of these methods are run before the primary widget tree has been created.
The initializeAfterCreate method is run after the primary widget tree has been
created. It is useful for configuring widgets after they have been created, and for
initializing graphics resources.
Resources
Set and get accessor methods should be added for each resource provided by the
extended widget. Usually, the get method simply answers the corresponding
instance variable. The set method usually sets the corresponding instance variable
and makes any required changes in the primary widget tree.
The get accessor method for a callback is a little more involved. In order to work
properly with methods inherited from CwExtendedWidget, the constant used to
specify the type of the callbackfor example, XmNactivateCallback for an activate
callbackis a pool variable that must be equal to the get methods selectorfor
example, activateCallback. The valueChangedCallback method in the example is a
callback get accessor. Callback type specifiers are already defined in the
CwConstants pool dictionary, however, if a new name is desired, it can be added to
an application-specific pool dictionary using the same naming convention.
The get accessor must answer an ordered collection, to which callback descriptors
are added whenever a callback is registered. If the callback resource is
uninitialized, the get method must set the callback resource to a new
OrderedCollection, and answer that.
Widget-specific methods
An extended widget works by forwarding most widget messages to its primary
widget. All of the methods inherited from CwWidget are automatically forwarded
to the primary widget if they are not explicitly overridden. In simple cases, an
extended widgets behavior can be implemented simply by adding resource and
callback methods as described above. For more complicated widgets, it is usually
necessary to extend the basic widget protocol by providing methods to support the
new operations on the extended widget.
The CewEntryField widget, shown below, has a label on the left and a text box on
the right. It allows a user to enter text into its text box, and it invokes a
valueChanged callback if a new value is present when the user either hits the tab
key or clicks on a different widget.
The extended widget is implemented using a CwForm as the primary widget with
CwLabel and CwText children. A losingFocus callback on the CwText enables the
|self parent
createForm: theName, 'Form'
argBlock: argBlock
createWidgetSystem
"Private - Create the children of the receiver's primary widget which form the
widget hierarchy."
| pw |
pw := self primaryWidget.
self labelWidget:
(pw
createLabel: pw name , 'Label'
argBlock: [:w | w labelString: self label])
manageChild.
self textWidget:
(pw
createText: pw name , 'Text'
argBlock: [:w |
w
borderWidth: 1;
value: self value])
manageChild.
self textWidget
addCallback: XmNlosingFocusCallback
receiver: self
selector: #losingFocus:clientData:callData:
clientData: nil.
"Add form attachments for the two children."
self labelWidget
setValuesBlock: [:w |
w
topAttachment: XmATTACHFORM;
leftAttachment: XmATTACHFORM;
rightAttachment: XmATTACHNONE;
bottomAttachment: XmATTACHFORM].
self textWidget
setValuesBlock: [:w |
w
topAttachment: XmATTACHFORM;
leftAttachment: XmATTACHWIDGET;
leftWidget: self labelWidget;
rightAttachment: XmATTACHFORM;
bottomAttachment: XmATTACHFORM].
initializeResources
"Private - Set the default extended widget resource values. This is sent
during create with isCreated set to false. All extended resource
variables should be initialized to default values here."
label := String new.
value := String new.
"Resources that involve the children of the primary widget have to be set
after the children are created."
self labelWidget labelString: label.
self textWidget value: value.
label
"Answer the value of the label resource.
Resource type: String
Default setting: ''
Resource access: CSG
Description:
Specifies the string for the CewEntryField's label.
This label is to the left of the CewEntryField's text box."
|label
label: resourceValue
"Set the value of the label resource to resourceValue.
Resource type: String
Default setting: ''
Resource access: CSG
Description:
Specifies the string for the CewEntryField's label.
This label is to the left of the CewEntryField's text box."
label := resourceValue.
self isCreated
ifTrue: [labelWidget labelString: resourceValue]
value
"Answer the value of the value resource.
Resource type: String
Default setting: '
Resource access: CSG
Description:
Specifies the string for the CewEntryField's value.
This value is displayed in the CewEntryField's text box if
set using the #value: message, or if a valid string followed
by a tab key is entered by the user.
Note that while the user is typing, the string displayed in the
text box might not be the same as the CewEntryField's value."
|value
value: resourceValue
"Set the value of the value resource to resourceValue.
Resource type: String
Default setting: '
Resource access: CSG
Description:
Specifies the string for the CewEntryField's value.
This value is displayed in the CewEntryField's text box if
set using the #value: message, or if a valid string followed
by a tab key is entered by the user.
Note that while the user is typing, the string displayed in the
text box might not be the same as the CewEntryField's value."
value := resourceValue.
self isCreated
ifTrue: [textWidget value: resourceValue]
valueChangedCallback
"Private - Answer the value of valueChangedCallback."
valueChangedCallback isNil
ifTrue: [self valueChangedCallback: OrderedCollection new].
|valueChangedCallback
valueChangedCallback := resourceValue.
labelWidget
"Private - Answer the value of labelWidget."
|labelWidget
labelWidget: aCwLabel
"Private - Set the value of labelWidget to aCwLabel."
labelWidget := aCwLabel.
textWidget
"Private - Answer the value of textWidget."
|textWidget
textWidget: aCwText
"Private - Set the value of textWidget to aCwText."
textWidget := aCwText.
losingFocus: widget clientData: clientData callData: callData
"Private - Process a losing focus callback for the primary widget."
| newValue |
shell := CwTopLevelShell
createApplicationShell: 'CewEntryField Test'
argBlock: nil.
entryField := CewEntryField
createManagedWidget: 'entryField'
parent: shell
argBlock: [:w |
w
label: 'Name :';
value: 'Your name here'].
Transcript cr; show: 'Value changed to: ' , callData value printString.
| newValue |
Direction
Up aCewTitleFrame
Down with a 2-button radio box child
|parent
createDrawingArea: theName , 'DrawingArea'
argBlock: argBlock
initialize
"Private - Perform any private widget-specific state initialization. This is sent
before any other initialization begins. borderInset, radius, and angles are needed
for drawing the frame. lineSegments is set to nil to ensure that the lineSegments
and arcOrigins collections are calculated on first expose. childInset used to size
child in resize callback."
self
radius: 15;
angles: #(90 0 270 180);
borderInset: (CgFontStruct default height) // 2 + 4;
childInset: self borderInset * 2.
initializeResources
"Private - Set the default extended widget resource values. This is sent during
create with isCreated set to false. All extended resource variables should be
initialized to default values here."
title := String new.
initializeAfterCreate
"Private - Perform any widget-specific post-create initialization."
self primaryWidget
marginHeight: self childInset;
marginWidth: self childInset;
addCallback: XmNexposeCallback
receiver: self
selector: #expose:clientData:callData:
clientData: nil;
addCallback: XmNresizeCallback
receiver: self
selector: #resize:clientData:callData:
clientData: nil.
title
"Answer the value of the title resource.
Resource type: String
Default setting: ''
Resource access: CSG
Description:
Specifies the string for the CewTitleFrame's title.
This title is displayed in the upper left portion of the
rounded-corner rectangle that frames the child widget."
|title
title: resourceValue
"Set the value of the title resource to resourceValue.
Resource type: String
Default setting: ''
Resource access: CSG
Description:
Specifies the string for the CewTitleFrame's title.
This title is displayed in the upper left portion of the
rounded-corner rectangle that frames the child widget."
title := resourceValue.
borderInset
"Private - Answer the value of borderInset."
|borderInset
borderInset := anInteger.
childInset
"Private - Answer the value of childInset."
|childInset
childInset: anInteger
"Private - Set the value of childInset to anInteger."
childInset := anInteger.
radius
"Private - Answer the value of radius."
|radius
radius: anInteger
"Private - Set the value of radius to anInteger."
radius := anInteger.
angles
"Private - Answer the value of angles."
|angles
angles: anArray
"Private - Set the value of angles to anArray."
angles := anArray.
lineSegments
"Private - Answer the value of lineSegments."
|lineSegments
lineSegments: anOrderedCollection
"Private - Set the value of lineSegments to anOrderedCollection."
lineSegments := anOrderedCollection.
arcOrigins
"Private - Answer the value of arcOrigins."
|arcOrigins
arcOrigins: anArray
"Private - Set the value of arcOrigins to anArray."
arcOrigins := anArray.
gc
"Private - Answer the value of gc. Create if not already created."
gc isNil
ifTrue: [self initializeGraphics].
|gc
gc: aCgGC
"Private - Set the value of gc to aCgGC."
gc := aCgGC
initializeGraphics
"Private - Set the receiver's palette and create a GC. This method is called
by the #gc method if gc is nil."
| pw colors |
pw := self primaryWidget.
colors := Array
with: pw backgroundColor "pixel 0"
with: pw foregroundColor. "pixel 1"
widget window
drawSegments: self gc segments: self lineSegments.
self arcOrigins
with: self angles do: [ :p :angle |
widget window
drawArc: self gc
x: p x
y: p y
width: diam
height: diam
angle1: 64 * angle
angle2: 64 * 90 ].
widget window
drawImageString: self gc
x: offset + 20
y: border + (widget display defaultFontStruct ascent // 2)
string: ' ' , self title , ' '.
titleFrame := CewTitleFrame
createManagedWidget: 'titleFrame'
parent: shell
argBlock: [:w | w title: 'Direction'].
(radioBox := titleFrame
createRadioBox: 'radio'
argBlock: nil)
manageChild.
(radioBox
createToggleButton: 'Up'
argBlock: [:w | w set: true])
manageChild.
(radioBox
createToggleButton: 'Down'
argBlock: nil)
manageChild.
shell realizeWidget
Fonts
The font used by certain widgets can be specified by the application. The following
widgets allow their font to be changed: CwLabel, CwPushButton, CwToggleButton,
CwCascadeButton, CwText, CwList, CwComboBox, and CwScale. The font is changed
using the fontList: method. The font to use is specified by a CwFontList object.
text := shell
createText: 'text'
argBlock: [:w | w
editMode: XmMULTILINEEDIT;
fontList: fontList].
text setString: 'This text is displayed using the 8x13 font.'.
text manageChild.
shell realizeWidget
You can change the browser font from the File menu. If the browser font has not
been changed, then the EtWindow class method fontName returns nil. If your
window will use the browser font, then you can make the window a subclass of
EtWindow. Your subclass should provide the instance method fontSettableWidgets,
which answers a collection of all the widgets to be notified in case the font
changes. EtWindow calls all of these widgets for you and tells them to change to
the new font.
You can still use the browser font, even if your window does not subclass
EtWindow. The following example creates a new window with the system font. The
class method fontList in EtWindow returns either the current CwFontList, or nil if the
font has not been changed.
|shell text fontList|
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Browser Font' ].
The following code creates a multiline text widget and sets its foregroundColor to
black and its backgroundColor to blue.
| shell text |
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Color Example'].
text := shell
createText: 'text'
argBlock: [:w | w
editMode: XmMULTILINEEDIT;
columns: 50;
rows: 4;
value: 'Hello';
foregroundColor: CgRGBColor white;
backgroundColor: (shell screen lookupColor: 'blue')].
text manageChild.
shell realizeWidget
Clipboard operations
Common Widgets provides operations to interact with the platform clipboard,
which holds data to be transferred between applications. An application can copy
data from Smalltalk to the clipboard, and paste data from the clipboard into
Smalltalk.
Data stored in the clipboard has an associated format name, which applications use
to identify the type of data. Common Widgets automatically handles two
predefined formats, string and pixmap, which are mapped to the corresponding
platform-specific format during clipboard operations. Applications can also work
with platform-specific formats, or define new proprietary formats. The format
names and corresponding buffer object classes used to represent data when
working with the clipboard include the following:
STRING
String
PIXMAP
CgPixmap
All others
ByteArray
Platform-integrated drag and drop involves two sides: the source and the
destination. The source and destination can be the same application or different
applications. For example, a user can drag from a widget that is part of application
A and drop on that same widget, on another widget in application A, or on a
widget in application B.
When a user performs a valid drop, data is transferred from the source to the
destination. In Common Widgets drag and drop, data transfer can be done at
several levels of complexity. The levels of data transfer include the following,
beginning with the simplest:
v Data transfer using built-in types. For example, String data can be transferred
using STRING and CgPixmap data can be transferred using PIXMAP.
v ByteArray data transfer. This is the default.
v Custom data transfer, which enables you to add to the set of built-in types.
To complete the first two levels of data transfer, you do not need to write
platform-specific code. These types of data transfer are described in this section. To
Target types
A target type is a string that represents the type of data transfer to perform when
a valid drop occurs. Target types are equivalent to format names, which are used
by the Common Widgets clipboard. Common Widgets drag and drop provides two
built-in target types that are supported on all platforms:
STRING
Specifies that a String is transferred
PIXMAP
Specifies that a CgPixmap is transferred
Also can also use their own target types, for example, EMPLOYEE. When
Common Widgets drag and drop encounters a target type that is not built in, a
ByteArray is transferred. An application must provide a ByteArray on the source
side, and interpret the ByteArray data on the destination side. ByteArray data
transfer is a flexible way to transfer data without writing any platform-specific
code.
Transferring data
Three types of data transfers can be performed when a valid drop occurs: move
(XmDROPMOVE), copy (XmDROPCOPY), or link (XmDROPLINK). The type of
transfer chosen depends on the advertised capabilities of each side involved in the
transfer and on the state of the keyboard at the time of the drop. If no type of
transfer can be chosen (XmDROPNOOP), then the drop is invalid. The user can
affect the type of transfer chosen by pressing the following keys while dragging:
Shift Forces a move if both sides support moving, otherwise no type of transfer
can be chosen
Ctrl Forces a copy if both sides support copying, otherwise not type of transfer
can be chosen
Ctrl+Shift
Forces a link is both sides support linking, otherwise no type of transfer
can be chosen
If no key is pressed while dropping, then the system chooses the first of move,
copy, or link (in that order) that is supported by both sides. If no type of transfer is
supported by both sides, then no type of transfer can be chosen.
Applications must ensure that the semantics of the data transfer are consistent with
the following conventions:
Move The destination first gets a copy of the data, then tells the source to delete
the data.
Copy The source and destination both keep a copy of the data.
Link The source keeps the only copy of the data, and the destination establishes
a link to that copy.
Procs
A proc is like a callback. It can be hooked to a drag and drop object to notify an
application of various events in the drag and drop process. A proc is specific to the
type of object and is represented by an instance of CwCallbackRec. When called, the
third parameter sent to a proc is a callData object created by Common Widgets
drag and drop.
Unlike callbacks, only one of each type of proc can be hooked to a drag and drop
object at any given time, and most procs are mandatory. A proc must be hooked in
the create argBlock using a proc resource setter. The first parameter sent to a proc
is a drag and drop object or a widget, depending on the proc.
CwDragDropObject hierarchy
CwDragDropObject
CwDragContext
CwDropSite
CwDropTransfer
CwDropTransferEntry
Note: The mouse button that initiates a drag is platform dependent. On Windows
platforms, the left mouse button triggers a dragDetectCallback. On OS/2, the
right mouse button triggers it. And, on OSF/Motif platforms, the middle
mouse button triggers it.
Some platforms allow dragging with a button other than those listed above.
To implement this behavior, applications call dragStart:argBlock: from a
button event handler.
On platforms that allow the left mouse button to initiate a drag, normal drag
selection might cause the dragDetectCallback to be amibiguous in certain widgets,
such as text and list widgets. In these cases, the callData reason will be
XmCRDRAGSELECT and the application can decide whether to allow the drag
selection to continue, or set the callData doit to false and start a drag.
OSF/Motif platforms provide default drag behavior in text, label, and button
widgets. Common Widgets drag and drop turns off this behavior in a widget when
a dragDetect callback is added.
Note: The dragStart:argBlock: message must only be sent from a dragDetect callback
handler or a button press event handler. The first parameter must be the
button press event that initiated the drag.
The following drag context resources can be set in the create argBlock when the
drag starts:
sourceCursorAll
The CgIcon that replaces the mouse cursor during dragging.
dragOperations
The operations that the drag source supports.
exportOperations
The target types that the drag source supports, in order of source
preference with the most complete and accurate representation of the data
first.
convertProc
A proc that is received one or more times after a valid drop, in which the
The following code shows a typical dragDetect callback handler. It starts a drag,
specifying the operations and targets that the drag source supports and hooking a
convertProc to handle the source side of the data transfer. Assume that self dragIcon
answers a CgIcon.
dragDetectCallback: aCwWidget clientData: clientData callData: callData
"Handle the dragDetect callback for the receiver."
| startDrag |
(startDrag := callData reason = XmCRDRAG)
ifFalse: [
"The reason is XmCRDRAGSELECT.
Decide if the user is doing a drag or a select."
(self checkDragSourceCoordinates: callData event x @ callData event y)
ifTrue: [
"Interpret the mouse action as a drag operation
rather than a selection operation. Set the doit flag to false to
prevent the widget from doing normal selection processing."
callData doit: false.
startDrag := true]].
startDrag
ifTrue: [
aCwWidget
dragStart: callData event
argBlock: [:dragContext |
dragContext
sourceCursorIcon: self dragIcon;
dragOperations: XmDROPMOVE | XmDROPCOPY;
exportTargets: #('STRING');
convertProc: (CwCallbackRec
receiver: self
selector: #convertProc:clientData:callData:
clientData: aCwWidget)]].
The following code registers the specified widget as a drop site. This establishes
the operations and targets that the drop site supports, as well as hooking a
dragProc and a dropProc:
registerDropSite: aCwWidget
"Register the specified widget as a drop site."
aCwWidget
dropSiteRegister: [:dropSite |
dropSite
dropSiteOperations: XmDROPMOVE | XmDROPCOPY;
importTargets: #('STRING');
dragProc: (CwCallbackRec
receiver: self
selector: #dragProc:clientData:callData:
clientData: nil);
dropProc: (CwCallbackRec
receiver: self
selector: #dropProc:clientData:callData:
clientData: nil)].
The following code shows a dragProc handler which does drag-under animation
based on the coordinates of the mouse in the callData, and chooses the operation
based on the mouse coordinates and the valid operations in the callData. Assume
the following:
v dropRectanglesFor: answers a collection of rectangles that are acceptable drop
areas in the given widget.
v operationForRectangle:widget:callData: answers the operation chosen by the
destination application, given a drop rectangle in the widget, and the operation,
operations and dropSiteStatus of the callData.
v animateRectangle:widget:operation: does drag-under animation for a drop rectangle
in a given widget, based on the selected operation.
v removeAllAnimation: removes all drag-under animation effects from the given
widget.
dragProc: widget clientData: clientData callData: callData
"Handle the drag proc for the receiver."
| point rectangle op |
Note: Some platforms allow the user to press the help key (usually F1) during a
drag. In this case, a drop proc is sent with the dropAction field in the callData
set to XmDROPHELP.
Two different dropProc handlers are shown in the following code. Both examples
call startTransfer:targetTypes:clientData:. The code for this method is shown in the
following section on the CwDropTransfer object.
The first example shows a very simple dropProc handler. This drop site only
supports the STRING target type. If the dropProc is called, the drop site knows
that the operation is valid and the drag source also supports STRING, therefore it
can simply ask for the data in STRING format.
dropProc: widget clientData: clientData callData: callData
"Handle the drop proc for the receiver."
callData dropAction = XmDROPHELP
ifTrue: [
"Help is not supported, therefore this is an invalid operation."
|callData dropSiteStatus: XmDROPSITEINVALID].
"A valid drop has occurred. Start a transfer, requesting our only target type."
self startTransfer: callData targetTypes: #('STRING') clientData: widget.
The second example dropProc is more complex. This drop site supports more than
one target type. In addition, the drop site cares about the semantic location of the
mouse, as did the example dragProc in the previous subsection, and it may decide
that the operation is invalid. Assume that intersectionOf: answers an
OrderedCollection containing the intersection of two collections, and that
dropRectanglesFor: and operationForRectangle:widget:callData: are the same methods
used in the dragProc example.
dropProc: widget clientData: clientData callData: callData
"Handle the drop proc for the receiver."
| point rectangle op exportTargets importTargets intersection |
A destination can add target types to the list of requests during the transfer
process if necessary, permitting it to change what it requests based on data
received. If the operation was a move, the destination must add the special
DELETE target to the request list, which the system will send to the convertProc,
so that the source knows when it is safe to delete the data.
The following example method was called from the dropProc example code in the
previous subsection. It creates a collection of CwDropTransferEntry objects using the
specified targetTypes and clientData, and starts the data transfer with these.
The following code shows a typical convert proc. Assume that stringFromWidget:
answers the selected string in the specified widget, and deleteStringFromWidget:
deletes the selected string from the specified widget.
Note: You can use the clientData parameter to find out the drag source widget
inside the convert proc.
convertProc: aCwDragContext clientData: dragSource callData: callData
"Handle the convert proc for the receiver."
| target |
(target := callData target) = 'STRING'
ifTrue: [
"Convert to requested type, and set value."
callData value: (self stringFromWidget: dragSource)]
ifFalse: [
target = 'DELETE'
ifTrue: [
"The operation was XmDROPMOVE, and the destination
successfully received the data. Delete the data from the source."
self deleteStringFromWidget: dragSource]].
Note: A convertProc is usually sent to the source application, and then the
corresponding transferProc is sent to the destination application. On some
platforms, the convertProc is called for all of the dragContexts export targets
when the drag starts. Thus, applications should not count on the order of
convertProc and transferProc calls.
Note: You can use the clientData parameter to find the dropProcs callData inside the
transfer proc. The callData is usually necessary to know because it contains
the operation and the coordinates of the mouse at the drop.
transferProc: aCwDropTransfer clientData: dropProcCallData callData: callData
"Handle the convert proc for the receiver."
callData value isNil ifTrue: [
"Conversion failed. The source did not pass any data."
|self errorMessage: 'Conversion failed.'].
callData target = 'STRING'
ifTrue: [
self
stringToWidget: callData transferClientData
string: callData value
x: dropProcCallData x
y: dropProcCallData y
link: (dropProcCallData operation = XmDROPLINK)].
"If this was a move operation, and if we have just received the
last dropTransfer, then send a 'DELETE' target to the drag
source's convert proc."
(dropProcCallData operation = XmDROPMOVE
and: [callData target = aCwDropTransfer dropTransfers last target])
ifTrue: [
aCwDropTransfer
dropTransferAdd: (CwDropTransferEntry
target: 'DELETE'
transferClientData: callData transferClientData)].
With Common Widgets, only the single Smalltalk user interface process is
permitted to dispatch events or directly perform user interface operations.
Common Widgets facilitates a proactive approach to event management, as
opposed to a defensive one. Rather than write code to defend themselves against
asynchronous user interface events, such as exposes, menu operations, or user
input, application developers control the user interface event processing. Event
processing is fully synchronous from the perspective of the user interface process,
although it can be asynchronous from the perspective of non-UI processes.
The message loop makes use of two methods defined in class CwAppContext:
readAndDispatch
Reads a single event, if one is available, from the underlying operating
system, dispatches it to the appropriate widget, and handles any callbacks
that occur. In addition, it handles any pending requests for user interface
operations by non-UI processes, as shown below. Finally, it returns true if
an event was processed and false otherwise.
sleep Checks for user interface activity, and if none, removes the UIProcess from
the ready-to-run queue. The system assumes there is user interface activity
in the following cases:
v There are events to process
v There are background user interface requests to be run
v There are work procs registered
v There are timer procs registered
As long as there is any activity in the user interface, the UIProcess will continue to
poll for events as quickly as possible. As soon as the activity stops, the UIProcess
becomes inactive and suspends. This enables any other Smalltalk processes that are
running at the the same or lower priority than the UIProcess to execute.
If the underlying operating system does not provide any mechanism for triggering
user-written code when events become available, the CwAppContext can still
function by generating a Smalltalk process that wakes the UIProcess at regular
intervals. By default, this is called the CwAsyncIOProcess.
As soon as input is available, both the IBM Smalltalk system and the UIProcess are
reactivated, because they are higher priority than the idle process.
If the operating system does not provide a facility for suspending execution of an
application until input is available, the suspendSmalltalk method simply returns to
Note that there is an interaction between the suspendSmalltalk method and the
Smalltalk Delay class. If the idle process runs because a higher priority process has
suspended on a Delay, the system must be reactivated when the Delay expires. This
situation is handled in one of three ways depending on the capabilities of the
operating system:
v With operating systems where the Delay class uses the same mechanism that
suspendSmalltalk uses to detect input activity, the system is reactivated with no
further intervention.
v With operating systems where Delay uses a different mechanism than
suspendSmalltalk, but it is possible for a user written application to post a user
interface event, this facility is used to reactivate the system.
v If neither of the above mechanisms are available, IBM Smalltalk checks for
Delays and deactivates the system only when there are none pending.
Unfortunately, OSF/Motif itself does not use a multithreaded model and thus it is
not possible for multiple application processes to concurrently execute user
interface code, for example, redrawing a widget, performing an individual
graphical operation, or reading an event. Some kind of synchronization is
necessary.
Tip: A background (non-UI) process must never make widget or graphics requests
directlyit must use syncExecInUI: or asyncExecInUI: to ask the user interface
to issue the request on its behalf.
asyncExecInUI
Processes with higher priority than the UIProcess do not block when this method is
run. In this case, aBlock is run the next time the UI becomes active and reaches a
clean point. Processes at the same or lower priority than the UIProcess normally
block until aBlock has been run, but this is not guaranteed.
The process that runs this message is not provided any indication of when aBlock
has been run. In particular, it is not guaranteed that aBlock has been run when the
method returns.
If this message is sent by code that is executing in the UIProcess, then aBlock is run
after all previously queued background user interface requests have been run.
Tip: A background process can reawaken the UIProcess and cause a context switch
(because the UIProcess is higher priority) by executing: CwAppContext default
asyncExecInUI: []
syncExecInUI:
Execution of the process that evaluates this method always suspends until aBlock
has been evaluated.
If this message is sent by code that is executing in the UIProcess, the block is run
immediately.
Both of these methods are implemented to add the code to be run, aBlock, to a
collection of background user interface requests maintained by the CwAppContext.
This collection is FIFO ordered so operations can use syncExecInUI: to ensure that
all previously sent asyncExecInUI: messages have been processed.
After adding the request, both of the above methods reactivate the UIProcess if it
was sleeping. As was described in the previous section, the CwAppContext
processes the background user interface requests as part of the normal functioning
The processing of active work procs and timer procs prevents the user interface
process from suspending, and background processes will not run until all work
procs and timer procs have been removed. However, the enableProcs: message in
CwAppContext can be used to temporarily disable work proc and timer proc
processing in order to allow the UIProcess to suspend.
As with background user interface requests, work procs and timer procs are only
processed by the UIProcess during readAndDispatch processing. If the UIProcess is
compute bound, no work procs or timer procs will be processed until the UIProcess
returns to the event loop.
Note: Keep the amount of work done in a work proc or timer proc to a minimum.
If they are too long, they keep the UIProcess from returning to the event
loop, which can cause user interface responsiveness to degrade.
Long-running operations should be coded in background tasks that use
asyncExecInUI: or syncExecInUI: to access the user interface.
addWorkProc:receiver:selector:clientData:
This message adds a work proc for idle UI processing, and returns an object which
uniquely identifies the work proc. This object can later be used to remove the work
proc using removeWorkProc:. The 1-parameter message defined by selector is
repeatedly sent to receiver (with clientData as the parameter) while the event loop
is idle waiting for events. Multiple work procs may be added, and the last one
added is called first. If the work proc message returns true, then it is automatically
removed and will not be called again.
addTimeOut:receiver:selector:clientData:
This message adds a timer proc which is sent after the specified number of
milliseconds have elapsed, and returns an object which uniquely identifies the
timer proc. As with work procs, this object can be used later to remove the timer
proc before it expires if it is no longer needed. Multiple timer procs can be added,
Note: Because timer procs are only sent when the UI is idle, they may not expire
at the exact time specified. They are only guaranteed to expire after the
given number of milliseconds have passed.
Notice that in this example, the same code could be used to do the actual
refreshing of the drawing, regardless of whether it is being run by the UIProcess or
by a background process, because the ExecInUI methods can always be called by
both background processes and the UIProcess.
The extended container widgets use different techniques for drawing the items
provided by applications:
v EwDrawnList uses callbacks to request customized drawing of items by an
application.
v EwIconList, EwIconTree, EwFlowedIconList, and EwIconArea use callbacks to request
icons and labels that represent each item from an application.
v EwTableList and EwTableTree use callbacks to request the objects to be displayed
in individual cells.
Tip: Most of the extended list widgets (all except for EwlconArea) support ordered
lists. The lists are protocol compatible with CwList. Items can be added,
deleted, replaced, or selected in the items list using standard CwList protocol.
Scrolled lists
Each of the container widgets can be created with scrolling capability, much like
CwList. All resources and callbacks associated with a particular list can be applied
to the scrolled version of that list. Scrolling is handled automatically. The
scrollHorizontal resource specifies whether the widget should provide a horizontal
scroll bar.
Tip: The horizontal scroll bar will not be shown in the list widget unless it has
been created as a scrolled list, even if the scrollHorizontal resource is set to
true.
An application hooks the displayCallback to draw the items in the list. If the items
in the list have different sizes, an application should hook the measureCallback to
specify the height of each individual item in the list. If all items have the same
height, the itemHeight resource can be used to specify the height in pixels.
The applicationDrawnStates resource allows for the specification of visuals for any of
the emphasis states in the list, such as selection emphasis or cursored emphasis.
Applications can choose to allow the drawn list to provide these emphasis visuals.
In the following code, a list of CgFontStructs is added to a drawn list, and each
font name in the list is drawn with the font that it describes.
Object subclass: #DrawnListExample
instanceVariableNames: 'fontStructs'
classVariableNames: "
poolDictionaries: 'CgConstants CwConstants EwConstants'
open
| shell drawnList |
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Drawn List Example'].
fontStructs := ((CgDisplay default listFonts: '*' maxnames: 100)
collect: [:fontName |
CgDisplay default loadQueryFont: fontName]) asOrderedCollection.
drawnList := shell
createScrolledDrawnList: 'drawnList'
argBlock: [ :w | w items: fontStructs].
drawnList
addCallback: XmNdisplayCallback
receiver: self
selector: #display:clientData:callData:
clientData: nil;
addCallback: XmNdestroyCallback
receiver: self
selector: #destroy:clientData:callData:
clientData: nil.
drawnList manageChild.
shell realizeWidget.
display: widget clientData: clientData callData: callData
"Display the fontStruct by drawing its font name in the
upper left-hand corner of the specified rectangle, using
the fontStruct as the font."
| fontStruct |
fontStruct := callData object.
callData gc setFont: fontStruct font.
callData drawable
drawString: callData gc
x: callData x
y: callData y + fontStruct ascent
string: fontStruct name
measure: widget clientData: clientData callData: callData
"Measure the fontStruct by querying its height."
| fontStruct |
fontStruct := callData object.
callData height: fontStruct height
destroy: widget clientData: clientData callData: callData
"Destroy the fontStruct collection."
The application hooks the visualInfoCallback to specify the label and icon for each
object in the list. Typically, an application provides both an icon and a string in this
callback. However, either value can be set to nil if only the icon or label is desired.
Tip: If your application requires a view with either an icon or label per item, but
not both, set the icon to nil, and the label to the desired visual object for each
item in the visualInfoCallback callData. Set the labelOrientation resource of the
widget to XmRIGHT (default), and set the emphasisPolicy resource of the
widget to XmTOGETHER.
iconList manageChild.
shell realizeWidget.
visualInfo: widget clientData: clientData callData: callData
"Provide the icon and label for the class contained in the callData."
callData
label: callData item name;
icon: icon
destroy: shellWidget clientData: clientData callData: callData
"Free the icon resource."
Renderables
In the Common Widgets subsystem, widgets require applications to provide a
strongly typed visual object. For example, labels are required to be an instance of
String, and images must be instances of CgIcon or CgPixmap. In the Extended
Widgets subsystem, the visual object types are less restrictive. Most extended
widget label, icon, or image resources require only that the supplied visual object
conform to a specified rendering protocol. Any object that conforms to this
protocol and can display (render) itself is called a renderable.
You can add rendering protocol to other visual objects in your application. For
example, a colored string renderable can be made by combining a String and a
CgRGBColor into a ColoredString object.
addCallback: XmNdefaultActionCallback
receiver: self
selector: #defaultAction:clientData:callData:
clientData: nil;
addCallback: XmNbeginEditCallback
receiver: self
selector: #beginEdit:clientData:callData:
clientData: nil;
addCallback: XmNendEditCallback
receiver: self
selector: #endEdit:clientData:callData:
clientData: nil.
icon := shell screen
getIcon: 'default_xm_information' foregroundColor: CgRGBColor black.
shell
addCallback: XmNdestroyCallback
receiver: self
selector: #destroy:clientData:callData:
clientData: nil.
iconList manageChild.
shell realizeWidget.
widget
replaceItems: (Array with: callData item)
newItems: (Array with: callData newValue)
visualInfo: widget clientData: clientData callData: callData
"Provide the icon and label for the class contained in the callData."
callData
icon: icon;
label: callData item.
destroy: shellWidget clientData: clientData callData: callData
"Free the icon resource."
The icons and labels an application supplies in the visualInfoCallback can be any
renderable or nil (see Renderables on page 238). This is the same as EwIconList.
The following example shows the creation of a flowed icon list (the
visualInfoCallback is not shown).
| flowedIconList shell |
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Flowed Icon List Example'].
flowedIconList := shell
createScrolledFlowedIconList: 'flowedIconList'
argBlock: [ :w | w
items: Object subclasses;
itemWidth: 200;
itemHeight: 36].
flowedIconList manageChild.
shell realizeWidget.
Each item in the list is associated with a location, which is a Point representing
where the item is displayed in the icon area. Items can be added to the list at a
specific location using the addItem:location: or addedItem:location: protocol.
Items in an icon area can be rearranged so that no items overlap using the
arrangeItems function. The icons and labels an application supplies in the
visualInfoCallback can be renderable (see Renderables on page 238). This is the
same as EwIconList. The following example creates an icon area and adds an item
at a specific point (the visualInfoCallback is not shown).
| iconArea shell |
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Icon Area Example'].
iconArea := shell
createScrolledIconArea: 'iconArea'
argBlock: nil.
iconArea manageChild.
shell realizeWidget.
iconArea addItem: 'Fred' location: 25@30.
Tip: The objects used for cell values and headings are typically Strings, Numbers,
and CgIcons, but this is not a requirement. It is possible for an application to
use any renderable object as the cell value or heading (see Renderables on
page 238).
The columns of the table list widget are defined by the columns resource that
should be set to a collection of EwTableColumn instances. The resources that can be
set for each column are described in Table columns on page 243.
The table list widget supports the standard selection policies, plus additional
policies for cell selection.
Standard selection policies
Additional policies for cell selection
XmSINGLESELECT
Standard selection policies apply to the rows in the table as a whole
XmEXTENDEDSELECT
Standard selection policies apply to the rows in the table as a whole
XmMULTIPLESELECT
Standard selection policies apply to the rows in the table as a whole
XmBROWSESELECT
Standard selection policies apply to the rows in the table as a whole
XmCELLSINGLESELECT
Allows individual cells to be selected
XmCELLBLOCKSELECT
Allows contiguous blocks of rows to be selected
The table list selection policies involve a separate protocol. Messages such as
selectPos:notify: and deselectAllItems are used with the standard policies. Messages
such as selectCell:notify: and deselectCell: are used with the cell selection policies. It
is important to ensure that the protocol being used at any given time is
Table columns
The EwTableColumn objects in a table list have their own set of resources and
callbacks. The cellValueCallbackmethod obtains the renderable object that represents
the value of a column in a particular row.
Additional resources control the appearance of the column and its heading. The
heading resource can be set to a renderable object that is to be displayed at the top
of a particular column. The resizable resource controls whether users can resize the
column to a different width by dragging the right side of the column heading.
Other resources control the size, alignment, and visual style of the column.
The following code creates a table list with the subclasses of the class Object as its
items. The first column displays an icon, the second displays the name of the class,
and the third displays the classs comment.
Object subclass: #TableListExample
instanceVariableNames: 'icon'
classVariableNames: "
poolDictionaries: 'CwConstants EwConstants '
open
| shell tableList |
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Table List Example'].
tableList manageChild.
shell realizeWidget
columns
"Answer a collection of EwTableColumns."
| OrderedCollection new
add: self iconColumn;
add: self nameColumn;
add: self commentColumn;
yourself
iconColumn
"Answer the column for the class' icon."
| col |
col := EwTableColumn new
editable: false;
resizable: false;
width: 36;
horizontalAlignment: XmALIGNMENTCENTER;
yourself.
col
addCallback: XmNcellValueCallback
receiver: self
selector: #iconCellValue:clientData:callData:
clientData: nil.
|col
nameColumn
"Answer the column for the class' name."
| col |
col := EwTableColumn new
editable: false;
heading: 'Class Name';
resizable: true;
width: (CgFontStruct default textWidth: 'Class Name');
horizontalHeadingAlignment: XmALIGNMENTCENTER;
yourself.
col
addCallback: XmNcellValueCallback
receiver: self
selector: #textCellValue:clientData:callData:
clientData: #name.
| col
commentColumn
"Answer the column for the class' comment."
| col |
col := EwTableColumn new
editable: false;
heading: 'Comment';
resizable: true;
width: 512;
horizontalHeadingAlignment: XmALIGNMENTCENTER;
yourself.
col
addCallback: XmNcellValueCallback
receiver: self
selector: #textCellValue:clientData:callData:
clientData: #comment.
| col
Cell editing can automatically begin when a user clicks on a cell. To enable
automatic cell editing the following resources must be set:
v The editable resource of the table list widget must be set to true.
v The editable resource of each column in which cells are to be edited must be set
to true.
v The selectionPolicy resource of the table list must be set to
XmCELLSINGLESELECT or XmCELLBLOCKSELECT.
When cell editing is under application control, editing begins when the application
sends editCellAt: or editSelectedCell to the table list widget. These functions can be
used to trigger cell editing regardless of the value of the editable resource in the
table or in its columns.
Editing ends when the widget loses focus or another item is selected. When this
happens, or when the value in the edit widget has been changed (the exact details
of when a change has occurred depend on the edit policy; see Edit policies on
page 246), the column activates its endEditCallback. The callData includes the old
value and the new value. Your application should hook this callback and save the
new edited value in your application as appropriate. The cell is then automatically
refreshed after the callback is sent, so that the new value is obtained and
displayed.
The following code builds on the previous example and shows how cell editing
can be provided. First, the table list must be created in a slightly different way:
...
tableList := shell
createScrolledTableList: 'scrolled list'
argBlock: [:w | w
columns: self columns;
items: Object subclasses;
addCallback: XmNbeginEditCallback
receiver: self
selector: #beginEditText:clientData:callData:
clientData: nil;
addCallback: XmNendEditCallback
receiver: self
selector: #endEditText:clientData:callData:
clientData: nil.
| col
Finally, the handlers must be written for the beginEditCallback and the
endEditCallback.
beginEditText: widget clientData: clientData callData: callData
"About to start editing a text field. Ensure the doit field is true."
Edit policies
For simple text editing of cell values, an application need only provide simple
beginEditCallback and endEditCallback handlers. In some advanced applications,
simple text editing of a cell might not be sufficient. In these situations, the
application can specify an edit policy in the callData of the beginEditCallback.
The edit policy defines the type of widget to be used for editing and some other
edit semantics. The default edit policy is EwTextEditPolicy set up to use a
single-line CwText as the edit widget. The application can substitute a more
appropriate edit policy. For example, if the cell contains a day of the week, the
application might wish to use an EwComboBoxEditPolicy. Applications can define
The following edit policies are provided in the Extended Widgets subsystem:
EwComboBoxEditPolicy
A cell is edited using a combo box. An application can specify the list of
items to choose from and the initial value. It can also specify whether users
can edit the value manually or just choose one of the items in the list.
EwTextEditPolicy
A cell is edited using a text widget. An application can specify whether the
widget is to be single or multiple lines, what the initial text value should
be, what the initially selected text should be, and what the maximum
length should be.
EwToggleButtonEditPolicy
A cell is edited using a toggle button. An application can specify the label
string for the toggle button, the horizontal alignment of the label string
and whether or not the toggle should be initially selected.
Tree widgets
Tree widgets (EwIconTree and EwTableTree) provide a hierarchical view of the items
in a list. Only the top-level items of a hierarchy (the roots) are set in the items
resource of a tree widget. The childrenCallback must be hooked so an application
can provide a collection of children for any of the items in the list. The
visualInfoCallback must be hooked so an application can set the hasChildren flag in
the callData to indicate whether the item to be displayed has any children. This
allows the tree widget to provide any visuals necessary to indicate that the item
has children.
Tip: The childrenCallback is activated only for items in the list that return true as
the value of the hasChildren flag of the visualInfoCallback callData.
Tree widgets provide protocol that allows applications to control that branches of
the tree are displayed. By default, only the roots of a tree are shown initially.
Methods, such as expandPos:notify:, collapsePos:notify:, and expandCollapsePos:notify:,
allow an application to expand or collapse branches of the tree.
The visual appearance of the tree depends upon the value of the hierarchyPolicy
resource. This resource allows applications to specify the amount of indentation
that should be used to distinguish levels of a hierarchy. It also allows the
specification of specialized hierarchy graphics, such as lines drawn to connect the
items, or the button to be provided next to items that have children. By default, the
hierarchyPolicy of tree widgets is an instance of EwHierarchyPolicy, with the lines
flag set to true (indicating that lines should be drawn to illustrate the hierarchy
when expanded).
Following are icon tree widgets with the default hierarchy policy and an icon
hierarchy policy:
Icon trees
In the following example, an icon tree is created that displays the Smalltalk class
hierarchy. The defaultActionCallback is hooked to allow navigation through the tree.
Object subclass: #IconTreeExample
instanceVariableNames: 'icon'
classVariableNames: "
poolDictionaries: 'CwConstants EwConstants'
open
| shell iconTree |
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Icon Tree Example'].
iconTree := shell
createScrolledIconTree: 'iconTree'
argBlock: [ :w | w
items: (Array with: Object)].
iconTree
addCallback: XmNvisualInfoCallback
receiver: self
selector: #visualInfo:clientData:callData:
clientData: nil;
addCallback: XmNchildrenCallback
receiver: self
selector: #children:clientData:callData:
clientData: nil;
addCallback: XmNdefaultActionCallback
receiver: self
selector: #defaultAction:clientData:callData:
clientData: nil.
"Note: The destroyCallback is not shown."
icon := shell screen
getIcon: 'default_xm_information' foregroundColor: CgRGBColor black.
iconTree manageChild.
shell realizeWidget.
widget
expandCollapsePos: callData itemPosition
notify: false
visualInfo: widget clientData: clientData callData: callData
"Provide the icon and label for the class contained in the callData.
Also check whether the item has children."
callData
icon: icon;
label: callData item name;
hasChildren: callData item subclasses notEmpty
Table trees
The table tree widget (EwTableTree) is a combination of the table list widget and
icon tree widget. Like the table list widget, it displays attributes of items in
columnar format. Like the icon tree widget, it also provides a hierarchical view of
the items. The columns and their callbacks are created and set up in the same way
as they are for the table list widget.
The following example creates a scrolled table tree widget. The column definitions
and column callbacks are the same as the EwTableList example. The
defaultActionCallback and childrenCallback methods are hooked and handled the
same as in the EwIconTree example. The visualInfoCallback must be hooked for a
table tree. Athough the icon and label information are not specified (they are
specified as cell values), the hasChildren flag must be set.
Tip: The first column of the table tree is used to show the hierarchy (typically, this
column shows icons). Because child items are indented, this column should be
defined with a larger width than a similar column for a table list widget.
Also, it is a good idea to define this column as resizable.
Object subclass: #TableTreeExample
instanceVariableNames: "
classVariableNames: "
poolDictionaries: 'CwConstants EwConstants'
open
| shell tableList |
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Table Tree Example'].
tableList := shell
createScrolledTableTree: 'scrolled table tree'
argBlock: [:w | w
columns: self columns;
items: (Array with: Object);
selectionPolicy: XmCELLSINGLESELECT].
...
"Callbacks are hooked as in previous examples"
...
visualInfo: widget clientData: clientData callData: callData
"Specify whether the item has children. All other values are
provided using cell value callbacks."
Creating pages
By themselves, notebook widgets do not offer much of an interface to users. To
present a useful interface to users, notebooks must contain pages (EwPage). Pages
are created by sending one of the following messages to either a PM or WIN
notebook:
createPage:argBlock:
Creates a new page within the receiver and adds it to the end of the
receivers collection of pages. The first argument is the widget name for the
new page, the second is its argument block.
createPage:argBlock:before:
Creates a new page within the receiver before an existing page within the
receiver. The last argument is the page that the new page is to precede.
createPage:argBlock:sharingWith:
Creates a new page that shares widgets with another page in the receiver.
The last argument is the page that defines the widgets shared by itself and
the new page.
createPage:argBlock:before:sharingWith:
Creates a new page within the receiver that shares widgets with another
page. The new page is inserted before a page already within the receiver.
The following expression adds a page with a tab labeled Top Page to a notebook:
topPage := notebook
createPage: 'envinfo_page'
argBlock: [:w |
"Note: The tab label can be any renderable."
w tabLabel: 'Top Page'].
topPage manageChild.
Tip: Pages only appear in the notebook if they are managed. To temporarily hide a
page in a notebook without destroying the page widget, use the
unmanageChild message.
Callbacks
The notebook and page widgets provide three different callbacks
(pageChangeCallback, pageEnterCallback, and pageLeaveCallback) when a new page is
brought to the top. The notebooks pageChangeCallback and a pages
pageLeaveCallback both allow, by setting a doit flag to false, an application to stop a
new page from being topped. This is useful when some input is still required from
the user on the old page. If the previous callbacks have been activated successfully,
the pages pageEnterCallback will fire. This notifies an application of the new page
being brought on top. By hooking this callback, an application can delay the
PM notebook widget
The PM notebook widget (EwPMNotebook) is used to create notebooks with the
look and feel of the OS/2 notebook control. By manipulating the values of a few
resources, a PM notebook widget can be made to adopt the range of looks
available from the native PM notebook control. Following is an example of a PM
notebook widget:
A notebooks image can be oriented so that the pages appear to turn vertically, like
a flip chart, or horizontally, like a book. If the value of the orientation resource is set
to XmHORIZONTAL, the binding will be drawn along the left side. If it is set to
XmVERTICAL, the binding will appear on the top or bottom depending on the
value of the backPagePosition resource.
The value of the bindingType resource determines what type of binding is drawn
along the bound edge of the notebooks collection of pages. Options include no
binding (XmNONE), a spiral binding (XmSPIRAL), or a solid binding (XmSOLID).
"Create the page that will define the widgets used by all pages."
basePage := aNotebook
createPage: 'basePage'
argBlock: [:w | w tabLabel: 'Object'].
basePage manageChild.
basePage
addCallback: XmNpageEnterCallback
receiver: self
selector: #pageEnter:clientData:callData:
clientData: 'Object'.
list := basePage
createList: 'list'
argBlock: [:w | w
topAttachment: XmATTACHFORM;
rightAttachment: XmATTACHFORM;
leftAttachment: XmATTACHFORM;
bottomAttachment: XmATTACHFORM].
list manageChild.
"Create the rest of the pages."
#('Behavior' 'ClassDescription' 'Class' 'Metaclass') do: [:name |
| aPage |
aPage := aNotebook
createPage: name
argBlock: [:w | w tabLabel: name]
sharingWith: basePage.
aPage
addCallback: XmNpageEnterCallback
receiver: self
selector: #pageEnter:clientData:callData:
clientData: name;
manageChild]
list items:
((Smalltalk at: clientData asGlobalKey) selectors collect: [
:s | s asString])
Progress is demonstrated visually by filling the progress bar with the widgets
foreground color or by displaying a specified ribbonImage. The application is
responsible for periodically updating the fractionComplete resource to indicate the
extent to which the task has been completed.
Resources are provided which allow the application to control the visual
appearance of the progress bar widget.
The shadowType and shadowWidth resources allow shadows around the bar to be
specified, while the size of the bar itself within the widget can be specified using
the innerHeight and innerWidth resources. The application may also use the
showPercentage and fontList resources to specify a percentage complete label and its
associated font.
The following method creates a progress bar with a thick shadow that fills in from
the bottom to the top. The Motif information icon is used to fill in the bar. The
percentage complete is shown in the bar using the default font. Add this method
to a class that uses the pool dictionaries CwConstants and EwConstants.
progressBarExample
| progressBar shell |
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w
title: 'Progress Bar Example';
width: 210;
height: 100].
progressBar := shell
createProgressBar: 'progressBar'
argBlock: [:w | w
orientation: XmVERTICAL;
direction: XmREVERSE;
ribbonImage: (CgScreen default
getIcon: 'default_xm_information'
foregroundColor: CgRGBColor black);
shadowType: XmSHADOWIN;
shadowWidth: 5;
fractionComplete: 1/4;
showPercentage: true].
progressBar manageChild.
shell realizeWidget.
When you run the method by evaluating <class name> new progressBarExample,
you get the following:
Slider widget
The slider widget (EwSlider) displays an analog representation of a value within a
specified range of values. The range is represented by a horizontal or vertical shaft.
The currentValue resource reflects the position of a slider arm that can be moved
along the length of the shaft to display the current value.
The orientation of the slider can be specified using the orientation resource. A value
of XmVERTICAL indicates that the slider will be drawn vertically, with the
minimum value at the bottom and the maximum value at the top. Similarly,
The slider widget can display one or two scales along the shaft, which can be
displayed above and below a horizontal slider, or to the left and right of a vertical
slider. Both scales provide a minimum value, a maximum value, and a resolution.
The resolution represents the size of the increments between the minimum and
maximum values. For example, using a minimum of 0, a maximum of 100, and a
resolution of 10 would result in valid values of 0, 10, 20...100. Minimum value,
maximum value, and resolution for each scale are set using the topOrLeftScaleMin,
topOrLeftScaleMax, topOrLeftScaleResolution, bottomOrRightScaleMin,
bottomOrRightScaleMax, bottomOrRightScaleResolution resources.
Several resources control the visual appearance of the slider. The thickness of the
slider shaft can be controlled using the thickness resource. The buttonStyle resource
provides the option of increment and decrement buttons on the shaft. If requested,
these buttons can be positioned together at either end of the shaft, or separately,
one at each end of the shaft. Further refinements to the shaft position within the
slider widget can be made using the verticalMargin and horizontalMargin resources.
The slider value is changed by moving the slider arm with the mouse, depressing
the increment or decrement buttons, using the arrow keys, or clicking on a detent.
The slider arm moves in units specified by the resolution of the scale. If two scales
are displayed on a slider, the resolution depends on the setting of the activeScale
resource.
A slider can be used as a progress bar by setting the readOnly and ribbonStrip
resources to true and setting the currentValue resource to show the progress.
Slider widgets provide two callbacks to alert applications to changes in the value
of the slider. The dragCallback notifies applications of the movement of the slider
arm. The valueChangedCallback notifies applications when the value has been
changed, that is, after a drag is complete, or if the buttons or detents are used to
change the value.
The following method creates a horizontal slider with tick marks along the top
scale. Add this method to a class using the pool dictionaries CwConstants and
EwConstants.
sliderExample
| slider shell |
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w
slider manageChild.
shell realizeWidget.
When you run the method by evaluating <class name> new sliderExample, you
get the following:
The itemType resource specifies whether the spin button is to display numeric data
or a collection of strings. Possible values for this resource are as follows:
XmSBNUMERIC
The spin button holds numeric data
XmSBSTRING
The spin button holds a collection of strings
The minimum, maximum, and increment resources can be used to define the range
values for a numeric spin button. These resources are ignored for spin buttons that
hold collections of strings.
The following method creates a spin button that traverses the days of the week.
Add this method to a class using the pool dictionaries CwConstants and
EwConstants.
spinButtonExample
| spinButton shell |
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w
title: 'SpinButton Example';
width: 110;
height: 30].
spinButton:= shell
createSpinButton: 'spinButton'
argBlock: [:w | w
itemType: 2; "Or XmSBSTRING instead of 2"
items: #('Monday' 'Tuesday' 'Wednesday' 'Thursday'
'Friday' 'Saturday' 'Sunday');
wrap: true].
spinButton manageChild.
shell realizeWidget
When you run the method by evaluating <class name> new spinButtonExample,
you get the following:
The childProportions resource can specify the initial size of the child widgets. The
childProportions resource contains an array of integers indicating the relative
percentage of the window to be occupied by each child. If the resource is not
specified, the child widgets are sized equally within the window.
Users can reposition split bars by first pressing and holding mouse button one
while the cursor is over a split bar and then moving the mouse to the desired
position. Dragging one split bar towards another causes the visible portion of the
enclosed widget to be reduced until it reaches a predefined minimum size as
defined by the childMinimumSizes resource.
The following method creates a split window with three child widgets. Child
widgets are given initial proportions and minimum sizes. Add this method to a
class using the pool dictionaries CwConstants and EwConstants.
splitWindowExample
| shell splitWindow |
shell := CwTopLevelShell
createApplicationShell: 'Split Window Example'
argBlock: nil.
splitWindow := EwSplitWindow
createManagedWidget: 'splitWindow'
parent: shell
argBlock: [:w | w
width: 400;
height: 200;
orientation: XmHORIZONTAL;
childProportions: #(60 15 25) ;
childMinimumSizes: #(30 15 15)].
CwList
createManagedWidget: 'list1'
parent: splitWindow
argBlock: [:w | w
borderWidth: 0;
items: (Collection withAllSubclasses collect: [:each | each name])].
CwList
createManagedWidget: 'list2'
parent: splitWindow
argBlock: [:w | w
borderWidth: 0;
items: (Collection class selectors collect: [:each | each printString])].
CwList
createManagedWidget: 'list3'
parent: splitWindow
argBlock: [:w | w
borderWidth: 0;
items: (Collection selectors collect: [:each | each printString])].
shell realizeWidget.
When you run the method by evaluating <class name> new splitWindowExample,
you get the following:
Several resources can control the way that a tool bar displays its tools. The
numColumns resource can specify whether the tools should be arranged in columns.
If the numColumns is 0, the tool bar lays out the tools in a row. Setting numColumns
to 1 produces a vertical tool bar. The spacing resource determines how much space
to leave between each tool.
You can specify the colors of the tool bar using the standard foregroundColor and
backgroundColor resources. In addition, you can specify the default colors of tools
using the toolForegroundColor and toolBackgroundColor resources.
Creating tools
The tool bar usually does not have widgets as children. Instead, it has specialized
children called tools (EwTool). Tools are user interface elements that look like
widgets but do not actually use all of the platform resources required by a widget.
Tools collaborate with the parent tool bar to display themselves and handle user
events. You add widgets as children of a tool bar by associating a widget with a
widget tool.
Tools are created by sending one of the following messages to a tool bar:
createGroup:argBlock:
Creates a group tool inside the tool bar. The first argument is the name for
the new tool; the second is its argument block. A group is a tool that
contains other tools.
createLabelTool:argBlock:
Creates a label tool inside the tool bar.
createProgressBarTool:argBlock:
Creates a progress bar tool inside the tool bar.
createPushButtonTool:argBlock:
Creates a push button tool inside the tool bar.
createRadioButtonTool:argBlock:
Creates a radio button tool inside the tool bar.
createSeparatorTool:argBlock:
Creates a separator tool inside the tool bar.
createSimpleGroup:argBlock:
Creates a simple group inside the tool bar. A simple group is a tool that
contains only buttons. It provides protocol for specifying collections of
images to be displayed in the group as buttons.
createToggleButtonTool:argBlock:
Creates a toggle button tool inside the tool bar.
Tools are lighter weight than widgets and minimize platform resources. However,
they do not always look like a platform widget. Using widgets and widget tools on
the tool bar ensures that the tools look exactly like their widget counterparts on a
particular platform. This platform look and feel, though, comes at the expense of
using platform resources.
Using tools
Tools on a tool bar can be used much like widgets. Resources such as width, height,
borderWidth, foregroundColor, and backgroundColor can control the appearance of the
tool. The variableWidth resource controls whether a tool shrinks or grows as the tool
bar resizes.
The following method creates a tool bar with four primitive tools. Add this
method to a class using the pool dictionaries CwConstants and EwConstants.
toolBarExample
| shell toolBar |
shell := CwTopLevelShell
createApplicationShell: 'Tool Bar Example'
argBlock: nil.
toolBar := EwToolBar
createManagedWidget: 'toolBar'
parent: shell
argBlock: [:w | w numColumns: 0; spacing: 3].
toolBar
createLabelTool: 'label'
argBlock: [:w | w image: 'Text Label'].
toolBar
createSeparatorTool: 'label'
argBlock: [:w | w
orientation: XmVERTICAL;
autoSize: true;
separatorType: XmSHADOWETCHEDIN].
shell realizeWidget.
When you run the method by evaluating <class name> new toolBarExample, you
get the following:
The argument for a widget tool argBlock is the tool itself, not the widget. To set
resources for the tools associated widget, use the widget resource to access the
widget from the tool.
The following example creates a widget tool using a combo box. It uses the
argument block to set both widget and tool resources.
...
toolBar createWidgetTool: CwComboBox name: 'combo' argBlock: [:tool |
tool
borderWidth: 1;
height: 36.
tool widget
items: #('Red' 'Green' 'Blue');
editable: true].
...
Using groups
Groups (EwGroupTool) are useful for grouping multiple tools in order to assign
common properties or provide specialized behavior for the group. The shadowType
resource determines what kind of border is drawn around the tools within a group
when the borderWidth is not 0. The spacing resource determines what spacing the
group uses for its tools. If the group contains toggle button tools, the radioBehavior
resource can enforce radio button style behavior within a group.
The following method below creates a tool bar that contains two groups separated
by a separator tool.
toolGroupExample
| shell toolBar group1 group2 |
shell := CwTopLevelShell
createApplicationShell: 'Tool Group Example'
argBlock: nil.
toolBar := EwToolBar
toolBar
createSeparatorTool: 'label'
argBlock: [:w | w
orientation: XmVERTICAL;
autoSize: true;
separatorType: XmSHADOWETCHEDIN].
group2 := toolBar
createGroup: 'group2'
argBlock: [:w | w
borderWidth: 1;
shadowType: XmSHADOWOUT;
radioBehavior: true].
group2
createToggleButtonTool: 'toggle1'
argBlock: [:w | w image: 'Yes'; radioBehavior: true].
group2
createToggleButtonTool: 'toggle2'
argBlock: [:w | w image: 'No'; radioBehavior: true; set: true].
shell realizeWidget.
The example below creates a group like the radio group in the previous example,
but it uses a simple group to create the toggle buttons.
...
group2 := toolBar createSimpleGroup: 'group2' argBlock: [:w | w
borderWidth: 1;
shadowType: XmSHADOWOUT;
radioBehavior: true;
toggleBehavior: true;
images: #('Yes' 'No')].
...
An application might wish to support drag and drop for a variety of reasons. It is
not the purpose of Drag and Drop subsystem support to prescribe the semantics of
drag and drop; this is left to the application. Instead, generalized objects called
adapters are provided to map low-level mouse and keyboard operations to drag
and drop callbacks. The primary role of the application in drag and drop is to
create the widgets and adapters required, hook the drag and drop callbacks on the
adapters, and then make the changes to the underlying objects as drag and drop
occurs.
Because the adaptors hook low-level mouse and keyboard events, any widget that
provides these events can be adapted to drop and drag. This includes those
widgets in the Common Widgets subsystem that fully support mouse and
keyboard events.
The Drag and Drop subsystem provides two kinds of adapters for use in drag and
drop: source adapters and target adapters. The adapters serve as a wrapper around
widgets, providing drag and drop callbacks that the widgets themselves do not
provide.
An application creates one source adapter for each source widget as follows:
aSourceAdapter := EwSourceAdapter on: aWidget.
The source adapter provides the following callbacks for drag and drop source
operations:
dragStartCallback
A drag has been started from the source widget.
dragChangeCallback
The target or semantics of the drag have changed.
dragCompleteCallback
The drag and drop operation has completed.
dragCancelCallback
The drag and drop operation was canceled or did not complete.
The application also should create a target adapter for each widget that is to be a
target for drag and drop. The target adapter is created as follows:
aTargetAdapter := EwTargetAdapter on: aWidget
The target adapter provides the following callbacks for drag and drop target
operations:
dragOverCallback
The mouse pointer is moving over the drag target widget.
dragLeaveCallback
The mouse pointer has left the drag target widget.
dropCallback
The drop operation was indicated over the drag target widget.
dragCancelCallback
The drag and drop operation was canceled or did not complete.
Sequence of events
An application prepares for drag and drop by creating a source adapter on the
widgets that acts as the drag source. It then hooks each source adapters
dragStartCallback and dragCompleteCallback and, optionally, the dragChangeCallback
and dragCancelCallback.
An application also creates a target adapter on each widget that can act as a drop
target, including any source widgets that can also act as targets. An application
then hooks each target adapters dragOverCallback and dropCallback and, optionally,
the dragLeaveCallback and dragCancelCallback.
In the following example, a window is created with a single EwIconList on it. This
list is configured to do drag and drop to and from another such window. This
portion of the example creates the widgets and adapters, hooks the necessary
callbacks, and realizes the window. The visualInfoCallback handler is also shown for
completeness. The application containing the class should have EwDragAndDrop for
a prerequisite.
The user initiates a drag by pressing the drag mouse button (ButtonDrag) over a
drag source widget and moving the mouse a certain nominal distance. (The
ButtonDrag is button 1 on Windows and UNIX platfoms, and button 2 on OS/2.
Note that these buttons are settable.) The source adapter, having hooked the mouse
events on the source widget, detects that a drag is starting and activates the
dragStartCallback to the application. The callData for the dragStartCallback includes
the mouse event that triggered the drag start, the items in the source widget that
are being dragged, and each items offset from the mouse location. It also contains
The images and offsets that the adapter provides as defaults depend on the API of
the source widget. For example, because CwList provides no API to allow the
source adapter to determine which item in the list is under the cursor when the
drag starts, the adapter simply provides the selected items as the drag items.
Similarly, because the source adapter cannot determine the offsets of the selected
items, it provides offsets that cause the drag items to be beveled up and to the
right from the mouse.
The dragStartCallback callData also contains a doit flag that is set to true by default.
The application can change this flag to false if it determines that dragging is not
allowed for some reason. The callData also includes a default vote (see Voting
and cursors on page 267), which is an array of operations that the source allows
for these items. The application can change this vote to an array of operations that
it will allow, for the source items being dragged.
Finally, the dragStartCallback callData contains a slot for the source model. The
application can place any value in this slot, but typically it is used to hold the
underlying object that contains the items being dragged. This value will be passed
along to the drag target in the callData of the target-related callbacks.
Each time the mouse moves, the system determines which widget is currently
under the mouse. The system keeps a registry of all target adapters and their
widgets. This enables it to map the widget under the mouse (if any) to its
corresponding target adapter (if any).
If a widget is not under the mouse or if a target adapter does not exist for a
widget, the cursor which indicates that the operation is not allowed (that is, the
No Parking cursor) is automatically shown.
If there is an API to determine which item is under the cursor (as is the case in
some extended widgets), the application must determine whether the item under
the cursor is itself capable of accepting a drop. For example, a trash can or a
printer typically would be able to accept a drop.
The callData also contains an emphasis flag that an application can set to specify
which kind of emphasis should be shown on the target widget. Again, this is not
The dragOverCallback callData also contains the source model and a field for the
target model. The source model is whatever value was last placed in the
sourceModel slot by the source in either the dragStartCallback or dragChangeCallback
callData. The application can set the target model to be any object, but this is
typically the object that would contain the drag items if they were to be dropped
on the target.
The dragOverCallback callData also contains a vote. The application can set this to
be an array of operations that it will allow given the current target widget, target
item (if any), and the items being dragged.
Finally, the dragOverCallback callData contains a repeat flag. This Boolean variable
determines whether another dragOverCallback should be activated even if the item
under the cursor does not change. By default, this value is false for performance
reasons. If the application needs the dragOverCallback to be triggered continuously,
then the repeat field must be set to true each time the callback is triggered. For
example, if an application uses a drawing area to present an image, the application
might need the dragOverCallback to be triggered repeatedly so that it can detect
when the items are being dragged over the various parts of the image.
Tip: In some cases, an application might need to draw while a drag is in progress
(for example, to change the icon of an item under the cursor). To avoid
leaving visual debris on the screen, the application must do any drawing
by sending the message drawDuringDrag: to the adapter. This message takes a
Block as its argument. All drawing that takes place in the block will not leave
debris on the screen.
The cursor images and keyboard mappings for each operation are configurable on
a global basis (see System configuration on page 269).
Leaving a target
In some cases, an application might need to be notified when items are dragged
away from a target. For example, if an application had hooked the dragOverCallback
to show target emphasis, it would need to be notified when the drag had left that
target widget so that it could erase the emphasis.
Dropping
When a user releases the mouse button, a drop occurs on the target widget. Both
the target and the source are notified. The target adapter first activates the
dropCallback. The callData includes the source widget, the source model, the source
items, the operation, and the mouse event. It also includes an offset for each source
item relative to the mouse location. If, in response to the last dragOverCallback, the
application set the emphasis to XmTARGETEMPHASIS, the callData for the
dropCallback also contains the target item. If the emphasis was set to
XmINSERTIONEMPHASIS, the callData contains the insertion index. Finally, the
callData contains a doit flag, which an application can set to false if it is unable to
perform the drop. In this case, the source adapter will activate its
dragCancelCallback. Otherwise, it is the applications responsibility to perform the
appropriate operation on the target widget and the underlying objects.
After the target has triggered the dropCallback, the source adapter activates its
dragCompleteCallback. The callData includes the source items, the target widget, the
target model, the target item (if any), and the operation. It is the applications
responsibility to perform the appropriate operation on the source widget and the
underlying objects. For example, if the operation was XmMOVE, the application
should remove the items from the source widget.
Canceling a drag
The user can cancel a drag at any time by pressing the key represented by
XkCancel. In this case, the source adapter and the target adapter (if any) activate
their dragCancelCallback. The callData for this callback depends on whether the
adapter is a source or a target adapter. For source adapters, the callData includes
the same information as the callData for the dragCompleteCallback. For target
adapters, the callData includes the same information as the callData for the
dropCallback.
The source adapter also activates the dragCancelCallback when items are dropped
outside any target widgets. The dragCancelCallback is also triggered when the items
are dropped onto a target that is unwilling to accept them; that is, when no valid
operation is in effect or when the target widgets application sets the doit flag to
false in the dropCallbacks callData.
System configuration
A central drag and drop manager (EwDragAndDropManager) stores system-wide
drag and drop parameters. It provides API to set and query: the list of possible
operations, the cursors that correspond to each operation, and the mappings from
keyboard combinations to operations.
The mouse button that should be used to initiate a drag operation is specified in
the button resource. An application can specify a particular button using Button1,
Button2, or Button3, or it can use the value ButtonDrag to indicate that the default
drag button for a particular platform should be used.
The set of operations and their relative priorities are configurable. By default, the
operations are XmMOVE, XmCOPY, and XmLINK (a Link is a reference to the
same object). When the source, target, and keyboard votes are tallied, the highest
priority operation of the intersection of the three votes is used as the operation.
The priority of the votes is determined by the order the source answers its
allowable operations in the dragStartCallback. The highest priority vote is the first
vote the source gives. If the intersection is empty, then the cursor which indicates
that an operation is not allowed is shown.
The mapping of the shift and control key combinations to drag operations can be
customized, too. The default mappings are:
None All operations are allowable.
The cursor images to be shown for an operation are also configurable. The default
mappings are:
At a minimum, the application must create the source and target adapters on the
appropriate widgets. On the source adapter it does not need to hook the
dragStartCallback unless it intends to change the vote, images, or offsets to values
other than the defaults or unless it wants to deny drag and drop in certain cases
by setting the doit flag to false. An application must hook the dragCompleteCallback
to perform the operation. The dragChangeCallback does not need to be hooked, nor
does the dragCancelCallback.
On the target adapters, the application only needs to hook the dragOverCallback if it
needs to check the kind of items being dragged to ensure that the target widget
can receive them or if it does not want the default target vote. It should hook the
dropCallback to perform the operation. It does not need to hook the
dragLeaveCallback or dragCancelCallback.
Tip: Most of the extended widgets are useful without drag and drop, but for
EwIconArea widgets, drag and drop is needed just to move the items around
within the widget. The EwIconArea widget provides an API to support
internal-only drag and drop without having to create a source and target
adapter, and without having to hook any callbacks. The method
configureForDefaultDragAndDrop automatically does this. It limits drag and
drop to only allow the user to move items within the container. If the
application requires any additional capabilities, it should not use this simple
API, but should create its own adapters and hook the callback accordingly.
Widget limitations
Technically, any widget is capable of drag and drop, although some types of
widgets are better suited than others. Drag and drop works best on container style
extended widgets such as EwIconArea, EwIconList, EwFlowedIconList, EwIconTree,
EwTableList, and EwTableTree. On these widgets, the sourceItems, targetItem, and
Drag and drop for the other list-oriented widgets such as CwList and CwComboBox
carries some limitations. First, the sourceItems, targetItem, and itemUnderCursor fields
in the various callData only ever contain strings when the callbacks are triggered.
However, the application can override these values in the callback handlers. Also,
because these widgets do not provide any way to determine which items are under
the cursor, they always assume that, regardless of where a drag actually starts, all
selected items are dragged. Drag and drop on these widgets is further limited by
the fact that they cannot show insertion or target emphasis. This implies that it is
not possible to inform users where items will be inserted if they are dropped on
one of these widgets.
Drag and drop to and from non-list-oriented widgets is more severely limited.
Because there are no commonly accepted semantics for drag and drop on labels,
buttons, forms, or other non-list-oriented widgets, the application must define
what is meant, for example, by dragging from a CwLabel to a CwToggleButton. Also,
the sourceItems, targetItem, and itemUnderCursor fields in the various callData are
always left nil.
A print job is a document stored within the computer that contains zero or more
pages of output. Print jobs have platform- and device-specific attributes associated
with them such as print resolution, form selection, special printer effects such as
orientation and duplex printing, and so on. Common Printing encapsulates these
attributes in the CgPrintJobAttributes class.
A CwPrinterShell provides job and page control methods, and processes printing
events. The window associated with a CwPrinterShell represents the printable area
on the physical page. Applications use Common Graphics operations to draw on
this window.
A print server is an object that manages one or more printers and common
resources such as printer fonts. In IBM Smalltalk, a connection to a print server is
represented by a CgDisplay. Each CgDisplay contains at least one CgPrinterScreen
object and zero or more CgScreen objects.
The following diagram shows how the classes are related from the applications
point of view.
Selecting a printer
There are several different approaches to selecting a printer. The recommended
approach is to allow the user to choose a printer display using a printer prompter.
For more information on CwPrinterPrompter, see Using the printer prompter on
page 275.
If a user selection is not required, the application can choose to use the default
printer. The default printer can be obtained by sending default to CgPrinterScreen
class. It can be changed via printer configuration tools provided by the operating
system, for example, the Print Manager in Windows or a Printer object in OS/2. If
there is no default printer, default answers nil.
Display names used for opening a printer display with openDisplay: can be
obtained from CgDisplay allPrinterDisplayNames. Do not cache the results of
allPrinterDisplayNames because printers can be added or removed from the
operating system at any time. To obtain a human-readable name of a printer, use
CgPrinterScreen name, rather than CgDisplay displayString
The CwPrinterShell notifies the application of printing events using the widget
callback mechanism. The callback model for printing is described in Adding
callbacks on page 277. See the Common Widgets chapter for more information
on callbacks.
A printer prompter returns the display name of the selected printer, or nil if no
printer is selected. The prompter also returns nil if no printers are available.
The display name returned by the prompter is used to open a connection to the
desired printer display by sending openDisplay:; to CwAppContext default. A printer
shell can then be created by sending appCreateShell:applicationClass:display:argBlock:;
to the CwPrinterShell class.
The following sample code shows typical printer prompter use, including opening
the selected display, retrieving selected job attributes, and creating a printer shell.
| prompter displayName display jobAttributes printerShell |
prompter := CwPrinterPrompter new.
printerShell := CwPrinterShell
appCreateShell: 'print job'
applicationClass: nil
display: display
argBlock: [ :w | w jobAttributes: jobAttributes].
Print job attributes can be configured by the user in two ways: default attributes
via printer configuration tools provided by the operating system, and job-specific
attributes via the platform- and printer-specific setup dialog of a printer prompter.
The application can obtain print job attributes by sending defaultJobAttributes to a
CgPrinterScreen or by sending jobAttributes to a CwPrinterPrompter after the user has
selected a printer. Attributes can also be saved between sessions.
CgPrintJobAttributes can be passed to a CwPrinterShell in the create argBlock for the
shell using the jobAttributes:; resource setting method.
Creating a shell
There are two different methods of creating a printer shell.
v CwPrinterShell>>createApplicationShell:argBlock: creates a printer shell on the
default printer display. Resource-setting messages can optionally be sent to the
shell in the create:argBlock:.
v CwPrinterShell>>appCreateShell:applicationClass:display:argBlock: allows an
application to specify the printer display (the print server) the shell is to be
associated with. This method is typically used when a printer display has been
selected using a printer prompter.
The screen: method can be used in the shells create:argBlock: to specify the
CgPrinterScreen (the print device) the shell is to be associated with. The screen
must be one of the printer screens of the shells display. If a screen is not specified,
the shell simply uses the default printer screen of the display. Because there is
rarely more than one CgPrinterScreen associated with a printer display, the default
printer screen is typically used.
Print job attributes for the shell can also be specified in the create:argBlock: via the
jobAttributes: method. All print jobs produced by the shell take on the given job
attributes. If job attributes are not specified at shell creation time, the shell obtains
default job attributes from its printer screen. Job attributes are not checked for
compatibility with the selected printer, but the application can use
isCompatibleWith:; to test compatibility if desired.
Both printer shell creation methods answer the newly created shell widget, or nil if
the widget cannot be created. An error message is displayed if a printer display or
printer screen is requested that is not valid.
Adding callbacks
Common Printing uses the callback mechanism to inform applications of events
related to the printing process. Callbacks are added to a shell using the
addCallback:receiver:selector:clientData: method.
Starting a job
After creation, a printer shell widget must be realized by invoking realizeWidget.
The shell responds with a map callback to indicate readiness for job control
messages. At this point the shell can be used to start print jobs.
After reception of a map callback the application sends startJob to the shell, which
begins a new document with the title specified by the shells title resource. The
title is used to identify the document within the operating system, and typically
includes the name of the application producing the document. Title changes only
take effect prior to the beginning of a new print job.
Producing a page
The actual contents of a print job consist of graphics and text created with the
Common Graphics API. See Printing with Common Graphics on page 279 for
more information on printing graphics.
An application uses the following steps to produce pages of graphics and text:
1. Send a startPage message.
2. Upon reception of an expose callback, draw graphics on the window associated
with the shell (window). The number of the page currently being drawn is
specified in the callback data of the expose callback.
3. Send an endPage message.
Ending a job
The endJob method ends a print job and submits the job for printing. A job can also
be canceled at any point by calling cancelJob. In this case, an attempt is made to
erase the entire contents of the print job and cancel printing of the job. It is not
necessary to issue endJob after canceling a print job.
Once a print job is complete or has been canceled, an application can begin
another document by issuing startJob and beginning the printing process again.
When all print jobs are complete, destroyWidget should be used to destroy the
printer shell and release its associated resources. Finally, the printer display should
be closed if it is no longer needed.
Tip: If necessary, device-specific data can be sent directly to a printer, one byte at a
time using the CwPrinterShell method sendRawPrinterData:.
The font model for printing is the same as that for screen graphics. However,
printer fonts generally work only on printers and not on video displays. In
addition, video display fonts do not necessarily work on printers, although in
practice many do. The application can determine which fonts are supported by
both the video and printer displays by comparing the font information returned by
listFonts:maxnames: for each display.
Common Printing does not support cursors, because cursors are screen-specific
objects. Printed image support (including that for CgIcon) is provided via
CgDeviceIndependentImage. Common Printing does not support CgPixmap, because
pixmaps are device-specific. Applications must take printer resolution into account
when scaling images for the printer.
A complete example
The following example prints two pages of text, diagonal lines, and filled circles.
The printer where the pages are being sent is prompted for, and if there are no
printers available or if no printer is chosen, no printing is done.
Object subclass: PrintingExample
instanceVariableNames: 'done gc fontStruct'
classVariableNames: ''
poolDictionaries: 'CwConstants CgConstants'
print
"Print two similar pages of text and graphics.
done := false.
The port is any string which users will recognize as referring to a particular
printer. The print_command is the command used for sending output to the
printer port (for example, lp -d or lpr -P. For example, suppose you have two
printers, ADMIN and LAB. Your definitions might look like the following:
ADMIN=rsh bandit "lp -d ps"
LAB=lpr -Pgonzo
Note: Some systems use lp -dqueuename to send output to the printer; others
use lpr -Pqueuename. You should specify whatever command is
appropriate to generate printed output on your system.
5. Select Add/Replace to add the new port in the list of current port definitions.
6. Repeat for each printer you want to send output to.
Note: To modify an existing port using the Printer Setup dialog, select the port
you want to modify and edit the port information in the Edit Port input
area, then select Add/Replace.
7. In the Printer Devices field, select the description that matches the printer you
are installing. If no description matches your printer, contact your printer
vendor for a PPD file.
8. Select the desired port in the Current Port Definitions list box and select Add
Selected. The new printer is now included in the list of currently installed
printers.
To close any dialog without accepting the changes, select Cancel. To close a dialog
and accept the changes, select Dismiss.
Button Description
Apply Provides changed configuration information to the application without
updating the default printer
Save Saves the current configuration information as your default printer
Reset Reloads the default configuration from $HOME/.Xpdefaults
Cancel Closes the dialog and aborts all configuration changes
Options Displays the options dialog box that allows you to select a different
printer setup
Install Displays the installation dialog box that allows you to add or remove
printer devices and printer ports
Selecting the Install button opens a dialog allowing you to add and remove printer
definitions.
Printer name, resolution, page size, and paper tray can be changed using this
dialog. Selecting the arrow button t the right of the field displays a list of valid
values from which you can choose. The values presented differ based upon the
particular printer selected. To save the configuration as the default, after you have
changed the printer option, select Save. Select Apply when finished to close the
printer prompter.
The Xpdefaults file is read-only. When a user modifies the printer information in
this file using the printer prompter, the resulting information is stored in the users
home directory in the file .Xpdefaults. If a users home directory contains an
.Xpdefaults file, this file will always be used in preference to the common
Xpdefaults file. Each user, therefore, can have a different printer configuration. As
a result, it is necessary to rename the $HOME/.Xpdefaults file to Xpdefaults and
move the file to the printing directory for all users to have access to the newly
installed printers.
This chapter describes the IBM Smalltalk Virtual Machine API and includes the
following:
v Calling functions in other languages from IBM Smalltalk
v Calling IBM Smalltalk from other languages
v Writing user primitives
This chapter assumes a familiarity with the C programming language and with the
C compiler for the IBM Smalltalk platform.
Conventions
The conventions used in this chapter are as follows:
v All publicly available IBM Smalltalk functions and macros are prefixed with Es.
This prevents name conflicts with user-developed code that is linked with an
IBM Smalltalk virtual machine.
v The terms byte, word, and long refer to 8-, 16-, and 32-bit quantities,
respectively.
v C language code in text is in the example font, while Smalltalk code is in italics.
v All function descriptions are in C and have the following form:
returnType functionName(type arg1, type arg2, ...)
This format is used to describe everything, even though some of the functions are
actually implemented as macros. You can see examples of this presentation format
in Functions available in user primitives on page 320.
Defined types
esuser.h defines the following C data types:
U_8, U_16, U_32
Unsigned 8-, 16-, and 32-bit integers
I_8, I_16, I_32
Signed 8-, 16-, and 32-bit integers
BOOLEAN
Either TRUE or FALSE (also defined)
EsObject
Pointer to an IBM Smalltalk object
EsVMContext
Pointer to a low-level process information block
EsBehavior
Pointer to a kind of Behavior (for example, Class or Metaclass)
EsGlobalInfo
Information global to all processes
EsPrimitiveTable
An IBM Smalltalk user primitive table
Object types
The five types of IBM Smalltalk objects include:
v Immediate objects
v Pointer objects
v Byte objects
v Word objects
v Long objects
In addition, all of these object types except immediate can be marked as one of
the following:
v Read-only
v Fixed
Pointer objects
A pointer objects instance variables or indexed slots contain references to other
objects. An example of this type of object is Array.
Read-only objects
Pointer, byte, word, and long objects can be marked read-only. A read-only object
cannot be stored into by Smalltalk.
Note: For immediate objects in ROM, this method will always answer true
because these objects are always read-only.
attemptedROMStore: storedObject intoSlot: anInteger
Sends this message to a read-only object when an attempt is made to store
into it. anInteger is the instance variable index of the slot in the receiver
where the store was attempted (this is the same index used by
instVarAt:put:). storedObject is the object being stored.
If this method answers true, the store operation is retried. If the receiver
has been marked read-write, the store will take place. If not, this message
will be sent again. If this method answers false, the store operation is
ignored.
Note: storedObject and anInteger are suitable for instVarAt:Put: not at:put:. If
the receiver is a String or DBString, storedObject will be an Integer,
not a Character.
Fixed objects
Pointer, byte, word, and long objects can be made fixed. A fixed object cannot be
moved by the Smalltalk garbage collector. Fixed objects can be used to provide C
code with memory that can be used to store results (for example, a buffer for a
communications interrupt handler). Once an object has been made fixed, its
address does not change.
Note: Fixed objects that become garbage are not garbage collected until the next
time the image is started. At that time, IBM Smalltalk can be sure that there
are no C code references to the object and the object can safely be collected.
For example, to create a PlatformFunction for the DosBeep function in OS/2, do the
following:
dosBeep :=
PlatformFunction
callingConvention: 'c'
function: 286
library: 'DOSCALL1'
parameterTypes: #(uint32 uint32)
returnType: #uint32.
See Platform requirements on page 333 for details about platform-specific calling
conventions. The function: parameter can be a positive Integer or a String. The
library: parameter is the name of the shared library where the function is stored. It
can be a String or nil. The parameterTypes: argument is an array of type names (see
below). The returnType: argument is a single type name.
If the library is given as nil, the function must be a String. The function is found in
the user primitive table instead of in a shared library. The string is the name of the
user primitive. The function that is called is not a user primitive; it is a normal C
function. See User primitive tables on page 319 for details on how to add
functions to the table.
Parameter types
The supported parameter types for the C language are:
none, void
This is not a valid parameter type. If used as the return type, the value nil
is returned.
object
No conversion is performed. The parameter value is passed directly to the
Passed parameters
All values passed to C functions are extended to a 32-bit quantity before being
passed. Signed integers are sign extended; unsigned integers are zero extended.
Because the C compiler does the same thing, it will be transparent to you.
All of the int types perform the same conversion for when converting from IBM
Smalltalk objects to external language values. For example, it is legal to pass -1 as a
uint32 or a uint8. Both result in 0xFFFFFFFF being passed.
Calling a PlatformFunction
Once a PlatformFunction has been created, it can be called using call, callWith:,
callWith:with:, and so on. The number of arguments in the call must match the
number of arguments in the PlatformFunction.
For example, to call the DosBeep function described above, you might use the
following statement:
rc := dosBeep callWith: 100 with: 200
Error cases
The call to a PlatformFunction can fail and cause a walkback for the following
reasons:
Invalid argument count
The number of parameters in the PlatformFunction is not the same as the
number of parameters in the call.
Invalid class
The parameter could not be converted because its class is invalid (for
example, trying to pass a Float in an int32 parameter). All such type
conversions must be done explicitly (for example, pass the Float truncated
as the parameter).
Value out of range
The Character or Integer value is not in the range allowed for the type (see
above for the ranges).
Not enough memory
There is insufficient memory to allocate a LargeInteger for the return value
of the function.
Operating system error
An operating-system-specific error occurred; for example, it could not find
the shared library or the function in the library.
For example:
send: msg to: id
<c: int32 'message':sendTo int32 int32>
|self primitiveFailed
The receiver of the method is not used in the function call. The number of
arguments to the method must be the same as the number of parameters specified
in the external function call. If callingConvention: specifies a returnType of void
or none, the Smalltalk method returns the nil.
If the call to the external function fails (for the reasons described in Error cases
on page 290), the fail code (the rest of the code in the method) is run. If no fail
code is provided, self is returned on failure. The suggested default fail code is |self
primitiveFailed. It generates a walkback describing the reason for the failure and
displaying the operating system error.
PlatformFunction protocols
The address of a PlatformFunction is not looked up until required (for example,
when calling it). Looking up the address is referred to as binding. A
PlatformFunction can also be unbound by the system, meaning that it must be
bound again before being called.
Note: If the library is given as nil, the function must be a String. The
function is looked up in the user primitive table instead of in a
shared library. The String is the name of the user primitive. The
function that is called is not a user primitive; it is a normal C
function.
callingConvention:address:parameterTypes:returnType:
Answers a new PlatformFunction with the given parameters. The
callingConvention:, parameterTypes:, and returnType: are the same as above.
The address: is a positive Integer. Use this method to create a
PlatformFunction for a function that is not in a shared library but whose
address is available by some other means (for example, returned by an
operating system call). A PlatformFunction created in this manner has its
address set to zero when the image starts.
Each instance has a logical name and a physical name. The logical name is the
name used to refer to the library in Smalltalk (for example, the library name used
in a PlatformFunction). The physical name (which can be the same as the logical
name) is the name that is passed to the operating system. The PlatformLibrary class
keeps a list of all instances that it creates. This way, multiple instances with the
same logical name are never created.
Entry points
IBM Smalltalk allows other languages to call Smalltalk through the EsEntryPoint
mechanism. An EsEntryPoint is an object that prototypes a function in another
language such as C. An EsEntryPoint contains the Smalltalk receiver and selector as
well as the types of the parameters and any return value.
To use an EsEntryPoint, create it and send it the message address. The result is an
integer that can be passed out to a PlatformFunction that requires a function pointer
as a parameter. When the external language calls the function pointer, IBM
Smalltalk converts the parameters into Smalltalk objects and sends the message
designated by the receiver and selector of the EsEntryPoint. The receiver parameter
can be any Smalltalk object. The selector parameter must be a Symbol.
parameterTypes is an array of type names and returnType is a single type name. The
callingConvention parameter must be a string that is one of the valid IBM Smalltalk
calling conventions. For details about platform-specific calling conventions, see
Platform requirements on page 333.
Note: When using an EsEntryPoint, you do not need to make any of the objects
(the receiver, selector, or the EsEntryPoint itself) fixed.
Returned parameters
All values returned to external language functions are extended to a 32-bit quantity
before being returned. Signed integers are sign-extended; unsigned integers are
zero-extended. All the int types perform the same conversion when converting
from IBM Smalltalk objects to external language values. For example, -1 can be
returned as a uint32 or a uint8. Both result in 0xFFFFFFFF being returned.
Calling an EsEntryPoint
Once an EsEntryPoint is created, send it the message address to bind it. The
address can be passed to an external language function that requires a function
pointer. For example, to get the address of the WindowProc callback described
above:
address := windowProc address.
External functions calling Smalltalk using EsEntryPoint must run in the same
thread as Smalltalk.
Error cases
The call to address can fail and cause a walkback for the following reasons:
No more entry points
All available EsEntryPoint addresses are in use.
Invalid calling convention
The EsEntryPoint is using an unsupported calling convention.
Not enough memory
There is insufficient memory to allocate a LargeInteger for the return value
of the function or to allocate internal support structures.
EsEntryPoint failure
When the address of an EsEntryPoint is called by the external language, there is
always the possibility of failure. It may not be possible to allocate the necessary
objects required for the conversion to Smalltalk objects. The return value from
Smalltalk may be invalid and unconvertable.
EsEntryPoint instances contain a fail address that defaults to 0. When the callback
fails for any reason, this address is checked. If it is 0, 0 is returned from the
callback. If it is not 0, it must be the address of a function that has exactly the
same prototype as the EsEntryPoint. Upon failure, this function is called.
For example, in Windows, the following code can be used to catch a WindowProc
failure:
windowProc failAddress: (PlatformFunctions at: 'DefWindowProc') address
For example, if a socket receive call that blocks is made in Smalltalk, no Smalltalk
processes run until the receive call completes. By using asynchronous callout, only
those processes waiting on the return value of the receive operation block.
The syntax for using asynchronous calls is similar to normal platform function
calls. The system sets up the OS resources required to make the call, executes the
call, and recovers the resources automatically.
With resource futures calls, the system manages the OS resources. The system sets
up the OS resources required to make the call, executes the call, and recovers the
resources automatically. Resource future calls can be used for single asynchronous
calls only.
Static futures calls require the developers to manage resources themselves. The
advantage of a static future call is that it can be used to make more than one
platform function call in the same thread. The developer must explicitly allocate
the resources for the future and return them when the future is no longer required.
The number of arguments in the call must match the number of arguments in the
PlatformFunction. The following sections describe how to make asynchronous calls
using each of the protocols.
param1 and param2 are the arguments to the call. dosBeep is a previously declared
platform function. The rc is the return value of the platform function, or an
AcoError if an error occurred during the execution of the call. For a discussion of
errors, see ACO errors and error cases on page 301.
When a standard asynchronous call is made during a system callback (that is when
the operating system has called back into Smalltalk), the call is performed
synchronously. Standard asynchronous calls are the fastest form of asynchronous
call.
When a resource future call is made, a future is returned. This future can be
queried for the state of the call while the platform function executes in a separate
thread. If a process asks for the value of the future before the value is ready, the
process is suspended. More than one process can wait on the return value of the
future. When the call completes, the result is posted back to Smalltalk and the
processes that are waiting on the result are resumed.
param1 and param2 are the arguments to the call. The rc is the return value of the
platform function, or an AcoError if an error occurred. The aFuture is a future that
contains the result of the platform function. You must explicitly ask the future for
To check the status of a blocking call, the messages checkForValue or isReady can be
sent to the future. The message checkForValue answers the return value if it is ready
or an AcoError if it is not yet ready. The message isReady answers true if the return
value is ready; otherwise, it answers false. When a future is asked for its value
during a system callback (that is when the operating system has called back into
Smalltalk), if the return value has not been calculated, then an AcoError is
answered. Resource future calls are slower than standard asynchronous calls.
In addition, static future calls allow you to use the same operating system thread
in more than one asynchronous call. The static future call does this by reserving a
thread. This is useful when data is stored in the local memory for a thread. As a
result, you must manage the resources by explicitly creating and destroying static
futures and their resources. Allocating resources for static futures on page 301
discusses the allocation and deallocation of resources.
To begin a static future call, the developer creates a static future and makes the
call. When the call completes, the result is posted back to Smalltalk and processes
waiting on the result are resumed. At this point, the developer may either reuse
the static future or return its resources to the AcoResourceManager.
param1 and param2 are the parameters to the call. The rc is the return value of the
platform function. The aFuture is a static future that contains the result of the
platform function. You must explicitly ask the future explicitly for its value. If the
platform function has not completed, the process asking for the value of a future
blocks until the value is ready.
The static future can be reused for other platform function calls. These calls will be
made in the same operating system thread. Once the developer no longer requires
the static future, it must be returned to the AcoResourceManager.
To check the status of a blocking call, the messages checkForValue or isReady can be
sent to the future. The message checkForValue answers the return value if it is ready
or an AcoError if it is not ready. The message isReady answers true if the return
value is ready; otherwise, it answers false.
Before a static future can be used in a subsequent call, the previous call must have
completed. There are two protocols for determining this. Sending value to the
future guarantees that the previous call has completed before the next starts
When a future is asked for its value during a system callback (that is when the
operating system has called back into Smalltalk), if the return value has not been
calculated, then an AcoError is answered.
Static future calls are slower than standard asynchronous calls, but faster than
resource future calls.
Walkbacks
The asynchronous call to a PlatformFunction can fail and cause a walkback for
several reasons. The reasons are identical to the error cases for callWith:, which are
described in Error cases on page 290.
ACO errors
The return value from an asynchronous call is either the return value of the
platform function or an AcoError; the AcoError identifies the problem that occurred
while making the asynchronous call.
To test for an error, isAcoError can be used. For example, to determine whether an
error occurred, the following code can be used.
Objects are otherwise passed in the same way as platform function calls using the
callWith:... protocol.
Because objects are copied during an asynchronous call, the parameters passed to
the function should not be used while the asynchronous call is in progress. The
contents of the parameters may be changed at any time by the asynchronous call.
Managing resources
Asynchronous calls are made in a separate operating system thread. When the call
is blocked waiting for a resource, only that thread blocks and not the thread that
IBM Smalltalk is running in.
To support asynchronous callout, IBM Smalltalk manages both the thread and
some additional resources in fixed space. The additional resources allow IBM
Smalltalk and the asynchronous thread to interact.
Additionally, the image must have adequate fixed space allocated to allow
asynchronous calls to be made.
The resource manager allows developers to control how operating system threads
and fixed-space resources are managed. The resource manager allows you to
control the resources using the following selectors:
cacheSize:, cacheSize
The number of resources to cache. The default is 10 threads.
maximumNumberOfResources:, maximumNumberOfResources
A maximum limit on the number of resources created. On some systems,
all the processes compete for a limited set of threads. This value is used to
limit the number of threads that Smalltalk acquires for itself. The default is
no limit.
limitOfReclaimedFutures:, limitOfReclaimedFutures
The number of futures that are recovered from fixed space when the image
starts up. The default is 10.
defaultStackSize:, defaultStackSize
The stack size that threads are created with. The default is 8192 bytes.
defaultPriority:, defaultPriority
The priority that operating system threads run at. This is a value between
1 (lowest priority ) and 7 (highest priority).
Resource limitations
An asynchronous call can have problems with resources by either:
v Losing a resource, for example, when a thread is killed
v Failing to acquire a resource
If resources are lost during a call, an AcoError is returned from the asynchronous
call indicating that the resources associated with the asynchronous call were lost.
Note: If you save an image, all the calls proceed normally in the running image.
However, when you load the saved image, the threads for the asynchronous
calls have been terminated, and the asynchronous calls indicate that they
have lost their resources.
The three different types of asynchronous calls can fail to acquire resources if one
of these conditions is true:
v The AcoResourceManager has reached the limit set by
maximumNumberOfResources:.
Depending on the type of asynchronous call, there are different responses to the
failure to acquire resources. Calls using asyncCall or futureCall are automatically
queued, on a first-come, first-serve basis, until the resources become available. As a
result, standard asynchronous calls and resource future calls are delayed if there
are not enough resources. Calls using the staticFutureCall: method answer an
AcoError indicating that the resources cannot be allocated.
Maintaining a thread
The AcoResourceManager and AcoStaticFutures let the developer maintain the thread
an asynchronous call is made in. This is important for allowing more than one call
to be made in the same thread. For example, getting the last error is thread-specific
and must be guaranteed to occur in the same thread as the call that generated the
error.
AcoResourceManager lets you specify a set of calls to be made in the same thread,
using the lockThreadIn: aBlock protocol. All the calls using asyncCall performed in
aBlock are made in the same thread. The resources are reserved exclusively for calls
made in this block until the block finishes executing.
An AcoStaticFuture explicitly acquires and releases its threads. Between the time
that an AcoStaticFuture acquires a thread and releases it, its thread is reserved for
exclusive use by that future.
OSObjects
Traditionally, Smalltalk has been ill-suited for modeling the complex memory
structures used by low-level languages like C, and by most modern operating
systems. Often programmers were required to build facilities for accessing these
structures using variable-byte classes and complex glue code. IBM Smalltalk, in
addition to having byte, word, and long classes for accessing simple array-like
memory structures efficiently, provides a mechanism for describing and accessing
arbitrary memory structures.
OSObject subclasses
Subclasses of class OSObject are used to model particular kinds of memory
references. OSObject closely models C-style addressing and structure sharing.
Every OSObject has an indirection level that describes how many times a pointer
must be followed to reach the actual data (a sequence of bytes in memory). The
operation of dereferencing a pointer results in another pointer or immediate data
and reduces the indirection level of the new pointer by one.
OSBaseType
You use subclasses of OSBaseType to represent pointers to C base types.
Dereferencing an OSBaseType answers a Smalltalk immediate type. All subclasses of
OSBaseType have indirection level one. To model int * (a pointer to a C array of
int), use the class OSInt32. Dereferencing an instance of OSInt32 results in a signed
Smalltalk integer. IBM Smalltalk provides the following standard OSBaseType
subclasses:
v OSBool8, OSBool16, OSBool32
v OSChar8, OSChar16
v OSFloat32, OSFloat64
v OSInt8, OSInt16, OSInt32
v OSUInt8, OSUInt16, OSUInt32
v OSVoid
OSStructure
You use subclasses of OSStructure to represent arrays of structs and unions. All
subclasses of OSStructure have indirection level one. To model struct POINT * (a
pointer to a C structure struct {int x, y} POINT;), a new subclass of OSStructure
is required. OSStructure provides the inherited class instance variables fixedSize that
must be assigned before the class can be used. In this example, fixedSize is 8 (the
size of int x, y;).
OSVariableStructure
You use subclasses of OSVariableStructure represent variable-sized structures.
Dereferencing is disallowed because the size of each element is not known. To
model struct DATA * (a pointer to struct {int cbSize; char data[1]} DATA;), a
new subclass of OSVariableStructure is required. OSStructure provides the inherited
class instance variables fixedSize and variableSize that you must assign before the
class can be used. In this example, fixedSize is 4 (the size of int cbSize;) and
variableSize is 1 (the size of one data element in the variable-sized portion of the
structure, or char data [1];).
OSObjectPointer
OSObjectPointer is used to represent pointers to other instances of OSObject.
Dereferencing an OSObjectPointer answers a new instance of OSObjectPointer or an
OSObject subclass.
OSObjectPointer can point to any OSObject. You can also use subclasses of
OSObjectPointer to model pointers to a single type of OSObject. The class of this
OSObject is specified by assigning the defaultItemType class instance variable in the
OSObject protocols
This section describes the protocols supported by OSObject.
Instance methods
castTo: Answer a new instance of anOSObjectClass, which is initialized to refer to
the same data as the receiver using the appropriate indirection level.
isOSObject
Answer true if the receiver is an OSObject or one of its subclasses.
isUndefined
Answer true if the receiver has no value.
makeUndefined
Make the receiver undefined. OSObject and PlatformFunction primitives fail
when used with an undefined OSObject.
OSImmediate protocols
This section describes the protocols supported by OSImmediate.
Instance methods
= Answer a Boolean indicating whether the receiver and anOSObject are
equal. Two immediates are equal if they have the same class and equal
values.
asInteger
Attempt to coerce the receivers value to an integer.
indirectionLevel
Answer the receivers indirection level.
isImmediate
Answer true if the receiver represents an immediate data element.
isNull Answer true if the receiver is a NULL value.
notNull
Answer true if the receiver is not a NULL value.
Class methods
immediate:
Answer a new instance of the receiver with indirection level 0, referencing
a data element with the same value as the parameter.
new Answer a new instance of the receiver with indirection level 0, referencing
0.
value: Same as immediate:.
OSStructure protocols
This section describes the protocols supported by OSStructure.
OSVariableStructure protocols
Class methods: initialization
variableSize
Answer an Integer that is the size in bytes of one data element of the
variable portion of the receiver.
variableSize: anInteger
Set an Integer that is the size in bytes of one data element of the variable
portion of the receiver. This value must be set before using an instance of
the receiver.
OSBaseType protocols
at: anInteger
Answer the data element at index anInteger from the data element
referenced by the receiver. Indexing is done in terms of data elements, not
bytes. Subclasses of OSBaseType override this method to return the
appropriate Smalltalk base type (for example, OSBool16 returns a boolean).
at: anInteger put: anOSObject
Set the data element at index anInteger in the data element referenced by
ObjectPointer protocols
Class methods: initialization
defaultItemType
Answer the default item type for new instances of the receiver. When not
specified, instances of the receiver point to items of this class.
defaultItemType: anOSObjectSubclass
Set the default item type for new instances of the receiver. When not
specified, instances of the receiver point to items of this class.
User primitives
IBM Smalltalk supports three syntaxes for user primitives:
messagePattern
<primitive: primitiveName>
...fail code...
messagePattern
<primitive: 'sharedLibrary':functionName>
...fail code...
messagePattern
<primitive: 'sharedLibrary':functionNumber>
...fail code...
The first syntax looks up the primitive in the user primitive table. primitiveName is
an alphanumeric identifier. It is not surrounded by quotation marks. For example:
add: a and: b
<primitive: add2Numbers>
|self primitiveFailed
The other two syntaxes look up a user primitive function in a shared library, either
by name or by number. The sharedLibrary, functionName, and functionNumber fields
have the same syntax and function as those in a PlatformFunction. sharedLibrary
must be surrounded by quotes. functionName has no quotes. For example:
myFunc: arg1
<primitive: 'MYLIB':myFunc>
|self primitiveFailed
The user primitive acts the same way whether it is in the user primitive table or in
a shared library. If the primitive succeeds, the object returned from the primitive is
returned from the method. If the primitive fails, the fail code (the rest of the code
in the method) is run. If no fail code is provided, self is returned on primitive
failure. The suggested default fail code is |self primitiveFailed. It generates a
walkback describing the reason for the failure.
For platforms that require static linking, all user primitives are accessed by the
virtual machine through the user primitive table, EsUserPrimitiveTable.
The file userprim.c contains an empty user primitive table. To add user primitives,
modify the file to include new primitives. For example:
EsDefinePrimitiveTable(EsUserPrimitiveTable)
EsPrimitiveTableEntry("nameFromSmalltalk", function)
...
EsEndPrimitiveTable
nameFromSmalltalk is the exact (case sensitive) name that the Smalltalk user
primitive used in the <primitive: > statement.
See the sections at the end of this chapter for platform-specific examples of
building a new user primitive table.
EsDefinePrimitiveTable(EsUserPrimitiveTable)
EsPrimitiveTableEntry("nameFromSmalltalk", function)
EsSubTable(file1Table)
EsSubTable(file2Table)
...
EsEndPrimitiveTable
/* file1.c */
EsDefinePrimitiveTable(file1Table)
EsPrimitiveTableEntry("nameFromSmalltalk", function)
...
EsEndPrimitiveTable
Object allocation
These functions enable users to allocate new objects from within a user primitive.
EsObject EsAllocateObject(EsVMContext vmContext, EsBehavior allocateClass,
U_32 size, U_32 saves, EsObject ** saveList)
Returns a new, initialized object of class allocateClass with size indexable
instance variables. Pass 0 for saves and NULL for saveList. The instance
variables of pointer objects are initialized to nil. All other objects are
initialized to 0.
Error cases: If the object could not be allocated for any reason, returns
NULL.
Smalltalk equivalent: allocateClass new: size
Side effects: This operation can cause a garbage collection.
EsObject EsAllocateFixedObject(EsVMContext vmContext, EsBehavior
allocateClass, U_32 size, U_32 saves, EsObject ** saveList)
Same as EsAllocateObject, except the object is allocated in fixed space.
This means that the object does not move during a garbage collect.
Error cases: If the object could not be allocated for any reason, returns
NULL.
Smalltalk equivalent: (allocateClass new: size) makeFixed
Sending messages
This function enables users to send a message from within a user primitive and
retrieve the result.
Testing objects
These functions enable users to determine what the class and type (immediate,
bytes, words, longs, or pointers) of an object are.
EsBehavior EsObjectClass(EsObject object)
Returns the class of object. The generic return type of EsBehavior is used
because the class of object can be a Class or Metaclass.
Smalltalk equivalent: object class
BOOLEAN EsIsImmediate(EsObject object)
Returns TRUE or FALSE, indicating whether or not object is immediate.
BOOLEAN EsIsSmallInteger(EsObject object)
Returns TRUE or FALSE, indicating whether or not object is a smallInteger.
BOOLEAN EsIsCharacter(EsObject object)
Returns TRUE or FALSE, indicating whether or not object is a character.
BOOLEAN EsIsNil(EsObject object)
Returns TRUE or FALSE, indicating whether or not object is nil.
BOOLEAN EsIsTrue(EsObject object)
Returns TRUE or FALSE, indicating whether or not object is true.
BOOLEAN EsIsFalse(EsObject object)
Returns TRUE or FALSE, indicating whether or not object is false.
BOOLEAN EsIsBoolean(EsObject object)
Returns TRUE or FALSE, indicating whether or not object is true or false.
BOOLEAN EsIsLargeInteger(EsObject object)
Returns TRUE or FALSE, indicating whether or not object is a largeInteger.
BOOLEAN EsIsFloat(EsObject object)
Returns TRUE or FALSE, indicating whether or not object is a Float.
BOOLEAN EsIsBytes(EsObject object)
Returns TRUE or FALSE, indicating whether or not the instance variables of
object contain bytes.
BOOLEAN EsIsWords(EsObject object)
Returns TRUE or FALSE, indicating whether or not the instance variables of
object contain words.
BOOLEAN EsIsLongs(EsObject object)
Returns TRUE or FALSE, indicating whether or not the instance variables of
object contain longs.
BOOLEAN EsIsPointers(EsObject object)
Returns TRUE or FALSE, indicating whether or not the instance variables of
object contain pointers.
Accessing objects
These functions enable users to access the instance variables of an object. For all of
the following functions that use an instance variable index, the first instance
variable is numbered 1.
U_32 EsInstSize(EsObject object)
Returns the number of named instance variables in object.
Smalltalk equivalent: object class instSize
U_32 EsBasicHash(EsObject object)
Returns the basic hash of the object.
Smalltalk equivalent: object basicHash
U_32 EsBasicSize(EsObject object)
Returns the number of indexed instance variables in object.
Smalltalk equivalent: object basicSize
U_32 * EsInstVarAddr(EsObject object)
Answers the address of the first instance variable of object. The result
value is cast to U_32 * regardless of the type of object.
Error cases: Assumes that object consists of not immediate.
Each object that is referenced only by the user primitive variables must be saved
before any operation that could cause a garbage collection. After that operation
completes, the objects must be restored in the reverse order from that in which they
were saved. All saved objects must be restored before the user primitive succeeds
or fails.
void EsSaveObject(EsObject object)
Pushes object onto the Smalltalk stack so that it is protected from a
garbage collection.
EsObject EsRestoreObject(void)
Pops the object on the top of the Smalltalk stack and returns it.
Miscellaneous
The virtual machine provides the following miscellaneous functions.
void EsRememberObjectStore(EsVMContext vmContext, EsObject targetObject,
EsObject storedObject)
Must be called whenever an object is stored into an instance variable of a
pointer object.
For example,
EsObject myObject;
EsObject storedObject;
EsObject * instVar;
instVar = (EsObject *) EsInstVarAddr(myObject);
*instVar = storedObject;
EsRememberObjectStore(EsPrimVMContext, myObject, storedObject);
versionNumber = EsVMVersion();
versionMajor = versionNumber >> 16;
versionMinor = versionNumber & 0xFFFF;
Because an interrupt handler could become active at any time (for example, when
the virtual machine is garbage collecting), normal objects cannot be used in
asynchronous messages. To support this, IBM Smalltalk allows objects to become
fixed, meaning that they do not move during a garbage collection.
When posting an asynchronous message, the receiver, selector, and any arguments
must all be fixed objects. In Smalltalk, send makeFixed to all objects that will be
used from interrupt handlers. After all of the objects are fixed, install the interrupt
handler (for example, with a user primitive that takes the fixed receiver, selector,
and arguments as parameters).
void EsPostNMI(EsVMContext vmContext)
Causes nmi to be sent to the active process at the next checkpoint. The
message is sent even if the process has asynchronous messages disabled.
BOOLEAN EsPostAsyncMessage(EsVMContext vmContext, EsObject receiver,
EsObject selector, U_32 argumentCount, ...)
Queues a message for processing by the active process at the next
checkpoint. The message is not sent if the process has asynchronous
messages disabled. All of the objects passed here must be fixed.
Error cases: If the message could not be queued for any reason, returns
FALSE. TRUE indicates successful queuing.
Do not save EsPrimVMContext in a global and use it in the handler. The saved
vmContext might not remain valid for the duration of the interrupt handler. The
globalInfo is always valid.
This mechanism can be used only from a clean point in the virtual machine. The
virtual machine is at a clean point when it calls a user primitive or
PlatformFunction. This mechanism should not be used during an interrupt handler,
because the virtual machine is not then at a clean point.
This mechanism is most useful for callbacks from the operating system. For
example, if you were to call out (using a PlatformFunction) to the operating system
and bring a window to the front, the operating system might immediately call
back asking the window to expose or paint. These messages can immediately be
sent to Smalltalk using EsSendMessage, because the virtual machine was at a clean
point when it called out to the operating system.
EsUserPrimitive(add2Numbers)
{
I_32 i1;
I_32 i2;
U_32 rc;
EsObject retVal;
rc = EsIntegerToI32(EsPrimArgument(1), &i1);
if (rc != EsPrimErrNoError)
EsPrimFail(rc, 1);
rc = EsIntegerToI32(EsPrimArgument(2), &i2);
if (rc != EsPrimErrNoError)
EsPrimFail(rc, 2);
rc = EsI32ToInteger(i1 + i2, &retval);
if (rc != EsPrimErrNoError)
EsPrimFail(rc, EsPrimArgNumNoArg);
EsPrimSucceed(retVal);
}
EsObject sampleCStringToString(EsPrimVMContext, str)
EsVMContext EsPrimVMContext;
char * str;
{
EsObject result;
U_32 i, size;
size = strlen(str);
result =
EsAllocateObject(
EsPrimVMContext,
EsPrimClassString,
size,
0, (EsObject **) NULL);
if (result != (EsObject) NULL)
for (i = 0; i < size; ++i)
EsByteAtPut(result, i + 1, (U_8) str[i]);
return result;
}
EsUserPrimitive(exampleMakeAString)
{
EsObject result;
result = sampleCStringToString(EsPrimVMContext,
"This is a C string");
if (result == (EsObject) NULL)
EsPrimFail(EsPrimErrNotEnoughMemory,
EsPrimArgNumNoArg);
EsPrimSucceed(result);
}
#ifdef LINKED_USER_PRIMITIVES
EsDefinePrimitiveTable(samplePrimitiveTable)
EsPrimitiveTableEntry("add2Numbers", add2Numbers)
EsPrimitiveTableEntry("exampleMakeAString",
exampleMakeAString)
EsEndPrimitiveTable
#endif
Now evaluate the following in a workspace (the expected result follows the --->):
nil add: 54 and: 7 ---> 61
nil add: 16r7FFFFFFF and: 1 ---> -2147483648 (overflow)
nil add: -1 and: -2 ---> -3
nil add: 1073741823 and: 1 ---> 1073741824 (LargeInteger)
nil add: $a and: 2 ---> walkback "invalid class in argument 1"
nil add: 0 and: 16r100000000 ---> walkback "value out of range in argument 2"
nil makeAString ---> 'This is a C string'
Note: IBM Smalltalk supports external language entry points that can be used
instead of hand-written code. See Entry points on page 294.
The Smalltalk code places the receiver and selector in fixed memory so that they
cannot move during garbage collection.
This code is based on the IBM Smalltalk code for the window procedure. Some
changes were made for clarity, so the code above might not match exactly the code
in IBM Smalltalk.
Platform requirements
This section describes, for each platform on which IBM Smalltalk runs, the
software required to write user primitives, the calling convention required for
PlatformFunction calls, and any other platform-specific information.
OS/2
Required software
The IBM VisualAge C++ compiler, or another compatible compiler, is required to
write user primitives.
User primitives
User primitives must be placed in a separate DLL. The DLL name must be
specified in the Smalltalk primitive declaration.
User primitives
User primitives must be placed in a separate DLL. The DLL name must be
specified in the Smalltalk primitive declaration.
16-bit PlatformFunctions convert and push char, bool, int8, int16, uint8, and uint16 as
16-bit values. pointer, object, struct, int32, and uint32 are converted and pushed as
32-bit values.
All 16-bit functions must be declared FAR and exported from their DLL.
PlatformFunction objects can also be created using the pascal16 and cdecl16 calling
conventions.
Note: On OS/2, all FAR PASCAL functions are exported as uppercase. The IBM
Smalltalk code must use an uppercase name to refer to these functions.
_cdecl functions prepend an underscore to the name. The Smalltalk code
must specify the underscore when calling these functions.
Two internal PlatformFunctions have been provided so that users can perform these
conversions manually, if required. They can be accessed as follows:
EsSelectorOffsetToLinear := "convert 16:16 to 0:32"
PlatformFunction
callingConvention: 'c'
function: 'EsSelectorOffsetToLinear'
library: nil
parameterTypes: #(uint32)
returnType: #uint32.
EsLinearToSelectorOffset := "convert 0:32 to 16:16"
PlatformFunction
callingConvention: 'c'
function: 'EsLinearToSelectorOffset'
library: nil
parameterTypes: #(uint32)
returnType: #uint32.
Note: If a pointer value is converted from 16:16 to 0:32 and then back to 16:16
(either automatically or manually), the final value will not necessarily be the
same as the original 16:16 value. It will, however, point to the same physical
memory location. Also, the functions do not assume that their argument is
an IBM Smalltalk object pointer, so they will erroneously convert immediate
object values.
These functions are also available from within user primitives. They are defined as
follows:
v U_32 EsSelectorOffsetToLinear(U_32 ptr)
v U_32 EsLinearToSelectorOffset(U_32 ptr)
UNIX platforms
Required software
The C compiler supplied with the operating system is required to write user
primitives.
User primitives
User primitives are statically linked with the virtual machine for these platforms.
You can create shared libraries containing user primitives.
In some cases, the error reported is 18 which is OSError. This indicates that an
OSError occurred during some primitive or system initialization. In the case of
image startUp, OSError 18 indicates that reading the image file resulted in an error.
Applications can return error codes from Smalltalk using the following code:
System exit: errorCode withObject: returnObject
The following error codes can be returned from the API functions or displayed in a
Fatal Application Error dialog box. Users can add new error codes with values
above EsPrimErrMinUserError.
Note: Not all of these error messages can actually appear in the Fatal Application
Error dialog.
Table 39. User Primitive Error Codes
Error Code Value Description
EsPrimErrNoError 0 No error occurred
EsPrimErrInvalidClass 1 An object did not have the correct type
(class)
EsPrimErrIndexOutOfRange 2 An index was out of range
EsPrimErrNotIndexable 3 An object that must be indexable was
not
EsPrimErrValueOutOfRange 4 A value was out of range
EsPrimErrReadOnly 5 An object was read-only and nothing
could be stored into it
EsPrimErrSignedNotDefined 6 Signed operations are not defined for
an object
EsPrimErrFPOverflow 7 Floating point overflow occurred in the
primitive
EsPrimErrFPDomainError 8 Floating point domain error
EsPrimErrFPGeneralError 9 Other floating point errors
EsPrimErrDivideByZero 10 Divide by zero attempted
EsPrimErrInvalidSize 11 An object did not have the correct size
EsPrimErrNotEnoughMemory 12 There was not enough memory to
allocate an object
EsPrimErrUnknownAPIType 15
EsPrimErrUnknownConversionType 16
EsPrimErrInvalidArgCount 17 Invalid argument count to the
primitive
This chapter:
v Introduces dynamic data exchange
v Explains how to build a DDE server and a DDE client
Introduction
A DDE conversation takes place between two applications. The client application
initiates a data exchange by requesting data from a server application. The server
responds by sending the requested data to the client application. A server can
provide data to many clients, but a client can make requests of only one server. An
application can create many DDE clients in order to exchange data with one or
more DDE servers. An application can be both a client and a server at the same
time. As in all client/server architectures, the client application always initiates the
DDE conversation.
The IBM Smalltalk implementation of DDE introduces DDE server and DDE client
objects. DDE servers can supply many DDE clients, but a DDE client can request
from only one server. An application written in IBM Smalltalk can have as many
DDE servers and clients as needed.
Previously, users have employed the clipboard for exchanging data between
different applications. This required the user to select the data, copy it to the
clipboard (usually by selecting a menu option), activate the second application, and
then paste the data into that application (again, usually by selecting a menu
option). This process is wholly dependent on the user, not only for copying the
data, but also for updating the data. DDE provides a mechanism for the complete
automation of this process. A DDE server application, for example, can be made to
send client data automatically without any user intervention.
There are many uses for this type of data exchange. A stock portfolio manager is
one example. Here a DDE stock server application might be created that, as part of
its initialization, would dial up a stock pricing service over a modem. It would
then collect data from the pricing service and make it available to any connected
DDE clients.
Upon startup, a DDE client application would make a request to connect to the
DDE stock server. The stock server would acknowledge that request and establish
a DDE conversation. The client application could then request continuous updates
on a specified, named piece of data (in this case the name of the stock). When the
data changed, the stock server application would automatically send a notification
to the client application. Then the DDE client application could recalculate its
entire portfolio and present its user with new or updated information.
Topic Identifies a logical grouping of data items within a named DDE server. A
piece of data is usually identified by an application (server name), topic,
and item. In a spreadsheet application, for example, the topic might be the
file name of a loaded spreadsheet.
Item Identifies a piece of data within a named topic of a named DDE server. An
items identification consists of a name and format specifier. The path to a
data item is usually denoted by a hierarchy of application (server name),
topic, and item.
Format
Specifies how a piece of data is to be interpreted by the receiving side of a
DDE conversation. In IBM Smalltalk, a format is specified by a Smalltalk
String (case insensitive). All data transferred to/from a DdeClient or a
DdeServerManager must be contained in a ByteArray object. The format
string tells each side of the DDE conversation how that data is to be
interpreted. There are certain predefined formats. The only exception to
this rule is the format String where the item being transferred must also
be a Smalltalk String.
System topics
A set of predefined topics and items that provide information of general
interest to DDE clients. Table 40 shows the predefined topics and items that
are supported by the default processing of the DdeServerManager.
Table 40. System topics
Topic Item Format Data
System Topics String A tab-delimited string of all the topics supported
by this server
System Formats String A tab-delimited string of all the formats for all
items
System SysItems String A tab-delimited string of all the items under the
System topic
System TopicItemList String A tab-delimited string of all the items under the
System topic
All topics TopicItemList String A tab-delimited string of all the items supported
by a topic
All topics Formats String A tab-delimited string of all the formats
supported by this topic
Hotlink
A request by a DDE client to a DDE server to be notified immediately
DDE classes
The following figure shows the typical Smalltalk DDE architecture. Either one of
these objects could be any other application that supports the DDE protocol for
either a DDE server or a DDE client.
DdeClient
DdeServer
message
DdeServer
The classes depicted in the figure are described in the following sections. Their
hierarchy is as follows:
Object
DdeClass
DdeClient
DdeServer
DdeServerManager
This hierarchy is not part of the windowing system (such as PM or Windows) but
it does use the windowing system of the underlying platform.
DdeClient class
An instance of the DdeClient class represents the client side of a DDE conversation.
Methods of DdeClient are available for connecting to a DDE server, requesting data
items from the server, requesting links to data items (hotlink, warmlink, or
coldlink), registering callbacks for data change and connection termination events,
sending data items to a server, querying server capabilities, and requesting
command execution by the server. A DdeClient can connect to any DDE server
Note: Windows prevents a DDE client from seeing servers provided from the same
program (.exe).
DdeServer class
An instance of DdeServer represents a single connection to a single DDE client
application. Methods of DdeServer are available to send data to a client and to
disconnect from a DDE conversation. The DdeServerManager class described below
is the primary application interface for building DDE programs.
DdeServerManager class
Instances of DdeServerManager are used to manage multiple connections to DDE
client applications. Each connection maintained by a DdeServerManager is
represented by a DdeServer object. There are two primary techniques that
applications can employ in using a DdeServerManager. With the first technique an
application simply registers with the DdeServerManager the callbacks for one or
more of the DDE server events (see Table 41) and directly handles the processing
for those events. For example, an application can provide the code to handle a
hotlink request from a DDE client.
Alternatively, a second technique can be used that relies entirely on internal default
processing of the DdeServerManager and requires little involvement by the server
application. To utilize this approach an application describes to a DdeServerManager
a database of topics, along with the name of each data item within a topic, the
format specification of each data item, and the current value of each data item. The
DdeServerManager then handles all of the interaction with the DDE client. The only
role of the server application is to update the DdeServerManagers database
whenever a data item changes.
DdeServerManager has methods to add and remove DDE event callbacks, add and
remove database items, and update items in the database.
Note: If a callback has not been hooked, default processing occurs. What the
default processor does depends on the event that caused the callback and
items in the default database. If a callback is hooked and the returnValue
instance variable is set to nil, default processing still occurs.
DdeCallbackData class
An instance of DdeCallbackData is passed as a parameter to all DDE event callback
methods. It contains data about the current event and has a returnValue attribute
(set with returnValue:), which is used to return information back to the IBM
Smalltalk DDE subsystem.
Table 41. DdeCallbackData instance variables
Instance variable (class) Description
item (String) Name of the data item
format (String) Format for the data (see Formats of data transferred
between DDE servers and clients on page 362).
data (String or ByteArray) The actual data
application (String) The server name for this DDE conversation
topic (String) The topic name for this DDE conversation
DdeCallbackData has access methods to obtain the server name, the topic name, a
data item (name and possibly its value), and data item format specification. The
availability and significance of the information in a DdeCallbackData object are
event dependent. For a list of callbacks and return values for the
DdeServerManager, see Table 44 on page 363 and Table 46 on page 364. For a list of
callbacks for the DdeClient, see Table 45 on page 363.
By default, data sent from any DDE client to your DdeServerManager (a poke, in
DDE terminology) is rejected. Requests from the client for the server to run
commands are also rejected. However, both of these callbacks can be hooked,
enabling the application to service these requests.
First, create a class called DdeTimeServer that is a subclass of Object. Give it the two
instance variables described in Table 42.
Table 42. Instance variables for DdeTimeServer
Instance variable Description
ddeServerManager The instance of the DdeServerManager class that you are creating.
timerOn A flag indicating whether the timer is on or off. Turning the timer
off is important when the server shuts down.
Now create a class method new to create a new server. Send the initialize method to
the new object in order to set it up.
new
"Answer a new server after initializing it."
|super new initialize
Before executing the new class method, you need to create the initialize method. In
the initialize method, first create the DdeServerManager and name it Timer. Next,
build the default database.
Note: With the default database setup you do not have to hook into any of the
callbacks provided by the DdeServerManager. All you need to do is to update
the items when the timer goes off. Notice that for the format of String, you
pass a Smalltalk String for the data. When the format is Smalltalk Object,
you use the IBM Smalltalk Swapper to convert the Smalltalk object into a
ByteArray. The method flatten to perform this function is specified in
Converting Smalltalk objects to a ByteArray and back on page 372.
initialize
"Private - Create and initialize a DdeServerManager. Set the name
of the server to 'Timer' and create two items in the item database
under the topic of 'Time'. The two items will be 'date' and 'time.'
Support two formats for the items, a string format and a Smalltalk
object. Support the Smalltalk object format by using the IBM
Smalltalk Swapper to turn a Smalltalk object into a ByteArray."
| currentDate currentTime |
ddeServerManager := DdeServerManager name: 'Timer'.
currentTime := Time now.
currentDate := Date today.
ddeServerManager
addItem: 'time'
topic: 'Time'
value: currentTime printString
format: 'String'.
At the end of initialization the timer is turned on by sending the timerOn: message,
which in turn sends the timerSet message. The timer uses the Common Widgets
timer functionality so that when the timer goes off, the message timerProc: is sent
to the application.
timerOn: aBoolean
"Start/stop the timer depending on aBoolean."
timerOn := aBoolean.
self timerSet.
timerOn
"Answer whether the timer is on or off."
|timerOn.
timerSet
"If the timer is on, reset it to go off in one second."
self timerOn ifTrue: [
CwAppContext default
addTimeout: 1000
receiver: self
selector: #timerProc:
clientData: nil
]
Now all initialization has been completed. The main work of the server is now in
the method that is run whenever the timer goes off. When the timer goes off and
the timerProc: method is run, all the items in the default database are updated. As
the items are updated, the DdeServerManager looks for any hot- or warmlinks to the
data and updates the clients that are linked to those items.
timerProc: clientData
"The timer has gone off so save the current date and time and then
update the item database."
| currentDate currentTime |
currentTime := Time now.
currentDate := Date today.
ddeServerManager
updateItem: 'time'
topic: 'Time'
value: currentTime printString
format: 'String'.
ddeServerManager
updateItem: 'date'
topic: 'Time'
value: currentDate printString
format: 'String'.
ddeServerManager
updateItem: 'date'
topic: 'Time'
value: (self flatten: currentDate)
format: 'Smalltalk Object'.
self timerSet.
The final method that you need to implement is the free method. This method
stops the timer and frees up the DdeServerManager.
free
"Close down by stopping the timer and freeing up the
DdeServerManager."
You now have a DDE server that not only provides the current time and date to
any DDE client but also allows access to the Systems Topics that should be
supported by all servers.
In this example server, the time and date are both updated when the timer goes
off. An improvement to the code might be to only update the date (and possibly
the time) if it has changed since the previous update.
If you follow this procedure, the DdeServerManager will not handle any requests
from DDE clients. Methods must be written and callbacks hooked in order to
provide service. This technique of providing DDE server services takes more work
but allows for more flexibility.
Suppose, for example, that a user wants an application to make data available
through DDE but there is so much data, or the data is so large, that it must be
stored on secondary storage. When a DDE client requests data, the server must
read the data from secondary storage and return it to the client. To do this the
application must hook the DdeNrequestCallback callback and write a method to
handle requests for data from a DDE client.
Second, create a class method new to create a new server. Also send the initialize
method to the new object in order to set it up.
new
"Create a new timer server and initialize it."
|super new initialize
Third, create the initialize method. In the initialize method you first create the
DdeServerManager and name it Timer. Next you hook up to the callbacks and turn
the timer on.
initialize
"Private - Create and initialize a DdeServer. Hook all callbacks."
ddeServerManager := DdeServerManager name: 'Timer'.
ddeServerManager
addCallback: DdeNcoldlinkCallback
receiver: self
selector: #coldlink:clientData:callData:
clientData: nil.
ddeServerManager
addCallback: DdeNhotlinkCallback
receiver: self
selector: #hotlink:clientData:callData:
clientData: nil.
ddeServerManager
addCallback: DdeNinitiateCallback
receiver: self
selector: #initiate:clientData:callData:
clientData: nil.
ddeServerManager
addCallback: DdeNrequestCallback
receiver: self
selector: #request:clientData:callData:
clientData: nil.
ddeServerManager
addCallback: DdeNwarmlinkCallback
receiver: self
selector: #warmlink:clientData:callData:
clientData: nil.
ddeServerManager
addCallback: DdeNterminationCallback
At the end of initialization, turn the timer on by sending the timerOn: message,
which in turn sends the timerSet message. These methods are implemented below.
The timer uses the Common Widgets timer functionality so that when the timer
goes off, the message timerProc: is sent to the application.
timerOn: aBoolean
"Start/stop the timer depending on aBoolean."
timerOn := aBoolean.
self timerSet.
timerOn
"Answer whether the timer is on or off."
|timerOn.
timerSet
"If the timer is on, reset it to go off in one second."
self timerOn ifTrue: [
CwAppContext default
addTimeout: 1000
receiver: self
selector: #timerProc:
clientData: nil
]
Implementing server behavior: Next, create the callback methods that implement
the behavior of this server. First you implement the callback for the
DdeNinitiateCallback. The initiate:clientData:callData: method checks that the
application name and the topic to which the client is attempting to connect match
what the server is looking for. If they do match, then the application sends the
notifyClientOfSupportFor: message to the DdeServer and sets the return value of the
DdeCallbackData object to true. To reject the connect attempt, set the return value of
the DdeCallbackData object to false. Notice that you are not attempting to handle an
empty string for the application name or the topic name. The code should be
embellished to handle this possible situation.
initiate: aDdeServer clientData: unused callData: ddeCallbackData
"Initiate callback - check the application name and topic
and set the returnValue to true if they match."
| app top |
app := ddeCallbackData application asLowercase.
top := ddeCallbackData topic asLowercase.
Handling links: The code that follows is for the DdeNhotlinkCallback method. The
method checks the item name and the format. It sets the DdeCallbackData object to
true if the item is supported and false otherwise.
The method for handling the warmlink and coldlink has the same form as the
method for the hotlink. Instead of implementing the code shown below, the
method could test the reason value of the DdeCallbackData object (by way of the
reason message) and print out different messages based on the value that is
returned.
warmlink: ddeServer clientData: unused callData: ddeCallbackData
"Warmlink callback - Check the item name and the format.
Set the returnValue to true if the item is 'time' and
the format is 'String'."
| item format string |
Note: The Excel spreadsheet program sends coldlink messages with an empty
string as the format.
coldlink: ddeServer clientData: unused callData: ddeCallbackData
"Coldlink callback - Check the item name."
| item format string |
Now all initialization has been completed and all the callbacks have been set up.
The main work of the server is now contained in the method that is run whenever
the timer goes off. When the timer goes off and the timerProc: method is run, the
one supported item is updated. As the item is updated the DdeServerManager looks
for any hot- or warmlinks to the data and updates the clients that are linked to
that piece of data.
timerProc: clientData
"The timer has gone off so update the item database."
ddeServerManager
updateItem: 'time'
topic: 'Time'
value: Time now printString
format: 'String'.
self timerSet.
The message: method that has been used throughout the example follows:
message: aString
"Print a message on the Transcript."
The final method that you need to implement is the free method. This method
stops the timer and frees up the DdeServerManager.
free
"Close down by stopping the timer and closing down the DdeServer."
You now have a DDE server that provides the current time in String format.
Application
DdeClient
DdeServer
DdeServer DdeClient
Application
Application
DdeClient
Building a database of all topics and data items lets the DdeServerManager perform
its default processing in servicing requests from DDE clients, without requiring any
intervention from the application. To illustrate, the following code creates a
DdeServerManager and builds a database of topics and items that will be serviced.
| aDdeServerManager |
aDdeServerManager := DdeServerManager name: 'Stocks'.
aDdeServerManager
addItem: 'time'
topic: 'Time'
value: Time now printString
format: 'String';
addItem: 'time'
topic: 'Time'
value: (self flatten: Time now)
format: 'Smalltalk Object';
addItem: 'BTRX'
topic: 'Price'
value: 26.50 printString
format: 'String';
addItem: 'EST'
topic: 'Price'
value: 104.56 printString
format: 'String'
The IBM Smalltalk Swapper makes it straightforward to use DDE to pass Smalltalk
objects back and forth between cooperating Smalltalk applications. The method
flatten: in the above code converts a Smalltalk object into a ByteArray. The Swapper
can also be used on the receiving side of a DDE connection to unflatten the
Smalltalk object. The code for doing this is specified in Converting Smalltalk
objects to a ByteArray and back on page 372.
Initiate event
When a DDE client attempts to connect to a DDE server, the DdeNinitiateCallback
runs on the server application (assuming the DdeNinitiateCallback is hooked). The
DdeCallbackData object passed to the callback contains the topic strings and the
name of the server to which the client is trying to connect. If the application wants
to accept the connect request, then it must send the following message:
ddeServerManager notifyClientOfSupportFor: topicStr
topicStr is the server application topic string. Note that it is possible for the client
to send an empty string for the topic request. In this case, the server application
should send the message notifyClientOfSupportFor: for each topic that it supports. It
is also possible for the DDE client to use an empty string for the server name. This
implies that any server, regardless of name, can respond to the connect request. For
example, a server application might implement the DdeNinitiateCallback callback as
follows:
initiate: aDdeServerManager
clientData: clientData
callData: callData
"If the application and the topic are empty, then notify the
client about both of the topics."
Note: The return value was not used. See Table 46 on page 364.
If DdeServerManager default processing is being used for the initiate request (that is,
the DdeNinitiateCallback is not hooked), then the DdeServerManager checks the
server name and the topic names in the default processing database and, if found,
creates a connection for the requested topic. However, if the DdeServerManager
receives an empty string for the server name or topic name, it responds by sending
notifyClientOfSupportFor: for each topic in the default processing database. Once
connected, a DDE client can request only items associated with the topic specified
in the connect request. A client must disconnect and then reconnect to access items
in a different topic. The following figure shows how a DDE client initiates a
connect request to a DDE server, thereby generating the initiate event.
Server application
DdeServerManager
Server 2.DdeNinitiateCallback
Application
Callback
Code 3.notifyClientOfSupportFor:
1.connect 4.connect
acknowledgement
Client Application
Dde client
| item format |
item := callData item asLowercase.
format := callData format asLowercase.
(item = 'time' and: [format = 'string'])
ifFalse: [callData returnValue: false]
ifTrue: [
Transcript show: 'warmLink ', item, ' ', format.
callData returnValue: true.
]
By setting the return value of the DdeCallbackData object to true, the DDE server
application has indicated that the item/format is supported. In that case, the
DdeServerManager creates a link to that item for the client, and it generates the
necessary events back to the DDE client when the data item is updated. The server
application must set the return value of the DdeCallbackData object to true if the
item/format is supported and to false if the item/format is not supported. A nil
return value in the DdeCallbackData object causes the DdeServerManager to look up
the item in its default database and, if found, link the client to that item.
Coldlink event
When a DDE client unlinks from a data item, the DdeNcoldlinkCallback is run on the
DDE server application (assuming the DdeNcoldlinkCallback is hooked). As with
hot- and warmlinking, the DdeCallbackData object passed to the callback contains
the name of the item to be unlinked, its format, and the current application and
topic names.
If the DDE server application sets the return value of the DdeCallbackData object to
true, then the DdeServerManager checks for an existing link to the data item and, if
one exists, it unlinks the client from that item. In this case, if a link to the data
item does not exist, the DdeServerManager generates an error response back to the
client. On the other hand, a false return value in the DdeCallbackData object has no
effect on the linkage status of the specified data item.
Request event
When a client wants to retrieve data from a server, the DdeNrequestCallback is run
on the server application (assuming the DdeNrequestCallback is hooked). Here the
DdeCallbackData object passed to the callback contains the name of the item, its
format, and the current application and topic names. If the data is available, the
application sets the return value of the DdeCallbackData object to true and sends the
message sendItem:value:format: to the DdeServer object passed as the first parameter
of the callback. For example, to register for the DdeNrequestCallback, a DDE server
application uses:
ddeServerManager
addCallback: DdeNrequestCallback
receiver: self
selector: #request:clientData:callData:
clientData: nil
| item format |
item := ddeCallbackData item asLowercase.
format := ddeCallbackData format asLowercase.
(item = 'time' and: [format = 'string'])
ifFalse: [ddeCallbackData returnValue: false]
ifTrue: [
aDdeServer
sendData: ddeCallbackData item
format: 'String'
with: Time now printString.
ddeCallbackData returnValue: true.
]
If nil is set for the return value of the DdeCallbackData object (or if the
DdeNrequestCallback is not hooked), the DdeServerManager performs its default
processing, which in this case is to check for the item in the default database and if
found, send it back to the client.
Termination event
When a DDE client shuts down or sends a connection termination request to a
server, the DdeNterminationCallback method is run on the server application. The
server application should not try to disconnect at this point because the client
cannot be stopped from terminating the connection. This callback only exists to
notify the server that the connection is terminating. Also, the server application
should not try to free the DdeServer passed as the first parameter of the callback.
The DdeServerManager manages the DdeServer objects for each connection and takes
appropriate action as required.
First, create a class called DdeTimeClient that is a subclass of Object. It will have the
ddeClient instance variable that represents the instance of the DdeClient class that
you will create.
Note: Include the pool dictionary DdeConstants because you are going to hook into
a callback that needs this pool dictionary.
Now, add a class method new to create a new client. Also send the initialize method
to the new object in order to set it up.
new
"Create a new timer client and initialize it."
|super new initialize
Now you create the initialize method. This method creates the DdeClient object and
connects it to the server. Next it hooks up to the hotlink callback so that when the
data at the server changes, the client is notified. Then the application attempts to
hotlink to the time and date items at the server.
initialize
"Initialize the dde client."
ddeClient := DdeClient new.
(ddeClient connect: 'Timer' topic: 'Time') ifFalse: [
|Transcript cr; show: 'Client cannot connect'.
].
ddeClient
addCallback: DdeNdataCallback
receiver: self
selector: #data:clientData:callData:
clientData: nil.
Next, create the hotlink callback method. All callbacks take three parameters: the
DDE object calling the method, the client data passed when the callback was
hooked, and the DdeCallbackData object containing the information about the
callback.
When the callback is run the method unflattens the data in the DdeCallbackData
object from a ByteArray into a Smalltalk object (because this is what we hotlinked
to in the first place). The unflatten: method takes a ByteArray and uses the IBM
Smalltalk Swapper to convert it back into a Smalltalk object. The code for doing
this is specified in Converting Smalltalk objects to a ByteArray and back on
page 372.
After converting the data back to a Smalltalk object, the method then prints the
object in the Transcript window.
data: aDdeClient clientData: clientData callData: callData
"Hotlink callback - unflatten the data and display it."
| data |
data := self unflatten: callData data.
Transcript
cr;
show: data printString.
The final method that you need to implement is the free method. This method frees
up the DdeClient.
free
"Free up the dde client resource."
ddeClient free.
Note: Once a DdeClient connects to a server for a particular topic, it cannot connect
to a different server or a different topic on the same server, unless it first
terminates the existing connection and then reconnects to a new server, or
topic, or both.
Requesting data
A client application can request a specific data item from the server using the
requestData:format: message:
result := ddeClient requestData: item format: format
The format specification tells the server what format the client is interested in. If
the server does not respond with data, the result is nil.
or
ddeClient warmLink: item format: format.
The format specification tells the server what format the client is interested in. The
hotLink:format: or warmLink:format: messages return true to indicate that the server
supports the data item in the specified format. However, if either message returns
false, then the server either does not support the data item or it does not support
the specified format for the data item.
and
warmLink: ddeClient clientData: clientData callData: callData
"The data at the server has changed. Go get the data."
| item format string data|
item := callData item.
format := callData format.
data := ddeClient requestData: item format: format.
string :=
data notNil
ifTrue: ['Data arrived %1; %2']
ifFalse ['Data unavailable %1; %2']
Transcript
cr;
show: (string bindWidth: item with: format).
Coldlinking items
A client application unlinks from an item previously hot- or warmlinked by
sending the coldLink:format: message to a DdeClient:
ddeClient coldLink: anItem format: aString.
This message returns true if the item successfully unlinks. It returns false if the item
cannot be unlinked. An item cannot be unlinked if it was never linked, or if the
item, its format, or both did not exist on the server. After this message is sent, the
client application is no longer notified of updates to that item from the server.
However, it can still use the requestData:format: method on the DdeClient to query
the server for the data item.
Here, true is returned if the server runs the command, and false is returned if it
cannot.
The data to be transferred, with one exception, must be provided to the respective
DDE object as a ByteArray. For example, if a DDE server is serving up bitmap data,
then the server application provides the DdeServerManager a format specification of
Bitmap and a ByteArray object containing the bitmap data. The only exception is
the String format where the data is always provided to the DDE objects as a
Smalltalk String.
The callback argument of this message identifies the particular DDE event that is
to be hooked. The receiver argument identifies the object that will be sent the
callback message, which is specified with the three-parameter message selector in
the selector argument. The clientData argument specifies optional data that is
passed to the callback method when it is run. The names identifying the DDE
events, or callbacks, are defined in the pool dictionary DdeConstants. The following
table identifies the callback names for the DdeServerManager.
The following table identifies the callback names for the DdeClient.
Table 45. DdeClient callbacks
Callback Explanation
DdeNchangeCallback The item at the server has changed. The client must request
the data from the server.
DdeNdataCallback The item at the server has changed and the data is available
from the DdeCallbackData object through the data message.
DdeNterminationCallback The server is terminating the connection with the client.
Thus, to set up a DdeServerManager to handle the DDE run callback, use code like
the following:
aDdeServerManager
addCallback: DdeNexecuteCallback
receiver: self
selector: #execute:clientData:callData:
clientData: nil.
All DDE callbacks are handled in the same manner as Common Widget callbacks.
When a callback runs, its first parameter is the particular DdeServer or DdeClient
object that originated the event; the second parameter is the client data object that
was provided when the callback was registered; and the last parameter is a
DdeCallbackData object that contains the information about the event (that is, any
data associated with the event), the current server name, the current topic, and so
on. From the example above, the callback method that responds to the DDE run
event looks like this:
execute: aDdeServer clientData: clientData callData: callData
"A client has asked to run a command. Print the command in the
transcript and set the return value to true to indicate that the
command was run."
Transcript
cr;
show: ('Execute : %1' bindWith: callData execute).
callData returnValue: true.
Return values
A method responding to a DdeServerManager callback can return information back
to the source of the event through the returnValue attribute of a DdeCallbackData
object. This value can be set through the returnValue: message. A return value of nil
DDE protocols
The following sections describe public protocols for these classes:
v DdeServerManager
v DdeClass
v DdeServer
v DdeClient
Return value:
true Item was removed.
false Item was not in the database and could not be removed.
servers Answers a set of DdeServers that are all the managers current servers (that
is, connections).
testMode
Answers a Boolean indicating the current test mode.
A Windows limitation prevents a DDE client from seeing servers provided
from the same program (.exe).
If you need to see DDE servers created from the same image as the DDE
client, set testMode to true. While testMode is true other clients cannot see
the servers from this program (.exe).
Return value:
true Test mode is on.
false Test mode is off.
testMode:
Sets the current test mode. If the answer is true, it turns the test mode on.
If false, it turns the test mode off.
A Windows limitation prevents a DDE client from seeing servers provided
from the same program (.exe).
Return value:
true Client received the item.
false Client did not receive the item.
Return value:
true The server disconnected the link to the item.
false The server cannot disconnect the link to the item.
connect:topic:
Attempts to connect to a DDE server identified by applicationString with
topic topicString. Only the first server to acknowledge is connected. All
other connections are terminated. Processing of the connections from the
servers happens elsewhere. Answers true if the receiver is connected.
Otherwise, answer false.
applicationString
A String identifying the name of the application to connect to.
topicString
A String identifying the name of the applications topic to be
connected to.
Return value:
true The connection was made.
false The connection cannot be made.
execute:
Asks the server to run a command string on behalf of the receiver.
Answers true if the server ran the command. Otherwise, answers false.
Return value:
true The server runs the command.
false The server cannot run the command.
free Frees any allocated resources. After this message is sent, the receiver is
invalid and cannot be used.
hotLink:format:
Attempts to create a link to item so that when the item is updated by the
server, a callback (DdeNdataCallback) is called, sending the data as part of
the callback.
Answers a Boolean indicating whether the server accepted the request.
item A String identifying the name of the item to hotlink to.
format A String identifying the format that the data should be in when it
comes back from the server.
Return value:
true The item is hotlinked.
false The item cannot be hotlinked.
queryAll
Queries all servers for all topics. Answers a Dictionary of Sets with the
application name as the key and a Set of topic names as the value.
queryServersFor:
Queries all servers for a topic. Answers a Set of applications (instances of
String) that handle the topic.
topicString
A String naming the topic of interest.
queryTopicsFrom:
Queries a server for all topics that it supports. Answers a Set of topics
(instances of String) that are handled by the application.
applicationString
A String identifying the name of the server.
requestData:format:
Requests data from the server.
item A String identifying the name of the item to retrieve.
format A String identifying the format that the data should be in when it
comes back from the server.
Return value:
nil The server could not provide the data.
String The format requested was String and, therefore, the data from the
server is a Smalltalk String.
ByteArray
The format requested was something other than String and
therefore the data from the server is a ByteArray.
Return value:
true The server accepts the data.
false The server rejects the data or a time out has occurred.
warmLink:format:
Attempts to create a link to item so that when the item is updated by the
server, a callback (DdeNdataCallback) is called. The data is not sent as part
of the callback.
item A String identifying the item to disconnect.
format A String identifying the format of the item to be disconnected.
Return value:
true The item is warmlinked.
false Item cannot be warmlinked.
When the ENVY/Image DDE Examples configuration map is loaded, one of the
applications that is loaded is DdeExamples. This application contains a number of
classes that facilitate the testing of the DDE classes. The loading of this application
requires that the ENVY/Swapper configuration map be loaded.
Timeouts
Both DdeClient and DdeServer objects can associate timeout values with their DDE
conversations. Because the IBM Smalltalk DDE model is synchronous, the timeout
values are used to time out responses to DDE requests across the connection. If a
response to a DDE requests timeout, then the value returned from the request
message is either nil or false, depending upon the context of the particular request
message. In other words, a timeout looks like a failure for the request message.
The timeout value can be set with the timeout: message in the following way:
ddeClient timeout: numberOfMilliseconds
or
ddeServer timeout: numberOfMilliseconds
The DdeServerManager also has a timeout: message that sets the timeout value for
any new DdeServer connections that are made.
or
ddeServer disconnect
Free
Any DdeClient or DdeServerManager must be sent the free message when they are
no longer required by their respective applications. This cleans up and releases any
operating system resources that these objects have allocated. For example:
ddeClient free.
or
ddeServerManager free.
Test cases
You can run the following test cases to exercise some of the DDE functions
described in this chapter.
This opens up a window with a menu named DDE. Next start Microsoft Excel
(Excel version 4.0 is used in this example) and fill in two cells with numbers.
Under the Microsoft Excel menu Formula choose Define Name. This option
enables these cells to be named x and y, respectively. Save the spreadsheet with the
name ddesrvr.xls. (The DDE client looks for a server with this name.)
The window in this test case provides a menu for the following:
v Connecting to the spreadsheet
v Hotlinking, warmlinking, and coldlinking to the two items
v Requesting the items from the server
There is also a menu option for bringing up a dialog showing the available servers,
topics, and items.
This opens up a window such that whenever the mouse passes over the window,
the x and y position of the mouse is displayed in the upper left corner of the
window. The name of the server is DdeExample, and there is a topic named
Mouse. Under this topic there are two items xpos and ypos.
As the mouse is moved over the Smalltalk window, the coordinates change in the
spreadsheet.
This opens up two windowsone a DDE client and the other a DDE server. Both
windows are able to interact with each other for the exchange of information
through DDE.
The menu items on the DDE server enable the server to update the item. The
menu items on the DDE client enable the client to connect to the server,
hotlink/warmlink/coldlink to the data item, request data from the server, poke
data to the server, and ask the server to run a command. The client can also bring
up a dialog showing the available servers, topics, and items.
When the server sends the item, it uses the IBM Smalltalk Swapper to flatten the
Smalltalk object into a ByteArray, and the client uses the Swapper to turn the
ByteArray back into a Smalltalk object before displaying it in the Transcript
window.
This creates a server and a client that do not have an interface but are primarily for
use as example code. The server in this case hooks to a timer and updates a time
and date item. The client connects to the server and hotlinks to the time and date.
When the items are updated, the client displays the time/date in the Transcript
window.
The time and date items in this test case are in the Smalltalk Object format. As in
Two windows exchanging data on page 373, the client and server use the
Swapper to convert a ByteArray to/from a Smalltalk object.
This creates a server as in Updating time and date, but only the current time in
the String format is supported. The client from Updating time and date does
not work with this server. Instead, use Microsoft Excel by entering into a cell:
= Timer|Time!time
The current time appears in the cell. It is in a floating point format but can be
changed by choosing Format/Number from the menu.
Platform-specific support
The IBM Smalltalk DDE subsystem was developed as an extension to the DDE
support provided by IBM OS/2 and Microsoft Windows. The following figure
illustrates the layers provided by the IBM Smalltalk DDE subsystem.
Application
IBM Smalltalk
platform interface
classes and methods
IBM Smalltalk
The application programmer can skip the provided IBM Smalltalk DDE layer and
program directly to the operating system support mechanisms in the platform
interface layer.
In the platform interface layer, objects have been created for all C data types for
both IBM OS/2 and Microsoft Windows. These data types include items such as
DDEACK, DDEADVISE, DDEDATA, and HWND for Microsoft Windows, or
DDEINIT, DDESTRUCT, CONVCONTEXT, and HWND for IBM OS/2. These data
objects are named by prefixing the data type name with OS, and applying
IBM Smalltalk adds NLS support classes and extends existing classes to support
platform-specific character sets, collation order, currency, date format, and time
format.
Each locale category controls the behavior of a predefined set of operations and
ensures that the operations under its control behave properly for the locale
associated with the category. For example, the LC_COLLATE category maintains
information pertinent to string sorting, and, if configured for the U.S. English
locale, ensures that strings are sorted according the rules for U.S. English.
Each process (application) maintains its own set of locale category information, and
can configure the locale categories appropriately for the needs of the application.
The POSIX locale model provides a well defined set of locale-sensitive operations
and a mechanism for controlling the behavior of these operations. By using these
locale-sensitive operations, the same internationalized application can support
several locales simply by altering the settings of the locale categories.
Overview of internationalization
A perfect internationalized application contains no code that is dependent on the
end users language, on the characters needed to represent the language, or on any
formats such as time, date, and currency that the user expects to see and interact
with. In order to minimize the cost of localizing an application (adapting it for a
particular locale) the following elements should not be represented explicitly in
application code:
v Displayed message strings, including the following:
Help text
Menu items
Prompts
Labels
Error messages
The following figure shows the relationship between localized messages, locales,
and character sets:
The Common Language Data Types (CLDT) subsystem provides the class Locale to
help determine the current language and territory. These values can be obtained by
evaluating Locale current language and Locale current territory. The language
and territory of a localized message determine what message text should be
displayed.
The IBM Smalltalk Common Widgets subsystem enables you to explicitly specify
the font used by a particular widget. If localized text is to be displayed correctly, it
is critical that you be aware of the fonts (and their coded character sets) that are
used in widgets that display this text.
A coded character set defines a mapping between code points (Characters) and
glyphs. A character set, in conjunction with a locale, also defines the semantic
value of a Character (for example, whether the Character is uppercase, lowercase, a
digit, or so on).
63 >
Code Point (Character) 64 ?
65 65 A Displayed Glyph
66 B
.
.
.
In addition to font metrics such as size (for example, 11 point) and style (for
example, bold, italic) every font maintains a mapping from character values to
graphical representations, called a coded character set. The coded character set
associated with a font determines how an character value is translated into its
graphical representation.
The translation of code points into their visual representation might necessitate
that a single localized message for a particular locale be stored separately for each
supported character set. Consider the font -microsoft-courier new-bold-i-normal-0-
0-96-96-m-0-iso8859-1, whose character set is iso8859-1. In this font, the character
with the graphical representation is associated with the code point 224 (Character
value: 224). In character set ibm-437 the same visual representation is associated
with the code point 133 (Character value: 133). Thus, in order to display the text
la mode, a distinct localized message is required for each character set. It is the
developers responsibility to ensure that localized messages are appropriate for the
font of the widgets in which they are being displayed.
Note: The Common Widgets subsystem allows you to specify the font used by a
particular widget explicitly. To display localized text correctly, you must be
aware of the fonts (and their character sets) that are used in widgets that
display this text.
The following code fragment demonstrates how to query a widget for the character
set used by its font. This code fragment assumes that the variable aWidget refers to
the widget whose font is being queried. Note that if the font being queried does
not conform to the X Logical Font Description (XLFD) naming convention the
CgLogicalFontDescription>>name: message can answer nil.
| aWidget fontName fontDescription |
...
fontName := aWidget fontList fontStruct name.
fontDescription := CgLogicalFontDescription name: fontName.
In IBM Smalltalk, the basic mechanism for maintaining localized messages external
to the application is the NlsMessageCatalog class. Instances of NlsMessageCatalog use
the IBM Smalltalk Swapper as the basis of a platform-independent mechanism for
writing and retrieving messages to and from secondary storage. The
NlsMessageCatalog class provides protocols and a file structure designed specifically
for storing localized messages.
Note: All NlsMessageCatalog protocols that read from a specified path first assume
the path to be absolute. If the first attempt to open this file fails, it is
assumed to be a relative path name located in one of the directories
specified by Locale nlsPath. Therefore, once NlsPath is set, you need only
specify the name of the desired message catalog when using the
NlsMessageCatalog read operations described in the following section.
An instance of class Locale is created and initialized with the currently configured
platform locale information at image startup. This instance is known as the current
locale and can be accessed by sending current to the Locale class. The current locale
cannot be set explicitly, only initialized from operating system values.
Characters
Characters are defined to be unique objects that represent code points. Single-byte
characters are restricted to values in the range 0 to 255. Double-byte characters
have values in the range 0 to 65535. The interpretation of character values is
dependent on a coded character set. Several Character protocols (that is, the
protocols concerned with collation, character classification, and case conversion)
have been refined or extended to be consistent with National Language Support
requirements.
String classes
The classes String and DBString are used to represent sequences of characters in
IBM Smalltalk. DBString represents a sequence of Characters that can take values
between 0 and 65535 (that is, double-byte characters). Similarly, String represents a
sequence of Characters that can take values between 0 and 255 (that is, single-byte
characters). Both String and DBString support efficient character-based accessing
and indexing operations. Common Widgets and Common File System subsystem
Attempting to store a Character with value greater than 255 into a String is an error.
IBM Smalltalk provides the Locale>>isDBLocale method to determine if a locale uses
double-byte characters. The isDBLocale message answers a boolean value indicating
whether or not the receiver locale uses double-byte characters.
A locale that supports double-byte characters should use DBString as its string
class. When sent to an instance of Locale, the preferredStringClass message answers
either String or DBString, whichever class is appropriate for the locale represented
by the receiver. For example, if the current locale is #(japanese japan), then
preferredStringClass answers DBString.
If the information obtained from the operating system identifies the platform
default locale as one of the IBM Smalltalk supported locales (see the section
entitled Locales and platform representation on page 415), then the language and
territory fields of the current locale are set accordingly. If the platform default
locale cannot be identified, the territory and language fields are set to the string
<unknown>. It is possible that the information obtained from the operating
system might only partially identify a locale (that is, either the language or
territory). In such a case the value that cannot be determined is set to the string
<unknown>.
The class LCCType has only one valid instance in the image, and no protocols are
provided to create new instances explicitly. New instances of Locale and LCCollate
are obtained by sending new to the classes. These instances of Locale and LCCollate
can be localized manually using the protocols described in Manual localization
on page 412.
Upon successful initialization of the current locale the relocalize message is sent to
every application and subapplication currently loaded in the image, in prerequisite
order (that is, Kernel first). Applications can use this mechanism to perform any
required locale-specific configuration.
Multiple instances
IBM Smalltalk supports the use of multiple LCMessages, LCMonetary, LCNumeric,
and LCTime objects. Each instance of these classes maintains information for a
particular locale and can be created using the for: protocol. The argument to the for:
message is an array of two strings specifying the language and territory for which
information is to be retrieved.
If the information for the requested locale is not available, the for: message answers
nil. Otherwise, the newly created and initialized object is answered. The following
code fragment queries the operating system for the monetary formatting
information for the territory 'us' and language 'english.' Note that the territory
and language strings must be in lowercase.
LCMonetary for: #('english' 'us')
| stream sep |
NLS-enabled classes
Several base classes in the image make use of locale-dependent information held
by the current locale (Locale current). A summary of these classes follows:
Character (CLDT)
<=, <, >=, >
Collates characters according to the collation ordering for the U.S. English
locale on OS/2 3.0 and Microsoft Windows 3.11, or the collation ordering
for the C locale on X-Motif.
asLowercase, asUppercase
Performs case conversions according to the LCCType object associated with
the current locale (Locale current lcCType).
isAlphaNumeric, isDigit, isLetter, isLowercase, isPunctuation, isUppercase
Performs character classification according to the LCCType object associated
with the current locale (Locale current lcCType).
LCCollate (NLS)
compareCharacter:and:
Collates characters according to the LCCollate object associated with the
current locale (Locale current lcCollate).
compareString:and:
Collates strings according to the LCCollate object associated with the
current locale (Locale current lcCollate).
Date (CLDT)
printOn:
Prints the date in the format specified by the LCCTime object associated
with the current locale (Locale current lcTime).
String (CLDT)
<=, <, >= , >, sameAs:
Collates characters according to the collation ordering for the U.S. English
locale on OS/2 3.0 and Microsoft Windows 3.11, or the collation ordering
for the C locale on X-Motif.
asLowercase, asUppercase
Performs case conversions according to the LCCType object associated with
the current locale (Locale, current lcCType).
Time (CLDT)
printOn:
Prints the time in the format specified by the LCTime object associated with
the current locale (Locale current lcTime).
Number formatting
Currency symbol, spacing, and sign locations
You format monetary values using a combination of the pSepBySpace, pSignPosn,
pCsPrecedes, nSepBySpace, nSignPosn, and nCsPrecedes values. The first three values
are used when formatting positive values, and the last three when formatting
negative values. Together these variables control the relative positions of the
currency symbol and sign indicator. Instances of the LCMonetary class are used to
hold these parameters for a particular locale.
The following table illustrates the use of these parameters in formatting the
non-negative monetary value 1.25. The same formatting rules are also used to
format negative monetary values by changing the positive sign character to a
negative sign character.
The pCsPrecedes value indicates the relative position of the currency symbol in
formatted monetary qualities.
0 The currency symbol follows the monetary quantity.
1 The currency symbol precedes the monetary quantity.
The SepBySpace value indicates if the currency symbol is separated from the
monetary quantity by a space in formatted monetary quantities.
0 No space separates currency symbol from monetary quantity.
1 A space separates currency symbol from monetary quantity.
2 A space separates currency symbol and positive sign if adjacent.
The pSignPosn value specifies the relative position of the positive sign string
formatted monetary quantities.
0 Parentheses enclose both the quantity and the currency symbol.
1 The positive sign follows the quantity and the currency symbol.
2 The positive sign follows the quantity and the currency symbol.
3 The positive sign immediately precedes the currency symbol.
4 The positive sign immediately follows the currency symbol.
Table 47. Numeric formats specified by currency symbol, spacing, and sign location
pSepBySpace
2 1 0
Numeric groupings
A grouping string is used to specify the size of each group of digits in formatted
monetary and numeric quantities. The grouping string consists of a series of
numbers separated by spaces. The first number in the string indicates the size of
the group of digits immediately to the left of the decimal indicator.
The effects of the grouping string on monetary formatting are most easily
visualized by example. The following table illustrates formatting the number
123456789 as a function of the grouping string, using a comma as the separator.
Table 48. Use of the grouping string to format numeric values
Grouping string Resultant formatted numeric quantity
3 123456,789
3 0 123,456,789
3 2 1234,56,789
3 2 127 1234,56,789
3 2 0 12,34,56,789
127 123456789
123456789
Note: The Locale class is initialized automatically at image startup, and can be
reinitialized manually by evaluating Locale initializeCurrentLocale. For
example, when using Windows 3.11, you might want to update the current
locale after changing the settings in the International control panel.
SubApplication class
localize
This message informs currently loaded Application and SubApplication
classes that the current locale has been changed. This message is sent to all
loaded Application and SubApplication classes on image startup, and each
time the current locale is successfully initialized (that is, by evaluating
Locale initializeCurrentLocale).
The original template string 'Sorry, %1, I cannot perform %2!' has been replaced
by a reference to a pool variable called MyAppWarningTemplate1. This method is
now language-independent and can be localized simply by modifying the value to
which the pool variable MyAppWarningTemplate1 is bound. Pool variables are most
easily rebound using the features provided by NlsMessageCatalog.
|Array
with: Message1
with: Message2
with: Message3
with: Message4
Note: Both indexed external messages and external message dictionaries are
associated with a particular locale and character set. Many indexed external
messages and/or a single external message dictionary can be stored within the
same file for a given locale and character set combination.
| catalog |
catalog := NlsMessageCatalog on: 'demo.cat'.
The following example loads messages into an existing pool dictionary using an
NlsMessageCatalog. The example assumes the existence of a pool dictionary called
MyNlsMessages into which all messages can be loaded. You must check the return
values of all NlsMessageCatalog operations to ensure that the operation has
succeeded.
"A code fragment to load messages from an existing message catalog file
called 'demo.cat' for the #('english' 'us') locale and the 'iso8859-1'
character set."
| catalog |
catalog := NlsMessageCatalog on: 'demo.cat'.
When pool variables are referenced directly in source code, the associations
contained in the pool dictionary are directly referenced by instances of
CompiledMethod. As a result, when a pool dictionary is localized, the associations
that contain the localized messages must be directly modified. Assigning a
localized version of the pool dictionary to the global variable that references the
dictionary does not work. The NlsMessageCatalog contains protocols that directly
modify the associations that contain localized messages and ensure consistency of
loaded messages.
The following example shows how to load an external message dictionary without
initializing a pool dictionary. You must check the return values of all
NlsMessageCatalog operations to ensure that the load operation has succeeded.
"A code fragment to load messages from an existing message catalog file
called 'demo.cat' for the #('english' 'us') locale and the 'iso8859-1'
character set."
| catalog |
catalog := NlsMessageCatalog on: 'demo.cat'.
The following example demonstrates how to declare pool keys from a message
catalog file. This example assumes the existence of the pool dictionary
MyNlsMessages in which keys will be declared.
"Declare the pool MyNlsMessages and populate it with keys based on the
messages stored for #('english' 'us' 'iso8859-1')."
| pool catalog |
pool := Smalltalk declarePoolDictionary: #MyNlsMessages.
catalog := NlsMessageCatalog on: 'demo.cat'.
(catalog
declareKeysFor: pool
language: 'english'
territory: 'us'
characterSet: 'iso8859-1') ifFalse: [
|self error: 'Declaration error: ',catalog currentErrorString].
You should place the following variable declaration in the application that
manages the pool dictionary. The following variable declaration method declares a
pool dictionary called MyNlsMessages, which contains two pool variables (Message1
and Message2) whose catalog file is mynls.cat.
_PRAGMA_MyNlsMessages
"%%PRAGMA DECLARE
(name: MyNlsMessages isPool: true pragma: 'NLS mynls')
(pool: myNlsMessages declarations: (
(name: Message1)
(name: Message2)
))"
Note: Deleting an external message dictionary does not immediately decrease the
size of the message catalog file on disk. Deleted external dictionaries are
marked as garbage and their space is reclaimed when the message catalog
file is compressed. See Compression on page 402 for a discussion of the
compression of message catalog files.
| catalog |
catalog := NlsMessageCatalog on: 'demo.cat'.
| catalog |
catalog := NlsMessageCatalog on: 'demo.cat'.
| catalog |
catalog := NlsMessageCatalog on: 'demo.cat'.
(catalog
deleteIndex: 1
language: 'english'
territory: 'us'
characterSet: 'iso8859-1') ifFalse: [
|self error: 'Deletion error: ',catalog currentErrorString].
Note: Deleting an indexed external message does not immediately decrease the
size of the message catalog file on disk. Deleted indexed external messages
are marked as garbage and their space is reclaimed when the message
catalog file is compressed. See Compression on page 402 for a discussion
of the compression of message catalog files.
| catalog |
catalog := NlsMessageCatalog on: 'demo.cat'.
(catalog
deleteLanguage: 'english'
Note: Deleting a locale does not immediately decrease the size of the message
catalog file on disk. Deleted locales are marked as garbage and their space is
reclaimed when the message catalog file is compressed. See Compression
on page 402 for a discussion of the compression of message catalog files.
Deletion is a permanent operation and cannot be undone. Once deleted, all objects
stored for a locale are lost.
Extended locales are intended to be used to identify objects other than localized
messages which are stored in message catalog files. For example, the #($Comment
Comment Comment) extended locale could be used by an application to
maintain a comment about the message catalog file.
The following protocols are provided to determine what language, territory, and
character set combinations are supported by a message catalog file. Each of the
following protocols answers a collection of ordered triples representing the
language, territory, and character sets for which application-defined objects or
localized messages have been stored. The following protocols report errors by
answering nil.
messageIdentifiers
Answers a collection containing language, territory, and character set
triples from which localized messages are stored, excluding objects stored
for extended locales.
hiddenMessageIdentifiers
Answers a collection containing language, territory, and character set
triples which are used to identify extended locales.
allMessageIdentifiers
Answers a collection containing all language, territory, and character set
triples for which messages are stored, including objects stored for extended
locales.
Compatibilities
NlsMessageCatalog supports compatibilities, a mechanism to facilitate the sharing
of translations among locales. Compatibilities can also be used to describe the
default messages to be used in the event that the message catalog file does not
contain messages for the current locale and character set.
If messages for the given locale are not explicitly stored in the catalog,
localeCompatibleWithLanguage:territory:characterSet: uses the compatibility information
to determine the compatible locale. The process starts with the first array (for
example, (japanese-japan-ibm-932 japanese-japan-microsoft-shiftjis) in the previous
example) and proceeds sequentially until the last array (for example,
(english-us-ansi-ascii *-*-*) in the previous example). If an element of an array
matches the given locale the messages of the primary locale (for example,
japanese-japan-ibm-932) are considered to be compatible with the given locale.
Compatibility matching stops after the first match is found.
german-switzerland-iso8859-1
matches
#('german' 'germany' 'iso8859-1')
#('german' '*' 'iso8859-1')
and uses
'german' 'germany' 'iso8859-1'.
matches
#('japanese' 'japan' 'ibm-932')
#('japanese' 'japan' 'microsoft-shiftjis')
The compatibilities from the example suggest that the application has iso8859-1
translations for German, but likely has has not been localized for different
German-speaking territories. In other words, the application likely has a single
German version sold in multiple German-speaking territories. The example above
illustrates the results of the compatibilities matching process as applied to the
compatibilities described in the example showing the structure of
NlsMessageCatalog compatibilities. The locale english-us-ansi-ascii is an abstract
Compression
Deletion of objects from a message catalog file is accomplished by marking objects
as unreachable and does not require the message catalog file to be entirely
rewritten. Furthermore, the structure of NlsMessageCatalog files allocates extra space
to objects stored with the file to allow efficient object modification. For these
reasons a compression phase is necessary to reclaim space used by unreachable
objects and to remove extra space allocated to live objects.
Note: When compressing message catalog files during the development cycle, it is
desirable to use compressMinimal: false option to ensure that updating
message catalog files is as efficient as possible. When all modifications have
been done, you can use the compressMinimal: true option to minimize the
size of the message catalog file.
Error object:
Array
with: name of file being renamed
with: name of rename target
with: CfsError resulting from failed attempt to rename file.
1006 Error string: Error removing secondary working file.
Error object:
Array
with: name of secondary working file
with: CfsError resulting from failed attempt to rename file.
1007 Error string: Invalid attempt to crossload.
Error object: An instance of NlsMessageCatalogHeader.
1010 Error string: File write error.
Error object:
Array
with: Locale name for which message could not be found.
with: Index of message which could not be found.
1015 Error string: No messages stored for locale.
Error object: Locale name for which messages could not be found.
1016 Error string: No message dictionary available.
Error object: Locale name for which messages could not be found.
1017 Error string: Locale not found.
Error object: Locale name for which messages could not be found.
2000 Error string: Error encountered loading object.
Error object: An instance of ObjectLoader.
2001 Error string: Error encountered dumping object.
Error object: An instance of ObjectDumper.
The NlsExternalizationTools application also defines the RCConverter class that assists
in converting localized message dictionaries to Microsoft Windows and OS/2
resource file formats. The RCConverter class is intended primarily for use in a
development image and should most likely be excluded from a runtime image.
RCConverter
Dynamic link libraries (.DLL files) created using the Microsoft and IBM OS/2
resource compilers are supported. The class RCConverter can produce a resource
compiler input file from a pool dictionary, and can re-create the pool dictionary
from the input file and the dynamic link library it produces.
Localized messages and their associated comments follow the series of define
statements. Localized messages are represented as entries in a STRINGTABLE
statement associated with an ordinal value declared in the series of define
statements.
Note: The resource file representation of a string can be quite different from its
Smalltalk appearance. Many non-printing and special characters must be
converted into the \DDD format for use with the resource compiler. When
retrieved from a dynamic link library using an RCConverter or a
PlatformFunction the string is equivalent to the original Smalltalk string.
RCConverter new
convertMessages: (Smalltalk at: #DemoPool)
comments: comments
toResourceFile: 'demo.rc'
This file was created with the following Smalltalk locale settings:
Language: german
Territory: switzerland
Code page/Language driver: deu
*/
#define X 0
#define Z 1
#define Y 2
STRINGTABLE
BEGIN
0, "localized message X" /* comment X */
1, "localized message Z"
2, "localized message Y" /* comment Y */
END
Dynamic link libraries use integers to identify localized messages and IBM
Smalltalk pool dictionaries use string identifiers. You must provide the mapping
between string identifiers to be used in the reconstructed dictionary and the
integer identifier used by the dynamic link library. The indexFrom: method can be
used to reconstruct an index from a resource compiler input file based upon a
RCConverter limitations
Messages must be instances of String or DBString containing Characters whose
values are strictly greater than zero (1 to 65535).
Note: The resource file representation of a single Character can range from 1 to 4
bytes. Characters expanded in this manner are the special or nonprinting
characters whose values are 1 through 31, 34, and 127. For example,
(Character value: 7) has a resource file representation of: \007.
Note: Only keys already in the pool have their values set.
localeCompatibleWithLanguage:territory:characterSet:
Retrieves and examines the receivers compatibilities array and determines
which message identifier best matches the language, territory, and
character set.
If the message catalog file contains messages for the specified language,
territory, and character set, the compatibilities are not examined and an
array containing the specified the language, territory, and character set is
returned. Otherwise the matching process examines each line of the
compatibilities array (whose format is described in the compatibilities
method comment). Within a line of the compatibilities, array patterns are
examined from left to right. If no match is found on a given line, the
search continues to the following lines until a match is found or no more
lines are available. A match occurs if the three elements of the pattern
array match language, territory, and character set.
Answers a three-element array consisting of strings that identify the
language, territory, and character set. Answers nil if an error occurs or if no
compatibility matches the supplied arguments.
messageIdentifiers
Determines which locales are supported by localized messages stored in
the file specified by the receivers path. Answers a collection of the locale
names for which the message catalog file specified by path has mappings.
Answers nil if an error occurs.
objectPlatform
Answers the object platform from which the message catalog file specified
by the receivers path was dumped. Answers a string indicating the object
platform, or nil if an error occurs.
pathName
Answers a string that contains the full path name of the message catalog
file represented by the receiver (nil if the full path has not yet been
computed).
Manual localization
Methods are provided to access and change the state information of LCCollate,
LCMonetary, LCMessages, LCNumeric, LCTime, and Locale objects. In particular, the
following methods are provided to enable you to perform further localization by
manually setting the values of various fields. This can be used to override values
obtained from the platform, or to configure locale-specific values on platforms that
do not provide the information.
Note: Some platforms do not have a one-to-one match with all of the formatting
information maintained by LCCollate, LCCType, LCMonetary, LCMessages,
LCNumeric, LCTime, and Locale. To prevent an error, a universal policy forces
initialization of such variables to U.S. English. A description of values that
cannot be obtained from the platform, and their default values, is presented
in Locales and platform representation on page 415.
LCCollate
collateTable:
Sets the collateTable. The collate table is a ByteArray consisting of 256
positions. The value at each position is the collating weight of a given
LCMessages
dataSeparator:
Sets the data separator string explicitly.
noStr: Sets the localized versions of the string No.
yesStr: Sets the localized versions of the string Yes.
LCMonetary
currencySymbol:
Sets the currency symbol used when printing a formatted monetary
quantity.
fracDigits:
Sets the number of fractional digits that appear in a formatted numeric
quantity.
intCurrSymbol:
Sets the currency symbol used when printing an international monetary
quantity.
intFracDigits:
Sets the number of fractional digits that appear in a formatted monetary
quantity.
monDecimalPoint:
Sets the decimal point used when formatting a monetary quantity.
monGrouping:
Sets the manner in which digits are grouped in a formatted monetary
quantity.
monThousandsSep:
Sets the thousands separator used in a formatted monetary quantity.
nCsPrecedes:
Combined with the following two methods, controls the formatting of
negative monetary quantities.
nSepBySpace:
Combined with the preceding and following methods, controls the
formatting of negative monetary quantities.
nSignPosn:
Combined with the preceding two methods, controls the formatting of
negative monetary quantities.
negativeSign:
Sets the indicator used to indicate a negative quantity in a formatted
monetary quantity.
positiveSign:
Sets the indicator used to indicate a positive quantity in a formatted
monetary quantity.
LCNumeric
decimalPoint:
Sets the decimal point used when formatting a numeric quantity.
grouping:
Sets the manner in which digits are grouped in a formatted numeric
quantity.
thousandsSep:
Sets the thousands separator used in a formatted numeric quantity.
LCTime
abDay: Sets the abbreviated days names.
abMon:
Sets the abbreviated month names.
amPm: Sets the two-element array used to represent the ante-meridian (before
noon) and post-meridian (after noon) indicator strings. Element1 is AM;
Element2 is PM.
day: Sets the full day names.
dFmt: Sets the date format template for the printing of dates.
dtFmt: Sets the date-time format template for the printing of dates and times.
mon: Sets the month names.
tFmt: Sets the time format template for the printing of times.
Locale
countryAbbreviation:
Sets the receivers two-character ISO country abbreviation string.
language:
Sets the descriptive language string for the receivers locale.
territory:
Sets the descriptive territory string for the receivers locale.
The following sections detail the mapping from IBM Smalltalk locale definitions to
platform values and discuss items of interest for each supported platform.
Although you normally do not need to map locale descriptions onto platform
values, this section illustrates the source of some platform limitations.
Note: When platforms do not have a one-to-one match with all of the formatting
information maintained by LCCollate, LCCType, LCMonetary, LCMessages,
LCNumeric, LCTime, and Locale, these methods are initialized with English
U.S. values. Manual localization on page 412 describes methods that you
can use to override values obtained from the platform, or to configure
locale-specific values on platforms that do not provide the information.
When information is unavailable from the platform, U.S. English default values are
provided. The following table shows the values that are not available from the
operating system, along with their default values.
Note: The country control panel setting for monetary leading zero is not
supported and is ignored on OS/2.
Microsoft Windows
As illustrated in the diagram at the beginning of this section, each IBM Smalltalk
locale name maps directly to a pair of language and sublanguage identifier
constants. The following table defines the mappings from IBM Smalltalk locale
definitions to language and sublanguage identifier pairs. The language and
sublanguage constants are defined in the WINNT.H header file that is part of the
Windows NT software development kit.
Table 55. Windows platform values and IBM smalltalk locale names
IBM Smalltalk Locale Name Platform Mapping
Language Territory Language ID Sublanguage ID
arabic arabic
croatian yugoCroatian
czech czechoslovakia LANG_CZECH SUBLANG_DEFAULT
danish denmark LANG_DANISH SUBLANG_DEFAULT
dutch belgium LANG_DUTCH SUBLANG_DUTCH_BELGIAN
dutch netherlands LANG_DUTCH SUBLANG_DUTCH
english australia LANG_ENGLISH SUBLANG_ENGLISH_AUS
english britain LANG_ENGLISH SUBLANG_ENGLISH_UK
english canada LANG_ENGLISH SUBLANG_ENGLISH_CAN
english ireland LANG_ENGLISH SUBLANG_ENGLISH_EIRE
english new zealand LANG_ENGLISH SUBLANG_ENGLISH_nz
english us LANG_ENGLISH SUBLANG_ENGLISH_US
estonian estonia
faeroese faeroeIsl
farsi iran
finnish finland LANG_FINNISH SUBLANG_DEFAULT
french belgium LANG_FRENCH SUBLANG_FRENCH_BELGIAN
french canada LANG_FRENCH SUBLANG_FRENCH_CAN- ADIAN
french france LANG_RENCH SUBLANG_FRENCH
french switzerland LANG_FRENCH SUBLANG_FRENCH_SWISS
german austria LANG_GERMAN SUBLANG_GERMAN_AUS- TRIAN
german germany LANG_GERMAN SUBLANG_GERMAN
german switzerland LANG_GERMAN SUBLANG_GERMAN_SWISS
greek greece LANG_GREEK SUBLANG_DEFAULT
hebrew israel
hindi india
hungarian hungary LANG_HUNGARIAN SUBLANG_DEFAULT
icelandic iceland LANG_ICELANDIC SUBLANG_DEFAULT
An attempt to initialize the current locale for a particular locale will fail unless the
International control panel has the country and language options set appropriately.
For example, on Windows NT, if English U.S. is the desired locale, the
International control panel must have the country set to United States and the
Language set to English (American) before the image is started or the locale is
manually reinitialized. Note that the language must be set to English (American);
that the setting English (International) does not work.
The UNIXProcess class controls other operating system processes from within
Smalltalk. The UNIXProcess class supports both interactive and passive processes.
Interactive processes support stream-based pipes for stdin, stdout, and stderr.
Stream-based pipes enable the developer to control the process (input) and record
the resulting output. For example, UNIX shell scripts can be invoked from within a
Smalltalk application.
UNIX environment
This subsection describes a conceptual model of the UNIX environment.
The UNIXEnvironment class provides access to the shell environment from within
Smalltalk. A subset of the Dictionary protocol is supported by the UNIXEnvironment
class. Through this protocol the environment can be queried and modified.
Method specification
Following are UNIXEnvironment methods and usage examples.
UNIXEnvironment methods
at: Answers the object of the receiver that is associated with an object
equivalent to the specified key. Answers nil if the receiver does not contain
an object equivalent to the key.
at:ifAbsent:
Answers the object of the receiver that is associated with an object
equivalent to the specified key. If an equivalent key is not found, answers
the result of evaluating the specified zero argument block. Returns fail if
the key is not found and if the block is not a zero-argument block.
at:put: Associates the specified key with the specified value, in the receiver.
Answers the value currently associated with aKey (or nil if absent). If the
receiver does not contain the specified key, it creates a new entry in the
receiver for that key.
envAt:
Answers the value at the specified key in the receiver in the form
aKey=aValue. If the key is missing, it generates an error.
envAt:ifAbsent:
Answers the value at the specified key in the receiver in the form
aKey=aValue. If an equivalent key is not found, answers the result of
evaluating the specified zero argument block.
envStringAt:ifAbsent:
Answers the key-value pair in the receiver, indicated by the specified key.
If an equivalent key is not found, answers the result of evaluating the
specified zero argument block.
keys Answers the environment keys in the receiver.
Usage example
This section contains examples using the UNIXEnvironment class. Evaluate each of
the expressions by highlighting them and selecting Display from the Edit menu.
The UNIXEnvironment class retains a copy of the environment that was initially
available. This is useful when the current environment has been changed and
access to the original environment is required.
"Retrieve the user's start up environment."
UNIXEnvironment startUpEnvironment
The keys method answers all of the keys in an instance of UNIXEnvironment. This
method is useful when checking a users configuration.
"Retrieve the names of all environment variables from the user's
start up environment."
UNIXEnvironment startUpEnvironment keys
If you are more familiar with the C language, you might want to use the getenv:
message to access the current environment instead of querying the current
environment and using the at: message. The messages getnev and at: are
functionally equivalent for operations on the current environment.
"Query the Smalltalk AIX process environment with a specific key.
Equivalent to C function."
UNIXEnvironment getenv: 'HOME'
Using the putenv: message is similar to the getenv: message except it is used to set a
value instead of query it. The messages putenv and at:put: are functionally
equivalent for operations on the current environment.
"Set a key-value pair in the Smalltalk UNIX process environment.
Equivalent to C function."
UNIXEnvironment putenv: 'TEST=1234'
UNIX process
This subsection describes a conceptual model of the UNIX process.
The C language process model is used by the UNIXProcess class. Each process is
defined as having an input stream (stdin), an output stream (stdout), and an error
stream (stderr). The streams made available by a UNIXProcess are defined when
they are created.
Through the UNIXProcess class, operating system processes can be controlled from
within Smalltalk.
Three file descriptors, stdin, stdout, and stderr, are available through the C
language. Direct access to these file descriptors is possible for each instance of the
UNIXProcess class. While directly accessing the file descriptors is possible, a
stream-based abstraction is the preferred method of interaction.
Note: Many UNIX processes that have stdin defined will not terminate unless
stdin has been closed. Sending the message close to the UNIXWritePipeStream
representing stdin closes the pipe and allows the process to end if it is
blocked on input.
Method specification
UNIXProcess methods and usage examples are described in the following section.
UNIXProcess methods
closePipe:
Closes the specified pipe. Generates a walkback if the pipe is not valid.
complete
Obsolete protocol retained for compatibility. It should be replaced with
isComplete
exitCode
Answers an integer representing the receivers exitCode. If the process has
not completed, answers nil. This is operating-system dependent.
fillPipe:
Flushes all data in the pipe from the operating system process associated
with the receiver to the Smalltalk UNIXReadPipeStream. Generates a
walkback if the specifed pipe stream is not an instance of
UNIXReadPipeStream or is not a valid UNIXPipeStream.
id Obsolete protocol retained for compatibility. Should be replaced with pid.
isComplete
Answers true if the receiver process has completed, answers false if not. If
the UNIX primitive for completion checking fails, generates a walkback.
kill Kills the receiver process. Try using the SIGTERM signal first. If the
process does not die within 5 seconds, sends the SIGKILL signal. This
method blocks until the process dies. Answers the receiver.
nextFrom:
Answers the next available character from the receivers stdout. If the read
would block, answers nil. On any other error answers a character with a
value of zero.
nextPut:on:
Writes a character to the receivers stdin pipe, specified by on:. Generates a
walkback if the pipe is not valid. Answers the character.
pid Answers the integer representing the process id of the receivers operating
system process. If there is no corresponding operating system process,
answers nil.
run Obsolete protocol retained for compatibility. The
execute:input:output:error:environment: message encompasses this behavior.
stderr Answers a UNIXReadPipeStream representing the receivers stderr pipe, or
nil if the pipe is undefined.
stdin Answers a UNIXWritePipeStream representing the receivers stdin pipe, or
nil if the pipe is undefined.
stdout Answers a UNIXReadPipeStream representing the receivers stdout pipe, or
nil if the pipe is undefined.
waitForCompletion
Ensures the receiver has finished the last operation. Answers the exitCode
of the process. This is a blocking operation. If the stdin pipe has not been
closed, the operation may block indefinitely.
One common use for a UNIXProcess is to invoke shell commands from within
Smalltalk and capture the output.
"Display on the Transcript the result of a shell command."
Transcript show: (UNIXProcess shell: 'ls -al')
To invoke a command and not capture the output, the system message is used.
"Perform a shell command and direct output to the start up terminal"
UNIXProcess system: 'env'
In some situations the shell: and system: messages do not provide sufficient control
over the process. The following example shows the three stages of a UNIXProcess:
creation, the reading and writing of data, and termination.
"Connect user defined streams to the UNIX process stdin, stdout
and stderr."
| process output input result |
Using the stream interface, it is possible to interact with a process. Future input to
the process can be determined by the currently available output or state of the
process.
"It is not necessary to provide all input immediately."
| process output input result |
process := UNIXProcess "Create the process."
execute: 'cat'
input: true
output: true
error: nil
environment: UNIXEnvironment current.
input := process stdin.
output := process stdout.
input
nextPutAll: 'This'; "Perform read/writes of data."
flush.
process isComplete ifTrue: [self error: 'Process should not be complete'].
input
nextPutAll: ' is input sent into standard in';
close.
process waitForCompletion. "Wait for process to terminate."
output contents
The following code is an example method for an application. The method makes
use of the UNIXProcess class to mail a message to a collection of recipients with a
given subject. A method similar to this could be used to interface to the UNIX mail
facilities from within Smalltalk.
process := UNIXProcess
execute: args
input: true
output: true
error: nil
environment: env.
process stdin
nextPutAll: 's ',subject; cr;
nextPutAll: string; cr;
close.
process waitForCompletion.
|process exitCode
In the C language, three file descriptors are made available to processes: stdin,
stdout, and stderr. The stdin file descriptor is used to provide input to the process,
while stdout and stderr are for output and error output respectively. The Smalltalk
interface to these pipe handles is either an instance of UNIXWritePipeStream or
UNIXReadPipeStream.
UNIXReadPipeStream methods
atEnd Answers a Boolean that is true if the receiver cannot access any more
objects, otherwise answers false.
blocking:
Sets a Boolean that indicates whether or not the receiver will block on I/O
operations. Answers the receiver.
close Closes the receivers associated UNIX pipe. If any unread information is
UNIXWritePipeStream methods
atEnd Answers a Boolean that is true if the receiver cannot access any more
objects, otherwise answers false.
blocking:
Sets a Boolean that indicates whether or not the receiver will block on I/O
operations. Answers the receiver.
close Closes the receivers associated UNIX pipe. If any unwritten information
remains in the receiver, it is flushed to the operating system before the
pipe is closed. The operating system pipe is closed, and the receiver is no
longer valid for flush or write operations. Answers the receiver.
contents
Answers the current contents of the receiver as a String.
cr Writes a logical carriage return to the receiver.
First, what is OLE? This is the definition from Inside OLE (Second Edition) by
Kraig Brockschmidt of Microsoft:
OLE is Microsofts entry into the world of object-oriented computing. At the lowest
level, object-based services are provided by C++ classes. These classes are called
interfaces. Interfaces can be defined in any language but are most easily defined in
C++. When one component needs to talk to another, it queries for a well-known
interface. If the query succeeds, an instance of the interface is returned.
Functionality is accessed using the methods of the interface. Access to the private
state of the instance is not allowed.
The problem is further compounded by the fact that when writing an interface,
much of the implementation can be easily and trivially defaulted, yet must be
present for the interface to work. This leads to increased code bulk and complexity.
The fact that there are multiple interfaces that are often tightly coupled leads to
design confusion, which is often resolved by trial and error by the less-experienced
OLE programmer.
OLE classes
The OLE application framework consists of the following classes:
OleMainWindow
OleMainWindow is a subclass of CwMainWindow. It extends the functionality of a
main window to support menu-bar negotiation when an object is activated in
place. You must use an OleMainWindow instead of CwMainWindow in any
application that embeds or links OLE objects or uses OCXs.
When an embedded OLE object is activated in place, the menu bar for the
container becomes a mixture of the containers current menus and the menus
provided by the server. Menu-bar items are divided into these groups:
v File
v Edit
v Container
v Object
v Window
v Help
A container provides the OleMainWindow with the pull-down menus for each of
the groups it is responsible for. For example, a container might specify its in-place
menu-bar groups as follows:
anOleMainWindow
fileMenuGroup: (Array with: fileMenuCascade);
containerMenuGroup: (Array with: optionsMenuCascade with: featuresMenuCascade);
windowMenuGroup: (Array with: windowMenuCascade)
Hence, when an embedded OLE object is activated in place, a merged menu bar is
built and managed by the OleMainWindowfirst using the menus specified by the
container, and then querying the server for its menu groups. In addition, real estate
in the containers OleMainWindow is negotiated for any tool bars and floating
palettes that the server provides for editing the object. When the embedded object
is deactivated, the original menu bar of the OleMainWindow is restored along with
any window areas used by the server for tool bars and so on.
OlePrimitive
OlePrimitive is a subclass of CwPrimitive. It defines common behavior to widgets
that provide sites for OLE embedded, linked, or OCX objects. Applications that use
OlePrimitive widgets must use OleMainWindow in their runtime widget hierarchy
instead of CwMainWindow.
OleClient
The OleClient class is a subclass of OlePrimitive. It allows OLE objects from other
applications to be embedded or linked into an IBM Smalltalk or VisualAge
application. An OleClient contains the OLE object and allows that object to behave
like other Common Widgets.
OleAutomationObject
An OleAutomationObject provides a programming interface to OLE automation
objects exposed by an OLE server. OLE automation objects contain data
(properties) and the functions (methods) used to perform an action with the object.
For example, Microsoft Excel handles all the operations and data associated with a
range of cells in a spreadsheet. With OleAutomationObject, you can do the
following:
v Create an instance of OleAutomationObject that references an OLE automation
server object through a name, through a server file, or from an existing
embedded or linked OleClient object
v Set or retrieve properties of an OLE automation server object
v Invoke methods on an OLE automation server object.
OleFile
An OleFile enables you to transparently persist embedded or linked OLE objects
into files. An OleFile works with an OleClient or OleControl widget to perform
persistence operations on their contained OLE objects. Items stored into an OleFile
are identified by a unique name that you provide when the object is saved.
Using instances of OleFile, you also can associate arbitrary data attributes with a
stored OLE object. These attributes are referenced for storage and retrieval by
using the same name the object was stored under. You can use this feature, for
example, to store various attributes of the OleClient widget containing the OLE
object, such as, its position, width, and height.
There is an additional facility in OleFile to store and retrieve a set of data attributes
that may be global to all the OLE objects stored within the file.
In addition, a container may save the data associated with an embedded OLE
object directly into one of its own files.
The steps to create an OLE-enabled application that will contain OLE objects are:
1. As part of the initialization of the user-interface widget hierarchy, create an
OleMainWindow and enable its menu groups to support in-place activation of
any descendent OleClient widgets.
2. Implement methods to create OleClient widgets and create the OLE objects that
will appear within their borders.
3. Implement methods to enable copying, cutting, and pasting OLE objects to and
from the clipboard.
4. Create and manage a menu containing the OLE objects verbs.
5. Invoke the Links dialog to allow a user to manage the links of any linked OLE
objects.
6. Save and restore embedded or linked OLE objects to and from files.
Here, the hostName resource is set even though it is often only used by
non-in-place-activated OLE servers. In the case of non-in-place activation, the
hostName may be displayed on the label of an OLE servers Close or Exit menu
items. For example, given the hostName above, the label of an activated OLE
servers normal termination menu item might read Exit to Container instead of
simply Exit, as it would when the server application is explicitly launched. The
menu bar of an OleMainWindow is partitioned into groups to facilitate the
negotiation for menu-bar space during in-place activation. The groups are:
v File
v Edit
v Object
v Container
v Window
v Help
For example, the createMenuBar: method of an OLE container might have five
pull-down menus defined for its normal operation and specify three of them for
use when any contained OLE object is activated in place:
createMenuBar: mainWindow
"Private - Create and answer the menu bar for the receiver."
| menu children |
menu := mainWindow
createSimpleMenuBar: 'bar'
argBlock: [:w |
w
buttons: #('File' 'Edit' 'Container' 'Window' 'Help');
buttonMnemonics: #($F $E $C $W $H)].
During in-place activation, all other negotiations for window real estate (for
example, tool-bar and status-bar space) between an OLE container and an OLE
server are transparently handled by the OleMainWindow class. The menu group
cannot be changed while an OLE client is active in place.
OleClient widgets are created and managed like other primitive widgets. However,
there are several techniques for creating and connecting an embedded or linked
OLE object to an OleClient widget.
Other techniques create and connect the OLE object after the widget has been
created but before it is managed in the widget tree. These include creating an OLE
object from the following:
v An invocation of the Insert Object dialog
v The clipboard through the Paste Special dialog
v The clipboard without going through the Paste Special dialog
v A drag and drop operation that uses the framework described in Chapter 9.
Drag and Drop on page 263.
Creating by name
To create an OLE object and connect it with an OleClient widget, the OLE objects
server must be registered in the system registry, which is often called the
registration database. One of the attributes of the registration is the
programmatic identifier, or ProgId, which identifies the registered OLE object. The
ProgId of a registered OLE object is the name used for the clientName resource of
an OleClient.
Since OLE clients are created as child widgets of non-primitive widgets (that is,
any CwComposite), there is an additional widget-creation convenience method
implemented in CwWidget called createOleClient:argBlock:. Hence, knowing an OLE
objects ProgId, an OLE client is simply created and connected to the OLE object.
Here, the clientType resource is set to XmEMBEDDED and specifically requests that
the OLE object named Word.Document.6 be embedded into the new OleClient.
This code fragment creates an OleClient widget that contains an empty embedded
Microsoft Word document. Double-clicking anywhere on the OleClient widget
causes Microsoft Word to be activated in place and ready for editing the embedded
document.
The OleClient is created to be linked to the same document file by changing the
clientType resource to XmLINKED. For example, an OleClient is created to be linked
to a Microsoft Word document file using:
| client |
client := composite
createOleClient: 'client'
argBlock: [:w |
w
x: 50;
y: 50;
width: 200;
height: 200;
clientType: XmLINKED;
sourcePath: 'c:\docs\sample.doc'].
client manageChild.
The embedded and linked cases look the same to the user. However, in the linked
case, when the user double-clicks on the OleClient, Microsoft Word opens in its
own windows for editing the linked document; it does not activate in place in the
container. Furthermore, since the OleClient is linked to the file, other users can
separately edit the file and modify its contents. However, an OleClient can be
explicitly updated to reflect third-party changes in the linked OLE objects content
by sending the OleClient the updateFromLink message.
Applications typically offer access to this dialog through one of their menu items.
In fact, the OLE user-interface guidelines suggest offering an Insert Object item on
either an Edit or Insert pull-down menu. Before the Insert Object dialog can be
invoked, an OleClient widget must already exist and must not be connected to an
OLE object. For example, an Insert Object menu item callback might look like:
menuEditInsertObject
"Private - Process the Edit menu Insert item."
| client result |
In this example, the OleClient is first created with an initial position and size. The
OleClient widget is then managed. It is not, however, made visible to you using
mappedWhenManged: false until the embedded or linked OLE object is created and
connected to the widget. The parameter to promptInsertObject: specifies what kind
of OLE object can be inserted in the widget. If XmANY is specified, for example,
you can select the Create from File option in the dialog and then optionally select
Link.
For each resource value designated below, you can take the designated action with
the inserted OLE object:
XmANY
Can be embedded or linked
XmLINKED
Must be linked
XmEMBEDDED
Must be embedded
If promptInsertObject: answers true, an OLE object was created and connected to the
OleClient widget, which can then be mapped with the message client
mappedWhenManaged: true.
If you insert an object from a file, whether it is linked or not, the widget displays
the contents of that OLE object. However, if the OLE object is inserted as
embedded, there is nothing to display, and the OLE object is immediately activated
to let you enter some initial content.
As with the Insert Object dialog, applications typically offer access to the Paste
Special dialog through one of their menu items. Again, the OLE user interface
guidelines suggest offering a Paste Special item on the Edit pull-down menu.
Before the Paste Special dialog can be invoked, an OleClient widget must already
exist and must not be connected to an OLE object. For example, a Paste Special
menu item callback might look like:
menuEditPasteSpecial
"Private - Process the Edit menu Paste Special item."
| client |
client := composite
createOleClient: 'client'
argBlock: [:w |
w
x: 20;
y: 40;
width: 200;
height: 200].
client
mappedWhenManaged: false;
manageChild.
(client promptPasteSpecial: XmANY) == true
ifFalse: [|client destroyWidget].
client mappedWhenManaged: true
In this example as in the one before it, the OleClient is first created with an initial
position and size. The OleClient widget is then managed but not made visible to
you until the OLE object is created and connected to the widget. The parameter to
promptPasteSpecial: takes the same values as promptInsertObject:. If XmANY is
specified, for example, you can select the Paste Link option in the dialog to create
a linked OLE object.
If promptPasteSpecial: answers true, an OLE object was created and connected to the
OleClient widget. The widget can then be mapped and the OLE object made
visible.
OLE servers render a variety of formats on the clipboard when one of their objects
is copied. With the Paste Special dialog, you can select for embedding one of the
This format is OLE object and its rendering is obtained from an OleClient through
its instance method oleObjectDescriptor. This special OLE object clipboard rendering
is copied to the clipboard using the standard Common Graphics clipboard
operations on a CgDisplay object:
v clipboardStartCopy:clipLabel:itemIdReturn:
v clipboardCopy:itemId:formatName:buffer:privateId:
v clipboardEndCopy:itemId:
As an example, consider the following callback method for the Copy menu item:
menuEditCopy
"Private - Process the Edit menu Copy item."
| widget display window itemIdHolder |
(widget := self shell focusWidget) isOleClient
ifFalse: [|self].
display := self shell display.
window := self shell window.
itemIdHolder := ReturnParameter new.
(display
clipboardStartCopy: window
clipLabel: self shell title
itemIdReturn: itemIdHolder) = ClipboardSuccess
ifFalse: [|false].
display
clipboardCopy: window
itemId: itemIdHolder value
formatName: 'Ole Object'
buffer: widget oleObjectDescriptor
privateId: 0.
|(display clipboardEndCopy: window itemId: itemIdHolder value) = ClipboardSuccess
This example first finds the widget that currently has focus in the shells widget
tree. It then queries that widget, using isOleClient, to determine whether it is an
OleClient widget. If it is, the remainder of the method uses standard clipboard
protocol on the shells CgDisplay to copy the special OLE clipboard format
rendering.
For example, the Paste menu item of the Edit menu might look like:
menuEditPaste
"Private - Process the Edit menu Paste item."
| display window bufferHolder result |
display := self shell display.
window := self shell window.
display clipboardStartRetrieve: window.
result := (display
clipboardRetrieve: window
formatName: 'Ole Object'
bufferReturn: (bufferHolder := ReturnParameter new)
privateIdReturn: ReturnParameter null) == ClipboardSuccess.
display clipboardEndRetrieve: window.
result ifFalse: [|self].
|self paste: bufferHolder value
Here, the OleClient does all the work of creating and connecting the OLE object
through its instance method pasteFromOleObjectDescriptor:.
Note: The OLE object format and its rendering are also used for drag and drop
operations on OleClient widgets. The OleClient method oleObjectDescriptor is
used to obtain the source OLE object format rendering for a drag operation,
and the OleClient method pasteFromOleObjectDescriptor: is used to create and
connect an OLE object described in a OLE object rendering obtained from a
drop operation.
The doVerb: method can also take as a parameter a constant representing the most
common verbs that most OLE objects support. For example, the edit verb could
also be invoked using anOleClient doVerb: XmVERBPRIMARY.
The standard parameter values for the OLE doVerb: include the following:
XmVERBPRIMARY
Open the OLE object for editing
XmVERBSHOW
Show the OLE object
XmVERBOPEN
Open the OLE object for editing in a separate window
XmVERBHIDE
Hide the OLE object
XmVERBUIACTIVATE
Activate the UI for the OLE object
XmVERBINPLACEACTIVATE
Open the OLE object for editing in place
XmVERBDISCARDUNDOSTATE
Close the OLE object and discard the undo state
XmVERBPROPERTIES
Request the OLE object properties dialog
The OLE user interface guidelines suggest that a container provide access to an
objects verbs from a cascade menu off its Edit menu. The container must
dynamically change the contents of the cascade menu depending on which
OleClient widget has the current focus. The guidelines also suggest that the label of
the cascade menu change to reflect the name of the object that the verbs apply to.
To help build a dynamic verb menu, the OleClient class provides these instance
methods:
These methods provide sufficient information to build the dynamic verb menus of
a container application. As an example, suppose the Edit menu of an application is
created as a simple pull-down menu using the following code:
createEditMenu: menuBar
"Private - Create and answer the Edit menu for the receiver."
| types |
types := OrderedCollection new.
types
add: XmPUSHBUTTON;
add: XmPUSHBUTTON;
add: XmPUSHBUTTON;
add: XmPUSHBUTTON;
add: XmPUSHBUTTON;
add: XmSEPARATOR;
add: XmPUSHBUTTON;
add: XmPUSHBUTTON;
add: XmCASCADEBUTTON.
editMenu := menuBar
createSimplePulldownMenu: 'edit'
argBlock: [ :w |
w
buttons: #('Cut' 'Copy' 'Paste' 'Paste Special...'
'Delete Object' '' 'Insert Object...' 'Links...' 'Object');
buttonType: types;
buttonMnemonics: #();
postFromButton: 1].
editMenu
addCallback: XmNsimpleCallback
receiver: [:w :clientData :callData |
| selectors |
selectors := #(menuEditCut menuEditCopy menuEditPaste
menuEditPasteSpecial menuEditDeleteObject menuEditInsertObject
menuEditLinks menuEditObject).
self perform: (selectors at: clientData + 1)]
selector: #value:value:value:
clientData: nil.
editMenu
addCallback: XmNmapCallback
receiver: [:w :clientData :callData |
| widget buttons |
buttons := editMenu children.
widget := self shell focusWidget.
(buttons at: 1) sensitive: widget isOleClient. "Cut"
...
(buttons at: 9)
sensitive: self enableVerbs. "Object"]
selector: #value:value:value:
clientData: nil.
|editMenu
In this example, the last item on the menu is a cascade button that is used to
attach the verb menu to. The verb menu must be disabled if no OleClient widget
has focus. Hence, the XmNmapCallback callback calls the enableVerbs method to
build the verb menu if appropriate. The enableVerbs method first checks for the
existence of an OleClient with focus and, if it finds one, proceeds to build the verb
menu:
Notice in this example that one additional menu item, Convert, is attached to the
bottom of the verb menu. This item provides access to the standard OLE Convert
dialog, which allows you to change the display aspect of the OLE object from its
The promptConvertObject method answers true if the display aspect of the OLE
object changed. If it did, the OleClient widgets display was already refreshed to
reflect the new aspect.
Upon return from promptEditLinks, all requested link edits have been completed,
and any link changes that effect the OLE objects display have also been made.
Note: If the Links dialog is used to break a link to an OLE object, the linked object
automatically reverts to a static embedding. The display of the OLE object is
still visible on its OleClient widget, but it can no longer be activated.
A file that supports the storage of both native data and OLE object data is
sometimes called a compound file. The OleFile class supports this kind of
compound-document storage requirement.
An OleFile works seamlessly with an OleClient in the storage and retrieval of its
contained OLE objects data. An OleFile can store multiple OLE clients and, hence,
it requires that each one be given a unique name when put into the file. OLE files
also allow arbitrary application data to be stored with the OleClient in the file.
These attributes of application data are stored in the OleFile under the same name
as the OLE object they are associated with. Finally, an OleFile permits one global,
unnamed set of application data attributes to be stored in the file.
Once an OleFile is created, all OleClient widgets in the application are obtained
from the OleMainWindow through its oleWidgets method. This example assumes that
when each OleClient widget is created, it is given a unique name which it uses as
the unique OleFile storage name.
Note: An OleFile must be closed with its close method before any OLE object data
is committed to the file.
In this example, the OleClient widgets are created with default size and position.
The following section describes saving additional attributes of the widgets in the
file.
Saving and restoring data attributes: An arbitrary, named data set can also be
stored in an OleFile. This permits, for example, data attributes of an OleClient to be
stored in the file under the same name as the OleClient. The data set to be saved
must be contained in a ByteArray, which can be filled in a variety of ways,
including by using the Swapper tool. The OleFile instance method
saveAttributes:named: saves a ByteArray of data to the file. For example, the
saveWidgetTo: method above can be enhanced to save each widgets position and
size:
saveWidgetsTo: filepath
"Save the applications data to an OleFile specified by the filepath String."
| file widgets data |
fileName := filepath.
(file := OleFile create: fileName) == nil
ifTrue: [|false].
widgets := self mainWindow oleWidgets.
data := ByteArray new: 16.
widgets do: [ :widget |
data
int32At: 0 put: widget x;
int32At: 4 put: widget y;
int32At: 8 put: widget width;
int32At: 12 put: widget height;
file
saveWidget: widget named: widget name;
saveAttributes: data named: widget name].
file close
Similarly, named data attributes can be restored from an OleFile through the use of
the restoreAttributesNamed: method, which answers a new ByteArray containing the
saved data. Hence, the example method restoreWidgetsFrom: can be rewritten as:
Additional methods of OleFile can be used to save and restore unnamed data sets
to a file. The methods, which also work with byte arrays, are saveAttributes: and
restoreAttributes.
Note: For numeric data, it is often simpler to use ByteArray accessors (such as,
int32At: and int32At:put:) rather than the Swapper tool to fill byte arrays for
use with an OleFile.
OLE automation provides the mechanisms for servers to group their function into
objects, called automation objects, which can then be exposed and used in a
standard way. Automation objects provide access to their function through
methods and access to their attributes or variables through properties (equivalent
to accessor methods for instance variables in Smalltalk). An OLE automation server
exposes its objects for use by registering them in the system registry. If an
automation server provides multiple objects, each one must be registered
separately.
Many, but not all, OLE document servers also provide OLE automation objects. An
OleClient, through its automationObject instance method, may answer the
automation object of its embedded or linked OLE object. For example, if a
Microsoft Word object is embedded in a container, its Word Basic automation object
is obtained using:
| word wordDispatch wordApplication wordBasic |
word := mainWindow
createOleClient: 'word'
argBlock: [:w |
w
clientType: XmEMBEDDED;
clientName: 'Word.Document'].
word isConnected ifFalse: [word destroyWidget. |nil].
word manageChild.
wordDispatch := word automationObject.
wordApplication := wordDispatch propertyAt: 'Application'.
wordBasic := wordApplication propertyAt: 'WordBasic'.
Both methods take as a first parameter the name (or DISPID) of the method to
invoke, and as a second parameter an array of parameters to be passed to the
method.
Note: If an error is indicated with a nil return from one of the OLE automation
objects invoke methods, a String describing the error may be obtained by
sending the lastErrorString message to the OleAutomationObject.
These methods function like the invoke methods with respect to parameters and
return values.
An OCX:
v Has an OLE automation element with properties that are retrieved and
modified, and methods that are invoked
v Obtains information from its container to initialize its own state; that is, it
gathers the ambient properties from its container
v Notifies its container, through events, when a user action causes its state to
change or a function to be performed.
Note: Some OCXs may not have a user interface component; it is not a
requirement.
v Sets properties and makes action requests, through OLE automation, of an OCX
v Provides an ambient-properties handler (callback) that responds to
ambient-property requests from an OCX
v Provides a set of event handlers (callbacks) that respond to event notifications
from an OCX.
Since OCXs do not use menus, no menu groups need be registered with the
OleMainWindow (unless of course, there are OleClient widgets used for embedded
or linked OLE objects in the same application).
Creating OCXs
An OleControl widget provides an OLE container the place to put OCXs. OLE
controls are primitive widgets and, as such, they conform to all standard
widget-geometry management. An OleControl widget contains the connections to
an OCX and transparently manages all low-level OLE interaction with that object.
In addition, an OleControl widget provides an OLE automation interface for its
contained OCX to be used to set and get OCX properties and to invoke OCX
methods.
OleControl widgets are created and managed like other primitive widgets. Unlike
OleClient widgets, however, the only way to create and connect an OCX to an
OleControl widget is through its registered name. This is the same as the creation
of OLE clients using the clientName resource, which is based on the OCXs
registered programmatic identifier, or ProgId.
To support its OLE automation aspects, the OleControl class has the same
automation protocols as the OleAutomationObject class, namely:
v propertyAt:
v propertyAt:put:
v invoke:withArguments:
v invoke:withArguments:returnType:
Since complex OCXs are often composed of many distinct objects, some of these
methods may themselves return OLE automation objects.
The properties that an OCX defines are characteristic of the OCX. These
characteristics can include the font used, the background color, or the caption.
Since so many of these characteristics are common to so many OCXs, OLE has
Note: The font property DISPID DispidFont is not supported. Instead, use the
fontList: instance method of OleControl to set a font into an OCX.
An OCX container must register a method for each OCX event it will handle. The
name of the event, the selector of the event-handler method, and the object that
receives the notification are registered with an OleControl through its
addOleEventHandler:receiver:selector: instance method. For example, the track bar
OCX sends an event called Change whenever the user moves its slider. The
handler for this event is registered using:
trackBar
addOleEventHandler: 'Change'
receiver: self selector: #updateValue.
Many OCX events pass one or more parameters to their container along with the
event notification. Therefore, the event-handler method selectors must
accommodate the required number of parameters. For example, an OCXs
mouse-down event passes four parameters to its containers event handler:
v The mouse button that was pressed
v The state of the shift key
v The mouse pointer X location
v The mouse pointer Y location.
Hence, a handler for this event is registered for the track bar OCX using:
trackBar
addOleEventHandler: 'MouseDown'
receiver: self selector: #eventMouseDown:shift:x:y:.
There is a set of predefined OLE event names and DISPIDs for the base mouse and
keyboard events that are used by most OCXs. These predefined events overlap the
Common Widgets event masks that are inherited by OleControl. As a convenience,
OleControl registers handlers for the corresponding OLE predefined events and
automatically reroutes them through the Common Widgets event-handler
mechanism. For example, the mouse-down OLE event handler registered in the
previous example can be rewritten to use a Common Widgets event handler:
trackBar
addEventHandler: ButtonPressMask
receiver: self
selector: #value:clientData:callData:
clientData: nil.
To support the use of licensed OCXs, the OleControl class has the class method
getLicenseKey:, which is used at development time to obtain the license key. This
method takes as a parameter the ProgId of the OCX and answers a string
representing the license key. The license key is then cached in the Smalltalk image
(for example, in a class variable of an Extended Widgets wrapper of the OCX) and
used in the subsequent creation of the OleControl widget.
For example, if the track bar OCX is licensed, its license key is obtained using the
following code:
getLicenseKey
"Answer a string that can be used to license the receiver,
or nil if either the OCX does not require licensing
or the OCX does support licensing but cannot be propagated."
LicenseKey := OleControl getLicenseKey: 'TRACKBAR.TrackbarCtrl.1'
Note: The licenseKey: method must be used in the create block of the OleControl.
Only set resource methods are shown for each class. Get methods use the same
format without the colon. Create/Set/Get (CSG) access designation and default
value are listed for each resource and call. For more on resource designation, see
Resources on page 141.
Call list set methods are also shown, even though the callbacks are usually added
using addCallback:receiver:selector:clientData:, and are not using a callback list set
method. These methods end in Callback:.
CallData type is
CwDrawingCallbackData.
focusCallback: C OrderedCollection new Specifies the list of callbacks that is
called after a widget has accepted input
focus.
CallData type is
CwDrawingCallbackData.
losingFocusCallback: C OrderedCollection new Specifies the list of callbacks that is
called after widget loses input focus.
CallData type is
CwDrawingCallbackData.
navigationType: CSG XmTABGROUP Specifies the value of the
XmNnavigationType resource.
strictClipping: CG self parent strictClipping Some platforms show a dramatic speed
improvement in the time to draw
windows containing many widgets if
they are not required to ensure certain
clipping constraints.
traversalOn: CSG true Specifies if traversal is activated for this
widget.
CwComposite also inherits resource and callback methods from CwBasicWidget and CwWidget. Consult the resource
tables of these classes for more information.
CallData type is
CwAnyCallbackData.
defaultButtonType: CSG XmDIALOGOKBUTTON Specifies the default PushButton.
dialogType: CSG XmDIALOGMESSAGE Specifies the type of MessageBox
dialog, which determines the
default message symbol.
messageAlignment: CSG XmALIGNMENTBEGINNING Controls the alignment of the
message Label.
messageString: CSG nil Specifies the string to be used as
the message.
symbolIcon: CSG dynamic Specifies the icon to be used as the
message symbol.
symbolPixmap: CSG dynamic Specifies the pixmap label to be
used as the message symbol.
CwMessageBox also inherits resource and callback methods from CwCompositeBox, CwBulletinBoard, CwComposite,
CwBasicWidget, and CwWidget. Consult the resource tables of these classes for more information.
CallData type is
CWTextVerifyCallbackData.
navigationType: CSG XmTABGROUP Overrides default value from CsPrimitive.
rows: CSG 1 Specifies the initial height of the text
window measured in character
heights.
scrollHorizontal: CG true Adds a ScrollBar that enables the user
to scroll horizontally through text
when set to true.
scrollVertical: CSG true Adds a ScrollBar that enables the user
to scroll vertically through text when
set to true.
tabSpacing: CSG 8 Indicates the tab stop spacing.
topCharacter: CSG 0 Displays the position of text at the top
of the window.
value: CSG Specifies the displayed text String.
valueChangedCallback: C OrderedCollection new Specifies the list of callbacks that is
called after text is deleted from or
inserted into Text.
CallData type is
CwToggleButtonCallbackData.
CwToggleButton also inherits resource and callback methods from CwLabel, CwPrimitive, CwBasicWidget, and
CwWidget. Consult the resource tables of these classes for more information.
CallData type is
CwConfirmationCallbackData.
CwWMShell also inherits resource and callback methods from CwShell, CwBasicWidget, and CwWidget. Consult the
resource tables of these classes for more information.
Callback list set methods are shown, even though callbacks are usually added
using addCallback:receiver:selector:clientData:, and not using a callback list set
method. These methods end in Callback:.
The tables below identify the platform constraints of the Common Graphics
subsystem. Blank cells indicate that the corresponding item is fully supported for
the indicated platform
Table 115. Constraints on cursors (CgCursor)
Item DOS/Windows OS/2 PM X/MOTIF
Color cursors Not supported Not supported
The tables below identify the platform constraints of the Common Widgets
subsystem. Blank cells indicate that the corresponding item is fully supported for
the indicated platform.
Table 120. General constraints
Item DOS/Windows OS/2 PM X/MOTIF
Border width The borderWidth resource The borderWidth resource
can only be 0 or 1 pixels. can only be 0 or 1 pixels.
Background and Only solid colors are
foreground color supported. Colors are not
dithered. Limited to the 20
system colors.
Table 122. Constraints on button and label widgets. (CwLabel, CwPushButton, CwToggleButton, CwCascadeButton,
CwDrawnButton)
Item DOS/Windows OS/2 PM X/MOTIF
Alignment The alignment resource is The alignment resource is
ignored for CwPushButton ignored for CwPushButton
and CwToggleButton. The and CwToggleButton. The
label is placed by the label is placed by the
operating system. operating system.
Margins The following resources The following resources
affect only the total width affect only the total width
and height of the widget, and height of the widget,
not the positioning of the not the positioning of the
label or pixmap inside the label or pixmap inside the
widget: marginBottom, widget: marginBottom,
marginHeight, marginLeft, marginHeight, marginLeft,
marginRight, marginTop, marginRight, marginTop,
marginWidth. marginWidth.
Table 126. Constraints on menus and menu bar. (CwRowColumn with rowColumnType of XmMENUBAR,
XmMENUPULLDOWN, or XmMENUPOPUP)
Item DOS/Windows OS/2 PM X/MOTIF
Types of child widgets Only CwLabel, Only CwLabel,
CwToggleButton, CwToggleButton,
CwSeparator, or CwSeparator, or
CwCascadeButton can be CwCascadeButton can be
added. added.
Help callback Not supported Not supported
Background color Not supported Not supported
Tab traversal and focus Not supported Not supported
control (setInputFocus:,
interceptEvents:, grabPointer:,
ungrabPointer:,
navigationType, traverseOn)
Geometry requests Geometry requests are Geometry requests are
ignored, and the initial ignored, and the initial
geometry values are geometry values are
undefined. undefined.
Stacking order Stacking order requests Stacking order requests
(bringToFront) do nothing. (bringToFront) do nothing.
Event handlers Can be hooked but do Can be hooked but do
nothing. nothing.
Updating widgets The updateWidget method The updateWidget method
does nothing. does nothing.
Ignored resources The following resources are The following resources are
ignored: adjustLast, ignored: adjustLast,
borderWidth, entryAlignment, borderWidth, entryAlignment,
entryBorder, marginHeight, entryBorder, marginHeight,
marginWidth, numColumns, marginWidth, numColumns,
orientation, packing, spacing. orientation, packing, spacing.
Table 133. Constraints on top-level shell and dialog shell. (CwTopLevelShell, CwDialogShell)
Item DOS/Windows OS/2 PM X/MOTIF
Window titles Centered Left Centered
Index 527
EwPushButtonTool 260 files (continued) GCJoinStyle 86
EwScrollable locking 68 GCLineStyle 86
methods 502 managing 55 GCLineWidth 85, 86
EwSeparatorTool 260 name length 57 GCSubwindowMode 86
EwSimpleGroupTool 262 names 57 GCTileStipXOrigin 86
EwSlider opening 64 GCTileStipYOrigin 86
methods 502 opening using share modes 71 geometry management 139, 161
EwSourceAdapter path 55 glyph cursors 104
methods 513 platform-specific calls 76 graphic constants 83
EwSpinButton portable names 54 graphics contexts 82
methods 505 properties 74 attributes of 86
EwTableColumn reading using low-level changing 89
methods 507 operations 66 constraints 515
EwTableList reading using streams 24, 61 convenience methods 88
methods 508 renaming 56 copying 88
EwTableTree rewind 67 creating 85
methods 510 seeking 66 freeing 89
EwTargetAdapter share modes 70 GCFont values 102
methods 513 size 67 methods for 84
EwToggleButtonTool 260 supported lock types 69 retrieving values 85
EwWINNotebook supported share modes 70 using 84
methods 511 with streams 67 valuemasks 85
ExAll 25 writing using streams 61 with other drawables 89
ExceptionalEvent 25, 26 Float 19 greying out widgets 178
collections 28 flowed icon lists 240
exceptions focus 158
completion-block 29
creating 25
fonts 93
as cursors 103
H
handling
examples 30 assigning to graphics context 101
client requests 351
handling 25, 26 drawing with 100
links 349
signaling 26 freeing 102
hierarchy, class 135
Extended class 39 from graphics context 102
hotlink events 355
Extended list widgets 234 loading 97
hotlinking to items 360
extended widgets 201, 233 overview and process 94
callbacks 203, 491 pattern matching 95
class hierarchy 233 querying 95
examples 203 scalable 97 I
initialization 202 used by widgets 211 IBM Smalltalk
overview 2 wildcard characters 95 architecture 3
resources 202, 491 form widgets 167 base subsystems 1
using 203, 206, 211 circular constraint errors 168 design intent 2
External language interface 288 formats overview 1
data platform support 4
DDE servers and clients 362 icon area widget 241
F Fraction 19
freeing
icon lists 237
icon trees 248
file formats 126
DDE clients or servers 372 icons 124, 126
file streams
fonts 102 constraints 515
classes 61
graphics contexts 89 creating 124
closing 61
pixmaps 107 drawing 125
line delimiters 63
summary 133 for application windows 159
locking 68
in buttons 176
of bytes 63
loading from a file 128
of characters 63
opening 61 G loading from ByteArray 130
loading from DLL 125
using access modes and flags 67 garbage collection 326
loading into a file 129
with file descriptors 67 GC 82
loading into ByteArray 130
writing 24 GCArcMode 86
OS 126
files GCBackground 86
IdentityDictionary 8, 10
changing offset 66 GCCapStyle 86
image file formats 126, 131
closing 65 GCClipMask 86
PCX 131
copying 56 GCClipXOrigin 86
PM BMP 132
deleting 56 GCClipYOrigin 86
TIFF 132
directory path case 57 GCDashOffset 86
Windows BMP 132
error handling 71 GCFillRule 86
images
existence 73, 74 GCFillStyle 86
copying from a drawable 123
file streams 61 GCFont 86
creating 121
flush 67 GCFunction 85, 86
Index 529
methods 19, 39, 99, 486 (continued) methods 19, 39, 99, 486 (continued) methods 19, 39, 99, 486 (continued)
basicAt: 10 comment: 37 createManagedWidget:parent:-
basicAt:put: 12 compile:inClass: 41 argBlock: 203
basicMultiAllInstances 40 compile:inClass:ifFail: 41 createMessageBox:argBlock: 191
basicNew 34 compile:inClass:warningLevel:- createMessageDialog:argBlock: 191,
basicNew: 34 onWarning:ifFail: 41 196
basicSize 7 compiledMethodAt: 35 createPixmap:height:depth: 107, 119
bearing 100 compiledMethodAt:ifAbsent: 35 createPixmapCursor:foreground-
become: 7 compiler 34 Color:backgroundColor:x:y: 105
between:and: 14, 21 compression 132 createPixmapCursor:x:y: 105
bindWith: 13 compression: 132 createPixmapFromBitmapData:-
bindWith:with: 13 configuredSubsystems 42 width:height:fg:bg:depth: 107
bindWith:with:with: 13 conform: 11 createPopupShell:
bindWith:with:with:with: 13 connectToSuper 38 -parent:argBlock: 149, 158
bindWithArguments: 13 contains: 22 createPrimaryWidget:parent:-
bitAnd: 18 containsPoint: 22 argBlock: 202
bitAt: 18 contents 23, 24, 62, 63, 72 createPromptDialog:argBlock: 197
bitInvert 18 context 40 createPushButton:argBlock: 172
bitOr: 18 Converting createQuestionDialog:argBlock: 196
bitShift: 18 asDecimal 20 createRadioBox:argBlock: 174
bitXor: 18 asFloat 20 createRowColumn:argBlock: 169
black 113 asFraction 20 createScrolledList:argBlock: 149, 185,
blackPixel 83, 119 asInteger 20 188
blue 113 copy 6 createScrolledText:argBlock: 162
bottom 22 copyArea:gc:srcX:srcY:width:- createScrolledWindow:argBlock: 160
bottom: 22 height:destX:destY: 108, 120 createSelectionBox:argBlock: 193
bottomCenter 22 copyFrom:to: 10, 23 createSelectionDialog:argBlock: 193,
bottomLeft 22 copyGC:dest: 84, 88 197
bottomLeft: 22 copyPlane:gc:srcX:srcY:width:- height:- createSimpleMenuBar:argBlock: 179
bottomRight 22 destX:destY:plane: 111 createSimplePopupMenu:
bottomRight: 22 copyReplaceAll:with: 10 -argBlock: 179
broadcast: 6 copyReplaceFrom:to:with: 10 createSimplePulldownMenu:
broadcast:with: 6 copyReplaceFrom:to:withObject: 10 -argBlock: 179
button 156 copyReplacing:withObject: 10 createText:argBlock: 162
buttonType: 72, 198 copyright 43 createWarningDialog:argBlock: 196
call 292 copyWith: 10 createWidget:parent:argBlock: 203
callingConvention 291 copyWithout: 10 createWidgetSystem 202
callingConvention:address:- corner 22 createWorkingDialog:argBlock: 196
parameterTypes:returnType: 292 corner: 21, 22 Creating instances 20
callingConvention:function:library:- cos 18 critical: 51
parameterTypes:returnType: 292 count 155 currentError 127, 129
callWith: 292 cr 24, 63 currentErrorString 127, 129
callWith:with: 292 createApplicationShell:argBlock: 149, data 121
callWith:with:...:with: 292 158 dateAndTimeNow 16, 17
canUnderstand: 36 createBitmapFromData:width:- dayIndex 16
ceiling 18 height: 105 dayName 16
center 22 createBitmapFromData:with:- dayOfMonth 16
changed 6 height: 109 dayOfYear 16
changed: 6 createBulletinBoardDialog:- daysFromBaseDay 16
changeGC:values: 84, 89 argBlock: 197 daysInMonth 16
character 156 createComboBox:argBlock: 189 daysInMonth:forYear: 16
chars: 101 createDrawingArea:argBlock: 164 daysInYear 16
chars:delta:font: 101 createErrorDialog:argBlock: 196 daysInYear: 16
chdir: 55 createFontCursor: 106 daysLeftInMonth 16
class 6 createForm:argBlock: 167 daysLeftInYear 16
classPool 37 createFormDialog:argBlock: 197 defaultButtonType: 199
classVarNames 37 createGC:values: 84, 85, 86 defaultChar 99
clearBit: 18 createGlyphCursor:sourceChar:- defaultHandler 25
close 23, 62, 65, 72, 293 maskChar: 104 defaultScreen 81
closedir 61 createGlyphCursor:sourceChar:- defineCursor: 103
collect: 11 maskChar:foregroundColor:- definitionString 37
colorCube: 122 backgroundColor: 104 degreesToRadians 17
colorEncode 132 createInformationDialog:- deleteAllSelectors: 36
colors: 116 argBlock: 196 deleteSelector: 36
colorScheme 132 createLabel:argBlock: 172 deleteSelector:ifAbsent: 36
colorsUsed 132 createList:argBlock: 185 delta: 101
comment 37 createMainWindow:argBlock: 161 denominator 19
Index 531
methods 19, 39, 99, 486 (continued) methods 19, 39, 99, 486 (continued) methods 19, 39, 99, 486 (continued)
isFloat 6 mask:keysym: 171 open:oflag:share: 68, 71
isInteger 6 match: 12 opendir:pattern:mode: 58
isKindOf: 6 max: 14, 21 openEmpty: 61, 71
isLetter 16 maxBounds 99 or: 7
isLowercase 16 merge: 22 origin 22
isMemberOf: 6 message 40, 72 origin: 22
isMetaclass 6 messageLoop 226 origin:corner: 22
isNil 6, 24 messageString: 72, 198, 200 origin:extent: 22
isPointers 35 methodClass 42 pageNumber 279
isPrimitive 42 methodClass: 42 palette 121
isReg 74 methodDictionary 35 paletteInfo 131
isSBString 6 methodsDo: 35 parameterTypes 291
isSeparator 16 millisecondClockValue 17 parent 25
isSpecial 74 millisecondsPerDay 17 parseColor: 114
isString 6 millisecondsToRun: 17 pathSeparator 55
isSymbol 6 min: 14, 21 pathSeparatorString 55
isUppercase 16 minBounds 99 peek: 24
isUserPrimitive 42 minutes 17 peekFor: 24
isVariable 35 mkdir: 55 pelsPerMeter 132
isVowel 16 monthIndex 16 pelsPerMeter: 132
jobAttributes 279 monthName 16 perChar 99
key 14 moreGeneralThan: 18 perCharAtRow: 99
key: 14 moveBy: 22 perCharNumColumns 99
key:value: 14 moveTo: 22 perCharNumRows 99
keyAtValue: 11 MultiAllInstances 40 perform: 7
keyAtValue:ifAbsent: 11 multiBecome: 12 perform:with: 7
keys 11 Multiple Instance Accessing 40 perform:with:with: 7
keysAndValuesDo: 11 mwmDecorations 159 perform:with:with:with: 7
keysDo: 11 name 34, 99, 291 perform:withArguments: 7
keysym 156 nameOfDay: 16 physicalName 293
labelString: 171 nameOfMonth: 16 pi 19
last 10, 55 nearestColor: 117, 120 platformErrno 76
lbearing 100 nearestPixelValue: 117, 120 platformErrorCategory 76
lcm: 19 negated 17, 21 platformErrorLocation 76
left 22 negative 17 platformRecommendedAction 76
left: 22 new 10, 51 point 156
left:right:top:bottom: 22 new: 10 pointRoot 156
leftCenter 22 newChild 25 position 24
lessGeneralThan: 18 newDay:month:year: 16 position: 24
library 291 newDay:monthIndex:year: 16 positive 17
lineDelimiter 23, 63 newDay:year: 16 predictor 132
lineDelimiter: 23, 63 newProcess 45, 46, 49 primaryInstance 38
lineWidth: 85 newProcessWith: 45, 49 primaryInstance: 38
listFonts: 97 next 24, 63 primaryWidget 202
listFonts:maxnames: 95 next: 24 primitiveFailed 7
ln 18 next:put: 24 printOn: 6, 72, 138
loadFont: 98 nextLine 24, 63 printOn:base: 19
loadFromByteObjects:- nextMatchFor: 24 printOn:showDigits:Pad: 20
offsetsIntoByteObjects: 130 nextPut: 24, 63 printString 6, 72
loadFromFile: 127, 128 nextPutAll: 24, 62, 63 printStringRadix: 19
loadFromFileHandle:atOffset: 127, noMask: 18 printStringRadix:padTo: 19
128 normal 21 printStringRadix:showRadix: 19
loadQueryFont: 98, 211 not 7 priority 46, 49
lock:start:len: 69 notEmpty 12 priority: 46, 47, 49
log: 18 notNil 6, 24 prompt 72, 198, 199, 200
logicalName 293 now 17 prompterStyle: 198
logicalName: 293 nullTerminated 12 putDeviceIndependentImage:-
lookupColor: 114, 116 numChars 99 image:srcRect:destRect: 122
lowIOPriority 46, 50 numerator 19 putPixel:y:pixelValue: 121
lseek:whence: 66 numerator:denominator: 19 putPixels:y:width:pixels:
manageChild 139 occurencesOf: 12 -startIndex: 122
mapFontCursor:platform- odd 18 queryBestIconSize 125
Cursor: 106 on: 24, 67, 76 queryFont 102
mapLogicalName:toPhysical- on:from:to: 24 queueInterrupt: 49
Name: 293 open: 61, 71 quo: 17
mapWidget 139 open:oflag: 64, 66, 71 radiansToDegrees 17
Index 533
methods 19, 39, 99, 486 (continued) methods 19, 39, 99, 486 (continued) OLE 437 (continued)
translateBy: 22 whichMethodsSend: 36 ambient properties 462
transpose 21 whichMethodsSendAll: 36 automation 457
trimBlanks 13 whileFalse: 25, 32 building containers 442
trimSeparators 13 whileTrue: 25, 31 classes
truncate 24 white 113 OleAutomationObject 442
truncated 18, 21, 22 whitePixel 119 OleClient 441
truncatedGrid: 21 width 22, 100, 109, 121, 155, 279 OleControl 442
truncateTo: 18, 21 width: 22 OleFile 442
type 155 width:height:depth:palette: 121 OleMainWindow 440
undefineCursor 103 window 81, 155, 164, 279 OlePrimitive 441
unload:intoByteObjects:offsetsInto- with: 10, 24 clipboard operations 449
ByteObjects: 130 with:do: 11 creating
unload:intoFile: 129, 130 with:from:to: 24 automation objects 458
unload:intoFileHandle:atOffset: 129 with:with: 10 clients 444
unloadFont 102, 133 with:with:with: 10 main windows 443
unlock:start:len: 69 with:with:with:with: 10 object verb menus 451
unmapWidget 139 withAllSubclasses 34 custom control (OCX) 460
untilMilliseconds: 50 withAllSubclassesBreadthFirstDo: 35 licensed OCXs 467
update: 6 withAllSubclassesDepthFirstDo: 35 Links dialog 454
upTo: 24, 63 withAllSubclassesDo: 35 retrieving clients 456
upToAll: 24, 63, 64 withAllSuperclasses 34 saving clients 455
upToEnd 24 withAllSuperclassesDo: 35 wrapping OCXs 466
userBackgroundPriority 46, 50 write:startingAt:nbyte: 66 OrderedCollection 8, 10, 13
userInterruptPriority 46, 50 writeBitmapFile:width:height:- origin
userSchedulingPriority 46, 50 xHot:yHot: 110 for drawing 81
value 14, 48, 111 x 21, 155, 156, 279 of graphic text 100
value: 14, 15, 48 x: 21 of Point 21
value:onReturnDo: 48 x:y: 21 OSBaseType 309, 311, 315
value:value: 48 xor: 7 OSF/Motif 135
value:value:onReturnDo: 48 xRes 132 input event processing 225
value:value:value: 48 xRoot 156 widget resource compatibility 143
value:value:value:onReturnDo: 48 y 21, 155, 156, 279 OSImmediate 309
valueOnReturnDo: 48 y: 21 OSObject 308
values 11 year 16 OSObjectPointer 309, 311
valueWithArguments: 48 yourself 7 OSStructure 309, 311, 315
valueWithArguments:onReturnDo: 48 yRes 132 OSVariableStructure 309, 315
variableByteSubclass:- yRoot 156 other languages, interfacing to 285
classInstanceVariable- modal dialog 191
Names:classVariableNames:pool- modifyVerifyCallback 163
Dictionaries: 39
variableByteSubclass:-
mouse events 154 P
pages
classVariableNames:-
callbacks 250
poolDictionaries: 39
variableByteSubclass:classInstance-
N creating 250
name and type, Volume 57 producing 278
VariableNames:classVariableNames:-
name length palettes 114, 118
poolDictionaries: 39
file 57 default 115
variableSubclass:-
volumes 58 direct 120
classInstanceVariableNames:-
National Language Support 377 indexed 116
instanceVariableNames:-
defining managed pool retrieving 118
classVariableNames:-
dictionaries 396 selecting 118
poolDictionaries: 39
overview 377 parent 135
variableSubclass:instanceVariable-
NLS 377 paths 55
Names:classVariableNames:-
notebooks 250 pattern matching 95
poolDictionaries: 39
callbacks 250 pie slices 93
version 131
PM 251 drawing 93
vmType 43
WIN 253 pixmaps
vRes 131
Number 17 as cursors 103, 105
wait 47, 48, 50, 51
copying 108
wake 227
creating 107
when:do: 26, 29
when:do:when:do:, etc. 26 O creating bitmaps 109
drawing in color 119
whenExceptionDo: 29 Object class 5, 6
freeing 107
whichClassIncludesSelector: 34 Object Linking and Embedding 437
geometry 108
whichMethodsReferenceInstVar- ObjectPointer 316
overview 106
Name: 36 OCX 460
reading files 111
whichMethodsReferenceLiteral: 36 OLE 437
writing files 110
Index 535
UNIXEnvironment (continued)
methods 426
W widgets (continued)
parent 135
UNIXProcess 427 warmlink events 355 parent-child relationship 138
methods 428 warmlinking to items 360 pop-up menus 183
usage example 430 warnings 174 primitive 136
UNIXReadPipeStream 431 widgets progress bars 253
UNIXWritePipeStream 434 application-drawn button 177 pull-down menus 180
user interface argBlock 143 push-button 172
background request 229 button 171 radio-button 174
concepts 137 callbacks 469 realizing 139
process model 225, 231 check-box 175 resources 141, 469
user primitives child 135 row-column 169
accessing objects 323 class hierarchy 135 scrolled-window 160
classes available during 327 colors 213 selection-box 193
converting C values to Smalltalk combo-box 189 setting resources 142
objects 322 composite 136 shell 137
converting Smalltalk objects to C convenience methods 148, 150 simple menus 179
values 322 creating 138, 148 sliders 254
definition 285 creation methods 148, 150 spin buttons 256
dummy environment 328 definition 135 split windows 257
error codes 336 destroying 140 steps to display 138
external shared library 319 dialogs 190 table lists 241
functions available in 319 drawing area 164 text 162
miscellaneous functions 326 drawing in color 118 toggle-button 175
object allocation 320 drawn list 236 tool bars 259
protecting objects from garbage event handling 146 top-level shell 158
collection 326 extended list 234 tree 138
samples 328 flowsed icon lists 240 trees 247
sending messages 320 fonts used by 211 icons 248
syntax for 318 form 167 table 249
testing objects 321 functions 144 wildcard characters 95
using functions from other geometry management 161 in file names 58
places 328 getting resources 142 WIN notebook widget 253
user primitives, error codes 336 greying out button 178 window
using icon area 241 decorations 159
licensed OCXs 467 icon lists 237 manager 158
OCXs 460 iconic-button 176 work proc 230
printer shell 276 layout 167 WriteStream 22, 23
using OLE automation 457 limitations 270
list 185
main-window 161
managing 139, 141 X
V mapping 139, 140 X logical font description 95
valuemasks 85 menus 178 X11 bitmap file format 110
Virtual Machine API 285 message-box 191 Xlib
volumes notebooks 250 Common Graphics compatibility
file name lengths 58 PM 251 with 79, 112
obtaining 57 WIN 253 Common Widgets compatibility
Volume name and type 57 overview of concepts 137 with 135
voting and cursors 267 conversion to Smalltalk 79, 135
predefined cursors 104