-*- Mode: Text, Fill -*- This file is meant to be a preliminary description of the Torch toolkit for the Igor development environment. It is incomplete in some areas and subject to change. Introduction to the Torch Toolkit ----------------------------------------------------------------- The programming environment which we have committed to construct is built around a graphical user interface. In order to support the sophisticated and highly interactive programming tools which we desire, the underlying interface toolkit must provide a high degree of flexibility and the ability to extend and embed existing interface elements. In addition to these fundamental goals, we believe that the toolkit should be easily transportable to new platforms and that it should preserve native look-and-feel as much as possible. The issue of defining the precise nature and extent of the interface toolkit is rather difficult, since we envision new interface elements being grown seamlessly out of simpler ones. There can be no clear distinction between the toolkit and its clients because applications create their interfaces through direct extension of primitive facilities. Any distinction between what is and is not in the toolkit is in some sense arbitrary, but none the less useful. We take a minimalist approach in defining the contents of the toolkit. It contains: - a small amount of platform-specific code to mesh with the native windowing system. - basic facilities for constructing, composing, and extending interface elements. - the most basic interface elements such as buttons, menus, and simple layout managers. - uniform model for handling events and associating actions with user input. This system fits the term "framework" just as much as it adheres to the traditional concept of a "toolkit". Surrounding this core functionality are layers of interface elements, which become increasingly more complex and more abstract. General Toolkit Model ----------------------------------------------------------------- Any particular graphical interface must be rendered on some particular DISPLAY. A display corresponds to a particular console running a particular windowing system. The display is an abstraction of the physical hardware used in interface presentation and interaction. A display may be composed of multiple screens, but has only one of any particular input device (typically one mouse and one keyboard). For every platform there must be a means for naming a display. This name is used only to locate physical hardware devices at initialization; consequently, there are not necessarily any 'display' data structures present in a running system. The first step in creating a graphical interface is to create an object known as a WORLD on some specified display. A world encapsulates information about the global state of the interface, and it provides a graft into the native windowing system. All graphical operations are performed through the associated display, and all graphical objects are bound to this world. For instance, an image can only be rendered on the display associated with the world in which it was created. The programmer has the option of creating new worlds; for instance, you might have the Igor world and the Tetris world running at the same time. A world differs from a traditional application in some important ways. Currently, the typical model of an application is: menu bar, tool box, dialogs, work area. An environment built around an object-oriented language is naturally object-oriented itself, and worlds reflect this. They offer a much more free-form approach to interface construction. Within the world, some set of objects are actively displayed, and the user can potentially interact with these objects. He may edit them, remove their presentations, or call up new presentations. All of these presentation mechanisms are defined in an object-oriented manner, so it is a relatively simple task to add new behaviors to the system. Within an established world, the tasks of the toolkit can be broadly categorized into two areas: provide a uniform interface to diverse windowing systems and to provide facilities for constructing user interface elements. The interface to the native substrate is provided primarily through graphical objects called PANES and RESOURCES. Interface elements are created using objects called VIEWS. Resources ----------------------------------------------------------------- Resources are graphical objects which can be shared by various elements of an active world. They provide a standard interface to system-level resources such as palettes, fonts, bitmaps, and icons. Note: The term "resource" is used in the same sense as Macintosh and Win32 resources; its usage is not related to X11 resource databases and should not be confused with them. Resources can either be defined dynamically, for instance with a call to CREATE-FONT, or they may be loaded from resource files. Loading resources from external files is the preferred method of operation because: - It allows resources to be easily updated and shared. - It permits easy substitution of resources in other languages. ... Insert stuff on resource management here NOTE: Resources will almost certainly require finalization, as most graphics systems require the programmer to explicitly relinquish resources. For instance, Win32 keeps reference counts in resources so it can deallocate resources when no one wants them anymore. ... drawing resources ... graphic contexts Panes ----------------------------------------------------------------- Panes are graphical objects which form the basis of the display and interaction mechanisms. They are similar to X windows in their semantics and in their ubiquity. Panes present a uniform interface to various native windowing systems. A pane has certain basic characteristics: - it corresponds to a particular screen region. - it has its own local coordinate system within that region. - all drawing operations are clipped to that region. - it is a site for receiving events. - it is a target for drawing operations. Panes are strictly an internal abstraction which the typical programmer will never see. Panes encapsulate most of the platform specific code, and export a standard interface to higher levels of the toolkit. In particular, pane facilities are used by views, which are discussed below. In general, there are two types of panes: and . Both are subclasses of the abstract class, and support virtually identical methods. Thus the fact that there are two distinct kinds of panes is often hidden from higher levels of the system. The class is meant for the construction of views which are implemented entirely in Dylan; there is no need to worry about interfacing with foreign code. The class is meant for interfacing with native controls such as buttons. A native pane will presumably have foreign code for operations such as redraw. Creation of panes is a two phase process. First, the programmer creates a pane with MAKE. This will allocate the pane structure and attach it to a specified view; however, it will not have any physical existence on the screen. In order to summon a pane into visual reality, you must call REALIZE. The realization method will create a native window for the pane and map it onto the appropriate display. IMPORTANT: Before realizing a pane, you must have already realized its parent. The following is a list of methods supported on the class : make ( :view v) - Creates a Dylan-based pane bound to the given view. make ( :view v :widget-class class) - Creates a native pane of the specified class and attaches it to the given view. NOTE: This is somewhat X specific. world ((p )) - Returns the world in which the given pane is defined. view ((p )) - Returns the view to which the given pane is attached. window ((p )) - Returns a handle for the native window attached to this pane. destroy ((p )) - Destroys the given pane. realize ((p )) - Creates a native window to represent this pane and maps it onto the display device. resize ((p ) (s )) - Resizes the given pane according to the given shape. You may leave slots set to #f to designate no change. However, you MUST define both coordinates of a pair (ie. both x and y and/or both width and height). Drawing Operations ----------------------------------------------------------------- Panes have a dual purpose: receive events and draw graphics, but so far we have only really described basics of creating views. This section deals with the primitives available for drawing graphic images, and event handling is discussed in a later section. From a basic perspective, the user is provided with methods for drawing specific simple geometric objects such as points, lines, boxes, and arcs. For very simple graphical applications, this is sufficient. However, a graphics programmer with any level of sophistication will often find himself needing tools for managing color, drawing style, font style, and a host of other rendering preferences. Some graphic systems, such as X, provide a monolithic approach to specifying drawing preferences (the Graphics Context in the case of X). We find this approach to be potentially confusing and awkward; it forces the program to carry around packages which contain completely unrelated preferences. For instance, when drawing a line there is no need to specify a font style. In contrast, the Torch toolkit attempts to decouple unrelated graphic preferences while providing greater structuring for those that are related. There are three general objects used in most drawing functions: BRUSHES, INKS, and PENS. An ink is either a color, a stipple pattern, or a bitmap. A brush is used for fill operations, and a pen is used for drawing operations. A pen might have characteristics such as ink, dash pattern, and width. These objects are considered to be resources, as they can be shared within their respective worlds. The other class of preferences, such as cap style, join style, fill style, etc. are passed as keyword arguments to the functions which use such preferences. ... Flesh out this stuff with explicit discussion of slots, methods. Right now, I don't want to commit to anything specific until I've had a chance to play around with implementation. An example method definition might be: draw-line ((p ) pen x0 y0 x1 y1 #key cap-style) draw-lines ((p ) pen vertices #key cap-style join-style) Views ----------------------------------------------------------------- While panes provide an internal interface to the native graphics system, VIEWS provide the exported mechanism for constructing graphical applications. The toolkit defines and exports a small group of basic view classes (all derived from the class ). Graphical interfaces are constructed by: - Defining new subclasses of existing view classes. - Composing various view classes together on the screen. Providing facilities to support these two mechanisms is one of the key functions of the toolkit. Definition of new subclasses is facilitated by the object-oriented nature of the Dylan language, and visual composition of views is supported by the layout management protocol (described later). In accordance with the profoundly object-oriented nature of the Dylan language and our vision of a programming environment centered around the interaction with objects, the general philosophy of the Torch toolkit runs along the lines of other object-oriented systems, such as Smalltalk. Smalltalk divided the functionality of the user interface between three kinds of objects: models, views, and controllers. Broadly speaking, models provided the data to be viewed, views controlled the display of the model, and controllers handled input from the user. Because we feel that input and output behavior should be tightly coupled, we have merged the functionality of controllers into views, and in our terminology the model is referred to as the "data object" or simply the object. The goal of the user interface is to provide a visual representation for objects and to potentially allow the user to interact with these objects. Since all data in a Dylan system is packaged in objects, anything can potentially be displayed. Views provide the user with interactive visual representations of data objects, and panes provide the physical manifestation of the view. There is a direct correspondance between views and panes: every view has a pane and every pane is attached to a view. However, there may be multiple views attached to a single object. The division of labor between panes, views, and objects is central to the design of the toolkit. Using it, we can achieve platform independence as well as the capacity to easily present stylistically different views of the same object. Views interact with their associated objects through standard method calls. These are discussed below in the section on the "View/Object Protocol". As an example, consider a class which models arbitrary undirected graphs. We might create two kinds of views for objects: a which draws a node/arc picture of the graph and a which displays an adjacency matrix. Having created an object G, of type , I can easily create one view of each type for G. Both views will present information about the same graph, but they will present it in very different ways. Moreover, any change to the graph made in one view will automatically be shown in all the other views. This provides the user with a great deal of power. Different views are often best suited to different tasks; with our view/object model, the user can operate in whatever mode(s) he finds convenient. No interface design choices are strictly enforced on the user to the exclusion of all others. This freedom of action, along with powerful and flexible customization, represents a significant improvement over more traditional toolkits. In order to add even greater flexibilty, we adopt the philosophy that it should be possible to effortlessly embed pre-written views into other views. So for instance, if I have a view implementation for my favorite board game , I can embed it into my personal word-processor simply by instantiating an as a child of my word-processor view. This facility rests on the standard layout protocol between parent and child and on the fact that every view is considered to exist within some CONTEXT. Typically, this context is simply another view or the current world. Also, it is important to keep in mind that a view does not need to be a descendent of its context within the visual hierarchy. However, any object which provides the proper methods can be used as a context. Whenever a view is created, its parent is allowed to fill in the new view's context slot. A view which desires to possess contextual control over its children should fill in this slot with a pointer to itself. Otherwise, it should simply fill in a copy of its own context slot. The default context is the operative world. The purpose of a context is to provide certain services to its subordinate views. Standard services include file selection, user prompts, color selection, and font selection. ... Need to specify exactly what services are standard and exactly how they are used. The toolkit itself provides a handful of basic view classes. These represent the skeleton of interface code, and they are meant to be subclassed and expanded upon. The classes available are: ... The most basic class. Since panes are an internal interface, this class exports operations for drawing. It is essentially just a window for the programmer to draw random things in. ... An atomic interface element such as a button. These will often be native components, and they are sensitive to user input. ... A view which supports the visual management of some number of inferior views. ... Views which are non-interactive. Text labels and shadow frames would good examples of static views. ... An extended version of the class. This provides an infinite drawing space with display list facilities, coordinate transforms, and arbitrary resolution. The following is a partial list of methods supported on views: realize ((v )) - Creates a realization for the given view. Typically, this just involves realizing the attached pane. destroy ((v )) - Destroys the given view. This will subsequently destroy all inferior views, as well as the attached panes. resize ((v ) (s )) - Resizes the given view to have the given shape. In the given shape, entries must always be filled in if their companion slot is. So, if the X slot is filled, the Y slot must be also (likewise for WIDTH and HEIGHT). handle-event ((v ) (e )) - A method to handle the event E in view V. Event handling is described in greater detail below. create-view (obj (parent ) #key prefer) - Method called to create a view for OBJ. This is an internal interface method which should not normally be called by users. However, adding new methods is a means of customizing the default presentation for objects. It is meant as a subsidiary function of ADD-VIEW. The prefer: argument allows the caller to specify a preference for what class the resultant view should be. The creation method should attempt to honor this preference, but it is not required to make any concessions to this stated preference. In addition, CREATE-VIEW will store the association of the newly created view with the given object. add-view ((v ) object #key prefer) - Adds a view for OBJECT as a child of V. The prefer: option is used to specify a preference for what class of view will be created (see create-view for further discussion). ... Talk about dialogs ... Talk about menus Event Handling ----------------------------------------------------------------- The heart of any interface toolkit is the process of event dispatching and handling. Typically, an event queue is the sole conduit between program, user, and window system. Much of the dynamic behavior of a program is in response to user input events. Not surprisingly, the number of events dispatched during the course of an average application tends to be quite large. To summarize, events are legion in modern applications. For this reason, it is important that the event handling mechanism be both well understood and efficient. During its lifetime, an average event goes through three distinct phases of processing: generation, dispatching, and handling. The generation phase is within the domain of the windowing system, and therefore not the concern of the toolkit. The next phase, dispatching, bridges the gap between the two. First, the system must perform dispatching tasks such as deciding which application to dispatch the event to and which window within that application should receive the event. Once the event arrives in the our application, the toolkit will dispatch the event to the appropriate event handler. The means by which events are dispatched throughout a particular interface world is the HANDLE-EVENT generic function (see above for arguments). Each world possesses an event loop which essentially: - Reads the next pending event - Determines what pane this event is destined for - Calls the handle-event method for that pane's view In general, the issue of event delivery is handled for us by the native system. Events are always delivered, by the underlying window system, to specific native windows. The toolkit determines where to send the event by translating the native window into its associated pane. Having found a particular pane, the handler method for the view bound to that pane is invoked. Although the issue of delivery is fairly simple, handling of input events does require some clarification: - Mouse events are delivered to whatever pane they occur in. (This ignores the complications of pointer grabs) - Keyboard events are delivered to whatever pane has the focus. Focus may be implicit or explicit, and the rules for changing the focus may be platform-specific. In order to control the focus, views support the following methods: (a) ACCEPT-FOCUS ... tells the view it is being given the focus (b) LOST-FOCUS ... tells the view it has lost the focus (and possibly) (c) RELINQUISH-FOCUS ... asks a view to relinquish the focus; it may decline Geometry Management (incomplete) ----------------------------------------------------------------- In order to construct an interface of any complexity at all, it is necessary to manage the layout of interface components. At the window manager level, layout management is performed explicitly by the user (with title bars and resize grips). However, within applications most layout management should be done automatically to best display the information which is to be presented to the user. This layout mechanism may be customizable (for instance, by means of dialogs), but it still operates without direct input from the user. Since the goal of automatic layout management is to best present available information, it seems natural that the burden of arbitrating layout be placed on the views displaying this information and their immediate parents. The active views have intimate knowledge of the information they want to display and how to display it. Their parents must reconcile the demands for space from all of their children in an attempt to find a reasonable layout. In order to facilitate layout management, the toolkit defines a protocol by which children can request changes in their shape from their parents. The relevant generic functions are: ADD-TO-LAYOUT ((parent ) (v )) UPDATE-LAYOUT ((parent ) (target ) (target-shape ) #key expect-others) The class is used to define the physical shape of a view. A the moment, it contains x, y, width, and height values. However, it is conceivable that the contents of the shape might be extended to describe physical characteristics of a view such a stretchiness. Initially, when a view is first added to a parent's control, the generic function ADD-TO-LAYOUT is invoked. The initial value of the new view's shape slot is taken to represent a preference for an initial size (values of #f in any fields of the shape indicate no preference). The parent may honor these preferences, but it need not do so; it has complete control over the eventual shape of the new view. In the future, if a view wishes to alter its shape, it invokes the UPDATE-LAYOUT method for its parent. TARGET is the view which is requesting the re-layout, and TARGET-SHAPE is the shape which the target view would like to assume in the new layout. The EXPECT-OTHERS keyword informs the parent whether it can expect other child views to request re-layout in the near future. If #t, the parent may choose to wait some small increment of time before performing re-layout so as to encompass more re-layout requests. If the parent decides to allow the request, it is responsible for actually resizing the target, using the RESIZE generic function. Possible return values are: - #f ... request denied, no changes made - allowed: ... request allowed, desired change made - compromise: ... a compromise has been used, change made - deferred: ... request deferred, notification later via method call If the parent elects for compromise, it must guarantee that the new size lies somewhere between the request and the original size. ... Should the parent execute compromises without consent? ... Is deferment really such a good idea? View/Object protocol ----------------------------------------------------------------- The system maintains a mapping between objects and their associated views. This mapping may be internal (cached with the objects) or external (in a keyed by object). The internal mapping is provided by the class . Although any object is potentially viewable, 's provide greater efficiency (due to direct view caching) and potentially greater functionality. The following generic functions define the basic view/object protocol. They will work for any class of object. If you wish to have more specific interaction between an object and its view, you may define your own view class and corresponding methods to talk with the object. (The default methods for some of these will probably be based on some kind of introspection methods.) create-view ... see above associate-view (obj view) - Stores the association between OBJ and VIEW. closing-view (obj view) - Notifies an object that the given view (which must be a view for OBJ) is being destroyed. This will disassociate the object with that view. update (obj #rest changes) - Notifies the views of OBJ that their object has changed. The #rest argument can be used to describe exactly what has been changed to avoid total redisplay. This is not necessarily guaranteed to be invoked by random setter methods. However, changes made in graphical views will always cause update methods to be invoked. Random Stuff ----------------------------------------------------------------- - As a general interface policy, I think that right-clicking on an object should bring up a menu of relevant operations on that object. - Another kind of useful view would be, let's call it, a command view. This view would have a command table mapping command names to actions (similar to Hemlock command mechanism). Some commands could be marked as "primary", and they would be used to construct context-sensitive popup menus as above. - Could probably have an which provides abstract facilities for building editor views. - Setting of event masks: You specify what event classes you want, and this is translated into an event mask for you. - We seemingly wish to have a portable resource language. I think it would be really great if we could find such a beast out there in the world. If not, we need to design our own. - It would seem that drawing transformations will be restricted to s. Issues to Resolve ----------------------------------------------------------------- - How to deal with multiple screens. - How to describe input gestures. Something like Hemlock key events? Something different? - Use of measurement systems other than pixels (eg. points, mm) for portability.