Inter-Office Memorandum To Cedar Interest Date October 21, 1983 From Scott McGregor, Doug Wyatt Location Palo Alto Subject The Viewers Window Package Organization PARC/CSL XEROX Filed on: [Indigo]Documentation>ViewerDoc.tioga. Documents: [Indigo]Top>Viewers.df, as exported by the Cedar boot file. The Viewers Window Package Tool design Designing good tools in the Viewers world is a tricky business. It shouldn't be tricky, but it is. This report talks about some of the issues involved, and explains why you might do things one way instead of another. It is the result of several people's experience in designing really solid tools. The conclusions are not always obvious, so it is worthwhile reading this report carefully. We hope this report will stimulate ideas about how to make tools easier to design. Viewers philosophy The Viewers window package is the arbiter of the user input and display hardware in the Cedar programming environment. It provides the illusion to the programmer that there is a private display, mouse and keyboard associated with each application, while allowing the user to simultaneously interact with many such applications. The basic object manipulated by client programs and visible to the user is the viewer; a rectangular area with arbitrary contents which may be made visible on the user display. A viewer takes its name in that it allows the human user to view and interact with the data associated with a Cedar application. The underlying applications software has complete control over the displayed contents of a viewer and has available a rich user interface for user input. The screen position and size of a viewer may be modified by the user as well as under program control. Underlying abstraction versus user's view. Using the new menus. In Cedar 4.4 there were six potential ways to invoke a particular function in a package: through a TIP table, through a menu item, through a button item, through a Viewer Class procedure, through a UserExec.CommandProc, or directly through a Mesa interface. Each of these had slightly different semantics and slightly different syntax. This made it difficult to treat input from the user in a uniform way. To improve things, Randy Pausch wrote a new menus package that made menus and buttons act like TIP tables. In addition to unifying the handling of user input, this also made it easier to separate the appearance of an interface from the functions it represents. The philosophy behind TIP tables was to have an external representation for mapping user actions into functions. A TIP table is not part of a package's code; it is a human-readable table found on the disk. If the user is unhappy with the package's interface, he can change it by modifying the TIP table. Menus and buttons now have this same feature: It is possible to change the mapping between mouse clicks and the functions invoked by changing a file on the disk. Another feature of the TIP scheme is the centralization of dispatching. All of the input derived from a TIP table is funneled through one procedure: the class's NotifyProc. From there, the input is dispatched to the appropriate procedure. Centralizing input like this allowed Tioga to have an event notification scheme, where clients outside of Tioga could ask to be notified whenever a particular event occurred. One possibility for the future is to make this event notification scheme available to every client of Viewers, not just to Tioga. The right way to separate the user interface from a package's functionality is first to write the package for clients, and then to design the interface for users. (A client is a program, a user is a human being.) Do not assume that users will be the only ones using your program. Eventually, someone will want to write a program that does automatically what he used to do manually. You might as well design your system now with that in mind. Once you have written a package that clients can use, you can design an interface for the user. Think of all of the functions you want the user to be able to do. Figure out a good name for each function. Then write a NotifyProc which dispatches these names (as ATOMs) into calls on your package. The NotifyProc is also the place to bind the parameters for the functions. For instance, an arm of a SELECT statement in the NotifyProc might look like this: $Rename => Tool.Rename[viewer: self, name: ViewerTools.GetSelectionContents[]]; Where the new name is given by having the user select it somewhere. Finally, you should design the interface that the user will see. Decide the names of the menu items. Decide whether left-clicking should have a different meaning from right-clicking. Write it all down using the syntax specified in MenuBNF.tioga. (There are some examples at the end which can be copied and modified.) Write it all down in a separate file, and then access that file from your code using a short path name. (If you use a long path name, then the user won't be able to tailor the interface to his needs.) Conventions for persistent viewers. There are several applications now that would like to be able to simulate viewers that persist over a rollback or boot. Unfortunately, they can't do this without some cooperation from the implementing package. This section explains what these applications want to do, what they need, and what the implementor must do in order to make it all work. There are three applications that would like to be able to simulate persistent viewers: desktops, whiteboards, and the data base. All three applications store viewers in data structures that persist beyond a rollback or boot. Desktops store configurations of viewers in a file on the disk, whiteboards store viewers on whiteboards in a data base, and the data base stores viewers in a data base file on a remote server. In order to be able to use the viewers they have stored, they must have a way of re-creating them from scratch. Calling their implementors doesn't work, because you may not know who implemented a particular viewer. The only way that can work is to establish some sort of convention with the implementors about how viewers get created. The convention that I propose is that calling ViewerOps.CreateViewer with a viewer class and instance name would return a correctly initialized viewer. For this to work, tool implementors must abide by the following rules: 1) Each tool must have its own class. If a there are several tools that belong to the same class then there is no way to distinguish between the tools later on. For instance, a large number of tools currently belong to the Container class. Calling ViewerOps.CreateViewer[$Container, "Watch"] may create a container with the name "Watch", but it won't create a Watch tool. ViewerOps.CreateViewer[$Watch, "Watch"] has to be implemented. 2) Each new instance must be initialized by the InitProc, and nowhere else. It must be the InitProc that puts the menus up and adds the buttons and sub-viewers. If any part of the initialization is done outside of the InitProc, then the special applications will get only partially initialized Viewers. 3) The name and file of a viewer must uniquely identify what instance is being created. The only variable parameters that can be passed to ViewerOps.CreateViewer are the name and backing file. Classes that don't need a backing file can use the name passed in the backing file to help identify the instance. For example, Walnut message windows might store the grapevine ID as the backing file and use that information to determine which message is wanted. To help make this all easier, Viewers now supports a simple sub-classing scheme. An optional parameter of ViewerOps.RegisterViewerClass called ``superClassFlavor'' specifies the type of the superclass, if any. RegisterViewerClass will fill in any NILs in the class record with procedures from the superclass. There is also a new procedure called IsClass which determines whether or not a viewer is an instance of a given class. Most of the time, a new sub-class will default all of the class procedures except for the InitProc. However, if the implementor wants to substitute his procedure for one of the super-class's procedures, he need only include it in the class record. If he wants to use his procedure sometimes and the superclass's procedure sometimes, then he can replace the superclass's procedure with his own and then make an explicit call within his procedure to the superclass's procedure. An example of this is: IF self.class.parent.notify # NIL THEN self.class.parent.notify[self, input]; (This scheme is similar to the way Smalltalk does inheritance.) The only class procedures that you do not have to call explicitly are the InitProc and DestroyProc. Whenever an instance of a sub-class is created or destroyed, the system will automatically call all of the InitProcs or DestroyProcs in order to make sure that the instance is properly initialized or destroyed. One caveat: If you define a private NotifyProc for your sub-class, make the ENDCASE of the SELECT statement call the superclass's NotifyProc. Otherwise, input that was destined for the super-class will get dropped on the floor. (This could be handled automatically by having the NotifyProc return a boolean saying whether it had accepted the input and letting the system traverse the class tree. There are other advantages, too. Is this worth doing?) Issues in serialization. There are three ways of serializing actions in the Viewers world: using the notifier, MBQueues, or monitor locks. Each method has its use; none of them is superfluous. However, knowing which of them to use can sometimes be tricky. Hopefully this discussion will help. Using the notifier. The notifier is the first line of defense. It is a single process that handles mouse actions and keyboard strokes. It is responsible for queueing up user actions, determining which viewer they belong to, and then passing them to the viewer via its NotifyProc. The notifier waits until the NotifyProc returns before processing the next user action. This means that all user input starts out serialized. A simple way for an application to keep its actions serialized would be to hang on to the notifier process until its work is done. That is, when its NotifyProc gets called from the notifier, it doesn't return until all of the work is finished. However, this would prevent the user from doing anything else while the application is running. Good citizenship requires that you release the notifier process as soon as you can so that the user can do other things. One way to release the notifier process is to fork another process to do the work. Buttons and menus automatically fork processes before calling the NotifyProc (unless the implementor tells them to do otherwise). However, once a process has been forked, all serialization has been lost. While the forked process is running, the notifier may fork more processes to do the same thing. Something else will have to be done to coordinate these processes further on down. Using a viewer input queue. A better way to release the notifier process is to put the user input on an a viewer input queue. A viewer input queue is a queue of user actions: button clicks, menu clicks, or other unspecified actions. The ViewerInputQueue (formerly MBQueue) interface allows you to replace the standard buttons and menu entries with special buttons and menu entries. These special buttons and menu entries will put their user actions on a queue. The notifier is held until the action is on the queue, so the queue is guaranteed to have its actions in the same order that the user invoked them. Usually an application will have one application-wide queue and a single process to handle it. This process will pull things off of the queue one at a time, finishing one action before going on to the next. This means that the application's input remains serialized and the user can go on to do other things. For the most part, viewer input queues are easy to use. The only time they become tricky is when you are dealing with parameters. Suppose you have a button that does something to a file, and the name of the file is kept in a nearby text viewer. If you use ViewerInputQueue in the obvious way, you may get an inconsistent name from that text viewer. Consider: The button click gets put on the queue. Things are slow, so it justs sits there for a while. In the meantime the user changes the name of the file in the text viewer. Finally, the button gets taken off of the queue and invoked. Unfortunately, it gets the wrong file name out of the text viewer. This is an example of incorrect binding. The way to fix this is to bind the name of the file to the button before putting it onto the queue. When the notifier calls the button, the button should extract the name from the text viewer while it has the world frozen. It should then put itself on the queue with the name of the file as its clientData. Later, when it gets invoked, it will execute with the correct parameter. This technique can be generalized for any type of parameterized action. This example points out a general principle: the notifier is the only means of making multiple user actions atomic. Any time you want to gather several parameters from the user atomically, you must do it while holding onto the notifier process. If you don't hold onto the notifier process, then you run the risk of having the user change one of the parameters before you read them all. This is true no matter what other means you plan to use to serialize the atomic actions. Using monitor locks. The notifier and viewer input queues are all you need if the application is used only by the user. However, if you ever plan to let people write programs that use your application, then the notifier and input queues aren't enough. Somehow client calls will have to be serialized with input from the user. A kludgy way around this problem is to force clients to act like users. That is, they have to simulate input from the user by putting user actions into Inscript or putting actions on the application's input queue. This is not recommended. The right way to handle this problem is to write your application with multiple clients in mind. Then the user is just a special client. I believe that people should write their applications this way, even if they think of their application as primarily being for the user. Somewhere down the road someone is going to want to write a program to automate what he has been doing manually. You might as well plan for it now. There are many ideas about how to write applications that work in the face of multiple clients. A whole paper could be written on that topic alone. This report will just touch briefly on the major schemes that have been used: Entry procedures. Require clients to call through a few well-known procedures. Make those procedures entry procedures which all use the same monitor lock. (This allows at most one client into the application at a time, but it is easy to implement.) Queues. Funnel all of the client requests through a queue. Have a single process at the other end that handles the requests one at a time. (This allows at most one client into the application at a time, like entry procedures. Its advantage is that requests are handled by the application in the same order that the clients give them. This scheme takes a little more work than entry procedures.) Stateless applications. Write the application in such a way that the only mutable values used are kept in local frames. Treat procedure parameters as immutable. Don't use global values unless they are immutable. Store your temporary variables only in local frames, never in a global frame. (This scheme doesn't require any locks at all and it allows lots of parallelism. However, the constraints are hard to meet.) Object monitor locks. Put monitor locks on each instance of the data. Make each procedure that reads or writes the data an entry procedure (object style). Eliminate any use of global data. (This scheme has lots of parallelism, but it takes a lot of work to implement.) Process re-entrant locks. Create your own brand of object monitor locks. Distinguish between read and write locks. Allow processes to re-acquire the same lock as many times as they want. Define "CallUnderLock" procedures that take the data and a client procedure and lock the data before calling the client procedure. (This is the most functional scheme, but it is the hardest to implement correctly. Viewers and Tioga both use this scheme. Talk to a wizard before attempting to implement it.) If you use one of these schemes to serialize client requests, you won't have to bother with a ViewerInputQueue to serialize user input. User input will be serialized just like all of the other client requests. However, you still may have to use the notifier to make some actions atomic. If you do, be sure to explicitly fork your calls to the application. Otherwise you will lock up the notifier while your application is running. (Normally Buttons and Menus fork a process for you. If they don't, then you will have to.) Multiple instances of a tool. It is a bad idea to design your tool so that there can be at most one instance of the tool. Even if you don't think anybody would ever want more than one, somebody will. They will want to be able to have different instances with different parameters so they can switch back and forth easily. It is better to let the user have the freedom to do what he wants. There are several ramifications to the decision to allow multiple instances of a tool. The first is that you cannot store data in the global frame, you must associate it with its viewer. The best way to do this is to attach it to the viewer's property list. (See ViewerOps.AddProp and FetchProp. It's a good idea to define inlines that let you add and fetch the data from the viewer without having to remember what the property name was). The second ramification is that serialization may be more difficult. If you want maximum parallelism, you will have to use object monitors instead of regular monitors. Object monitors are harder to use than regular monitors. The last ramification is that care must be taken or you will end up with circular data structures. Circular data structures are a problem because they aren't collected by the normal garbage collector; they can only be collected by the trace-and-sweep garbage collector. They may show up if you have a main data structure associated with the whole viewer, plus auxiliary data structures for each button. If a button wants to go from the auxiliary data structure to the main data structure, the auxiliary data structure might have a pointer to the main data structure. If the main data structure wants to be able to enumerate the auxiliary data structures, it may have pointers to them. The result is circularities. These circularities can be avoided if the button gets the main data structure from its parent rather than having a pointer in the auxiliary data structure. Dealing with control panels that appear and disappear. Sometimes you want a tool that displays different control panels depending on what the user is trying to do. The easiest way to do this is to have all of the control panels available and just move the appropriate one into view. This can be accomplished by creating all of the sub-viewers, and then moving the ones you don't want to see way out into hyper-space. (Scott had aesthetic objections to this, but I don't see any practical problems with it.) Sub-viewers can be moved around with ViewerOps.MoveViewer. To make things easier, put your control panel inside an invisible nested container. Then all you have to do is move the container around, and all of the other viewers will follow automatically. The nested container will be invisible if you create it with border: FALSE and scrollable: FALSE. Dealing with ViewerBLT. Don't enable ViewerBLT if your viewer in any way adapts its layout to its current size. In particular, watch out for nested viewers with xbound or ybound set to TRUE. (Could this decision be made independently in x and y?) What are the implications of allowing a viewer to be updated but not painted? (Various calls with paint: FALSE.) Known problems. Using other fonts. The viewers package allows you to use fonts other that the Tioga font, but this doesn't work very well. It is hard to get the base line of text boxes and buttons to line up. The Tioga font has been hand-twiddled to make viewers look nice. If you use another font, it doesn't look very good. You should probably stick to the Tioga font until this gets fixed. A Summary of Design Rules. 1) Design your tool for clients first. After your package is ready to handle clients, write a thin veneer over the package for the user. Set it up so that the user can tailor the interface the way he wants. 2) Handle serialization within the application rather than within the interface. Don't depend on input coming from the user alone. Assume that you will have other clients some day. 3) Follow the protocol for creating new instances. Let each tool have its own class. Make sure that all of the initialization goes on in the InitProc and nowhere else. Make sure that the name and backing file for the viewer uniquely identify what instance is being created. 4) Plan for multiple instances of the tool. Don't store data in a global frame; put it on the viewer's property list. Use object monitor locks or depend on the application to handle serialization. 5) Set fork: FALSE on Buttons or Menu entries that get parameters from other viewers or sub-viewers. Otherwise the parameters may change before they get accessed. Be sure to release the notifier after you have the parameters by forking a new procedure or putting your input on a queue. 6) If you use paint: FALSE, be sure to write-lock the viewer, otherwise you will get painting glitches. Invoke ViewerLocks.CallUnderWriteLock with a procedure that does all of the painting. *** old stuff below here *** Disclaimer This document is currently in progress and hence is incomplete (as witnessed by a number of sections not yet written). It reflects the state of the Viewers package for Cedar version 5.n. The original version of the document was written by Scott McGregor. Introduction The Viewers Window Package is the arbiter of the user input and display hardware in the Cedar programming environment. It provides the illusion to the programmer that there is a private display, mouse and keyboard associated with each application, while allowing the user to simultaneously interact with many such applications. The basic object manipulated by client programs and visible to the user is the viewer; a rectangular area with arbitrary contents which may be made visible on the user display. A viewer takes its name in that it allows the human user to view and interact with the data associated with a Cedar application. The underlying applications software has complete control over the displayed contents of a viewer and has available a rich user interface for user input. The screen position and size of a viewer may be modified by the user as well as under program control. This documentation is written for the programmer intending to use the Viewers Window Package to build a new application. It is organized along the broad areas of functionality that the Viewers system provides and attempts to explain design theory and some pragmatics. For examples of usage, see the references within each section, and for exact details consult the interfaces directly. One point of notation: throughout this document, client refers to a program calling the Viewers interfaces, whereas user refers to the human invoking Viewers operations with the mouse and keyboard. Screen Layout The User Desktop From the user point of view, the black and white screen is divided into four areas. The top quarter inch of the display is used for the messages and a small set of system command buttons. The bottom of the display is a variable height area reserved for displaying icons, which are small pictorial representations of normal viewers made iconic to conserve screen space. The remainder of the display is divided into left and right columns with a moveable partition between them. The user may optionally attach a color display, which is equivalent to an additional color column. Unlike many window packages, viewers do not normally overlap, but instead spread out to cover as much as possible of the available space on the user display. Within a column, the height of a particular viewer is determined by satisfying a number of constraints set by the Viewers system, the implementing program and the user. Viewers constraints include balancing of the columns; i.e. viewers will be expanded so that the entire column area is used, and that no viewer is smaller than the height required for the caption and command menu at the top of each viewer. A client program may specify a height hint in the viewer instance's openHeight field, which will be honored if it doesn't conflict with other constraints. In addition, the user may set a height hint that overrides the openHeight value by invoking the Adjust menu command. User-set hints are discarded when a viewer is made iconic. A fine point: The current implementation does not deal particularly well with overconstrained columns; i.e. when the sum of the Viewers enforced heights plus the client openHeight hints exceeds the height of the column. In order to solve the conflicting constraints, each viewer is given one nth of available column height. Ideally, Viewers should either pro-rate the existing space according to the client requests or force some of the viewers in the overconstrained column to become iconic. Internally, the tree of viewers is partitioned into four subtrees, corresponding to static (typically those viewers with a permanent, fixed position on the screen), the left column, the right column, and the color display. The static subtree comprises all icons, the message window at the top of the display, and the system buttons located in the upper right hand corner. The boundary between the left and right columns as well as the height of the columns may be set by invoking the Adjust menu command in any viewer and then sliding the mouse across the column boundary. The column height is quantized to the height of each row of icons, permitting several rows of icons or as few as none. Icons are displayed behind the columns; in other words, viewers in a column overlapping the display of an icon will hide the icon until the user shortens the column, or removes all the viewers from that column. Multiple Desktops Users can save configurations of viewers in a special viewer called a desktop. The user can then move back and forth between different configurations of viewers with a simple command. There is always one desktop which represents the configuration of viewers currently on the screen. This desktop (called the current desktop) is indicated on its icon by having its name inverted. The user can "fly" to another desktop by middle clicking the desktop icon while holding down the control key. The viewers and icons on the screen will be stuffed into the current desktop, to be replaced by the viewers and icons in the new desktop. The new desktop then becomes current. To get a new desktop, type "Desktop 'name'" to the UserExec. This will create an icon that looks like a minature screen. The first desktop created becomes the current one. A second desktop must be created before you can move to a new desktop. When you fly to the new desktop, only desktops and the UserExec will go with you, all other viewers will be stored in hyperspace. Viewers can be brought over one by one as described below. To move a viewer from another desktop to the screen, open the desktop icon and left-click the button representing the viewer. This will move the viewer from that desktop onto the screen. If the viewer is already on the screen but iconic, clicking the button will open the viewer. Clicking the button with the shift key down causes the viewer to grow to full column. Viewers can be added to a desktop by selecting the viewer and clicking "AddSelected" in the desktop's menu. This will cause the viewer to be removed from the screen. To move an individual viewer from the screen onto a particular desktop, open the desktop icon, select the viewer and left-click "AddSelected" in the menu of the desktop. The viewer will then disappear from the screen. You can remove a viewer from a desktop by clicking its button with control held down. If this was the last reference to the viewer, it will automatically be moved back onto the screen. Destroying a desktop viewer may also cause hidden viewers to appear on the screen. Although some viewers may be inaccessible to the user because they are on different desktops, all viewers are accessible to client programs via ViewerOps.EnumerateViewers. This means that programs don't have to enumerate desktops to see all of the viewers. The bit "offDesktop" will tell the client program whether the viewer is currently accessible to the user. Opening, closing, repositioning, or blinking a viewer will automatically move it onto the screen, whether or not it was on the screen before; painting a viewer will not. If the client program wants to manually move a viewer onto the screen, it can do so with ViewerOps.ChangeColumn[viewer, {left, right, color}]. All viewers are stored off the screen as icons, therefore a program should only have to worry about manually moving viewers onto the screen if it wants the user to see it repaint an icon. Viewers The display of a viewer consists of two nested rectangles. The outer rectangle bounds the viewer window overhead, consisting of space for an optional black border around the viewer, an optional scrollbar space on the left, a black caption bar at the top, and an optional menu below the caption containing an arbitrary number of menu lines. The inner rectangle defines the viewer client area, which is available to an application for display of its data. Each viewer has a parent, a sibling and a child viewer, any of which may be NIL. Viewers may be recursively nested within (i.e. children of) other viewers, in which case display of the caption and menu will be suppressed and no space reserved in the outer window rectangle. The coordinates for the window and client rectangles are computed with respect to the enclosing parent's client rectangle origin. If the viewer has no parent, it is known as a top level viewer and its rectangle coordinates are with respect to the lower left hand corner of the display. A viewer may extend outside of its parent's client rectangle, in which case it will be clipped during display. Sibling viewers may not overlap. Viewer Classes The Viewers package allows the programmer to create a class of viewer, all instances of which will share code for common operations. A viewer class is a record that defines a set of operations and parameters common to all viewers of a particular user defined type. For example, the Tioga document preparation system creates a viewer class for all of the editable text viewers, so that each instance of a Tioga viewer uses the same code to display, scroll, accept input from the user and so forth. A viewer class record (as described in ViewerClasses.mesa) contains the following information: flavor: ViewerClasses.ViewerFlavor _ NIL; Each class has a unique atom which associates a name with the class record. init: ViewerClasses.InitProc _ NIL; During the creation of a new viewer, this procedure is called in order to permit the client to initialize private data structures. The viewer will not appear on the screen until the procedure returns. Class implementors should use this procedure to insert menu entries and add sub-viewers. paint: ViewerClasses.PaintProc _ NIL; The Viewers package assumes that the bitmap is write-only and that the client can reconstruct its screen image on demand. Whenever the information on the screen for a particular viewer becomes invalid, the PaintProc will be called by the system to restore the correct display. The PaintProc is passed a graphics context clipped to the bounds of the viewer, a whatChanged parameter which the client may use to encode private information about what to update, and a clear boolean that indicates whether the viewer has been whitened before the paint call. There is a complete section on painting elsewhere in this document. destroy: ViewerClasses.DestroyProc _ NIL; The Cedar garbage collector normally deallocates storage associated with a viewer when it is destroyed, but sometimes a client finds it convenient or necessary to free resources or note the destruction of a viewer instance explicitly. This procedure is called just before the viewer is removed from the viewer tree. notify: ViewerClasses.NotifyProc _ NIL; Mouse and keyboard input from the user is communicated to a viewer as a list of results described in a tip table (see the tipTable field below). This procedure will be called whenever an input event for a particular viewer is received. tipTable: TIPUser.TIPTable _ NIL; A TIP Table is a list of productions, mapping mouse and keyboard operations into a list of results meaningful to the client. Please see the section on Mouse and Keyboard input for more information. modify: ViewerClasses.ModifyProc _ NIL; Keyboard input depends on a viewer owning the input focus described later. This procedure is called whenever a viewer loses or acquires the input focus in order to provide feedback, such as a blinking caret. set: ViewerClasses.SetProc _ NIL; Some viewer classes choose to implement this interface as a way of setting the contents or state of a viewer. The data and op arguments may be interpreted any way the client chooses and the finalise parameter determines whether the new information should be reflected on the display. Tioga, Buttons, and Labels are three viewer classes which implement this interface to set the text contents when passed a ROPE or REF TEXT. get: ViewerClasses.GetProc _ NIL; A viewer class may implement this interface as a means of allowing a client to query the contents or state of a viewer. As with the SetProc, the op parameter may be interpreted by class implementors as they see fit. scroll: ViewerClasses.ScrollProc _ NIL; The ScrollProc is called with an operation parameter, corresponding to scrolling up, scrolling down, thumbing to a particular place in the data structure, or querying the client as to its current scroll position. For the scroll up and scroll down operations, the amount passed is the user's request, measured in points. It is up to clients to interpret the amount with respect to their internal data structures, adjust their display data structures and request repaint. For the thumb operation, the amount passed is the percentage to scroll into the viewer; i.e. 47 would mean to start the top of the display 47% into the data displayed, as interpreted by the client. The query operation requests that the client return two numbers for feedback to the user, corresponding to the percentage of the data displayed at the top and bottom of the viewer; i.e. if the entire data structure were visible, the client would return 0 and 100. If the client cannot reasonably compute the query percentages or chooses not to, then it may simply return from the ScrollProc when this operation is encountered. menu: Menus.Menu _ NIL; A viewer may optionally display a menu of commands underneath the caption. See the section on Menus for more information. icon: Icons.IconFlavor _ document; When a viewer is iconic, the client PaintProc is intercepted and the viewer is instead displayed as a small picture, determined by the IconFlavor set here. The Icons interface has an enumerated set available, as well as an interface to create new icons. Clients may also paint their own icons using the CedarGraphics facilities by setting the iconFlavor to private, which will cause the painting of iconic viewers to no longer be intercepted. If the iconFlavor is private, then it is up to the client to notice that they are iconic and paint appropriately. cursor: Cursors.CursorType _ textPointer; Viewers will display a cursor when moved over a viewer, determined by the value here. The Cursors interface has an enumerated set available, as well as an interface to create new cursors. clipChildren: BOOL _ FALSE; The normal paint order for viewers with embedded children is the parent first and then the children. There is normally no protection from the parent painting over a child unless this bit is set to true, in which case all child viewers will be clipped (excluded) from the context supplied to the parent. This field is not defaulted to true, since it makes normal CedarGraphics operations computationally expensive for the parent. The following viewer class fields are either rarely used or not currently implemented. They are documented here for completeness: save: ViewerClasses.SaveProc _ NIL; The SaveProc is called when the client is expected to write the private data structure to a disk file. This is currently only used by Tioga. copy: ViewerClasses.CopyProc _ NIL; The data associated with a particular viewer is private to the class implementation and hence the Viewers package doesn't know how to replicate the structure. This procedure passes an old viewer and a new viewer and requests that the data for the new viewer be made the same as the old. caption: ViewerClasses.CaptionProc _ NIL; The caption normally displays the name and status of the viewer. If this is not acceptable to a particular client, then they may implement this procedure and paint the caption themselves. The graphics context passed is clipped to the caption area. coordSys: ViewerClasses.CoordSys _ bottom; The coordinate system of the graphics context passed to the viewer PaintProc will normally have its origin at the lower left corner of the viewer, with x and y increasing to the right and upward. If the coordSys is set to top, then the origin will be set to the top left corner with y increasing downward. bltContents: BltRule _ none; The bltContents rule in the class requests that the window package attempt to save some of the current screen contents of a viewer by moving the bits to the new viewer location (e.g. when the viewer is moved). Each rectangle thus saved will show up as a PaintRectangle with flavor=blt, as a convenience to clients that keep caches of screen contents. It does not make sense to turn this bit on without also turning the paintRectangles bit on as well. Clients that scale an image to fit the viewer bounding box should not use this feature. paintRectangles: BOOLEAN _ FALSE; The paintRectangles bit in the class determines whether or not the window manager will pass a PaintRectangle (as the whatChanged parameter) to the viewer when the paintProc is called. These rectangles may be used by the client to optimize painting. If whatChanged=NIL, then the client must assume the entire viewer is invalid. If a PaintRectangle is seen by a paintProc, then it can safely assume that it need paint only those areas with flavor=invalid. It is important to note that the implementor of a viewer class can default any of the fields if they choose not to provide that particular functionality; the system will "do the right thing" with requests on viewers that do not implement a particular operation. The programmer creates a new viewer class by allocating and initializing a ViewerClasses.ViewerClassRec and then calling ViewerOps.RegisterViewerClass, passing the class record and a unique atom which names the new class. This operation creates a binding between the class operations and any newly created viewer instances (see the Viewer Instances section below). Subclassing (as in SmallTalk) is not directly supported by the system; the programmer must explicitly copy and modify an existing viewer class. A simple example of creating and registering a viewer class can be found on [Indigo]Viewers>LabelsImpl.mesa. Viewer Instances Once a Viewer Class is defined, instances may be created which will inherit information from the class. Many of the predefined viewer classes (listed in the next section) provide instance creation operations, otherwise a client may call ViewerOps.CreateViewer, passing the class and initial data for the instance. A ViewerClasses.ViewerRec defines the data associated with each viewer instance. During viewer creation, the client may initialize some of the fields, the Viewers package will initialize others, with the rest defaulting. The remainder of this section describes the data structure in some detail, and is of particular interest to a client implementing a viewer class or creating a tool comprised of many embedded viewers. Each viewer instance maintains a pointer back to its class data record: class: ViewerClass _ NIL The class field is initialized by Viewers during instance creation and remains constant for the lifetime of the viewer. Each viewer contains two rectangles, describing the outer boundary of the viewer and a properly contained inner area available for displaying client information: wx, wy, ww, wh: INTEGER _ 0, cx, cy, cw, ch: INTEGER _ 0 The wx and wy fields define the corner of the viewer with respect to its parent's client origin. In the case of a top level viewer, wx and wy will be with respect to the bottom left corner of the display screen. The ww and wh fields describe the width and height of the viewer, measured in screen pixels. The cx, cy, cw, and ch fields define an inner rectangle and represent the clipping boundary for display of the client information. cx and cy are measured from the parent's client origin, just as the wx and wy fields. The client should initialize the wx, wy, ww, and wh fields during viewer creation if and only if the viewer will be embedded in a parent viewer, since the column constraint algorithms will recompute these values. Since viewer position and other information is cached by Viewers, clients should never directly set any of the viewer rectangle values, but instead should use routines in the ViewerOps interface to change the size or position of a viewer. Other viewer fields of interest to clients include: data: REF ANY _ NIL The implementor of a viewer class may associate instance data with a viewer in this field. Note that only the class implementor may access the data field; other clients should use the property list associated with each viewer. name: Rope.ROPE _ NIL Each viewer has a text name associate with it, which in the case of top level viewers, is displayed in the caption. The client may modify the viewer name, but must be sure to repaint the caption if applicable (via ViewerOps.PaintViewer[viewer: viewer, hint=caption]). id: Rope.ROPE _ NIL Some viewers choose to associate a backing file with their data structures. While this logically belongs with the implementor's data, it can be stored here as a convenience to clients. It may be removed in the future. border: BOOL _ TRUE An embedded viewer is displayed with a one-bit wide black border if the border field is set. Top level viewers are always displayed with a border. tipTable: TIPUser.TIPTable _ NIL The TIPTable defines how each viewer interprets mouse and keyboard input and is copied from the class record when a viewer is created (there is a later section describing the mouse and keyboard input mechanism in more detail). In some cases, the client may wish to change the behavior of a particular viewer and may do so by modifying this field. A fine point: if a viewer owns the input focus, then its TIPTable is cached by the notifier, which presently must be reset by releasing and then recapturing the input focus. menu: Menus.Menu _ NIL A top level viewer may optionally have a menu of commands permanently displayed below the caption. Ideally, the client should pass a menu when a viewer is created. To add a menu to an existing viewer, the client must use ViewerOps.SetMenu to reset internal rectangle caches and to repaint the new menu on the display. icon: Icons.IconFlavor _ tool When a top level viewer is made iconic, it no longer displays the client data, but instead appears as a small symbol, easily recognizable by the user. The instance's icon field is normally initialized from the viewer's class, but may be overridden by the client during viewer creation. The Icons interface provides routines to access existing Viewers icons (Viewers includes symbolic pictures for a document, tool, typescript, and a file drawer) or to create new icons from either a procedure or a set of bitmaps stored in a file. The client may specify whether an icon is to be labelled by Viewers with the viewer name. If the instance's icon field is set to private, then the client PaintProc will be called even though the viewer is iconic, which is how the clock icon displays the current time. column: ViewerClasses.Column _ left When a viewer is created, the client may specify in which column a viewer will be displayed. Clients should only change this field in an existing top level viewer by calling ViewerOps.ChangeColumn. scrollable: BOOL _ TRUE Viewers will allocate a scrollbar area on the left hand edge of a viewer as well as provide appropriate user feedback if this bit is specified during viewer creation. The actual scrolling operations are performed by calling the ScrollProc in the viewer's class. iconic: BOOL _ TRUE Clients may set the iconic bit during the creation of a top level viewer in order to initially display the viewer as iconic at the bottom of the display or open in its column. A client may not change the iconic bit for an existing viewer, except by calling ViewerOps.OpenIcon or ViewerOps.CloseViewer. newVersion: BOOL _ FALSE A client may test to see if edits have been made to a viewer by testing the newVersion bit. Viewer implementors should use ViewerOps.SetNewVersion to set the newVersion bit and display the "[New Version]" message in the viewer caption. newFile: BOOL _ FALSE A client may test to see if there is currently no existing backing file for a viewer by testing the newFile bit. Viewer implementors should use ViewerOps.SetNewFile to set the newFile bit and display the "[New File]" message in the viewer caption. offDeskTop: BOOLEAN _ FALSE A client may test to see if the viewer is currently accessible to the user. ViewerOps.ChangeColumn[viewer, {left, right, color}] will move it back onto the screen. openHeight: INTEGER _ 0 A client may set the value of openHeight to be the desired vertical space taken up by a viewer when open in a column. The window manager will attempt to honor the request, but may give more or less space than requested. parent: ViewerClasses.Viewer _ NIL Each viewer that is nested within another viewer keeps track of its parent through this field. This field will be nil for top-level viewers. sibling: ViewerClasses.Viewer _ NIL Viewers of equal depth within a tree are linked via the sibling field. child: ViewerClasses.Viewer _ NIL A viewer may have children nested within it. props: Atom.PropList _ NIL Clients may associate arbitrary data and properties with a viewer by adding them to the property list. The AddProp and FetchProp operations in ViewerOps are the recommended way of accessing the viewer property list. A number of fields in the viewer record are private to Viewers or are not particularly of interest to most clients. They are included here for completeness: lock: ViewerClasses.Lock _ [NIL, 0] The viewer data structure may be locked with non-exclusive read or exclusive write semantics, making use of the lock data structure within each viewer instance. The client should never access this field directly, but should use routines provided in the ViewerLocks interface. A more detailed treatment of locking may be found elsewhere in this document. visible: BOOL _ TRUE The Viewers package maintains a private visibility hint to speed some repainting operations. Clients should not rely on the value of this field. offDeskTop: BOOL _ FALSE Some viewers may be on other than the current desktop, in which case the offDeskTop bit will be set. destroyed: BOOL _ FALSE A viewer is destroyed by calling ViewerOps.DestroyViewer, at which time it is removed from the viewer tree and this field is set to false. saveInProgress: BOOL _ FALSE Some clients need to know when a Save operation is currently pending for a viewer, and may examine the state of the saveInProgress field. Note that the newVersion bit remains set until completion of a Save. init: BOOL _ FALSE Viewers maintains a private hint indicating that a new viewer has been properly initialized. It has no other uses. inhibitDestroy: BOOL _ FALSE The inhibitDestroy bit prevents the user from invoking the Destroy operation in a top-level viewer menu. This field will be removed in future releases, so clients should not depend on it. guardDestroy: BOOL _ FALSE The Destroy menu command for top-level viewers is normally guarded iff there are new edits. If the client wishes the Destroy menu command to always be guarded, then they may set this bit. link: ViewerClasses.Viewer _ NIL Linked viewers are connected via a ring data structure. Currently, linked viewers are not completely supported by the system, but this field is included for clients to implement linking semantics, as in Tioga. position: INTEGER _ 0 The position field is a private value used by Viewers for icon positioning and adjusting of viewer size when open in a column. Predefined Viewer Classes Listed below is a set of viewer classes for client use with implementations provided in the Cedar boot file. Buttons Buttons are a class of viewers that display a text label and call a procedure when the user clicks the mouse over them. Buttons are usually created within a container (see below) as part of a tool, either directly throught the Buttons interface or as part of a higher level object created with the ChoiceButtons interface. Parameters to the client procedure include the button viewer, the mouse button that invoked the button, and the state of the shift and control keys at the time of invocation. Containers A container is a viewer that permits other viewers to be recursively embedded and scrolled, and is created using the Containers interface. Routines are also provided to constrain embedded viewers to be clipped to the edges of the parent container. Unlike most viewer classes, the coordinate system inside a container has its origin at the upper left corner, with the y coordinate increasing in the downward direction. A container initially has no menu, but many tool implementors find it convenient to include a menu of tool-related commands when creating a container. Labels A label is a simple kind of viewer that displays a text message in a single font. The label may not be selected or edited, but has the advantage over Tioga text viewers (described below) of having very low data structure overhead. Rules A rule viewer displays as a rectangle of a single color on the display, and is created using the Rules interface. They are useful for creating line graphics when designing the layout of tools. Text Text viewers are implemented by the Tioga Document Preparation system described in detail on [Indigo]Documentation>TiogaDoc.tioga. The ViewerTools interface provides a set of operations that allow a client program to create text viewers as well as to fetch and store their contents. There are a great many additional operations supported by the Tioga editor, exported by the TiogaOps interface, which is described in the Tioga documentation. Implementation Guidelines The procedures and variables in a viewer were designed to support a particular style of implementation for new classes. Many of the procedures and variables were added specifically to help solve some general problem of concurrency or user interaction. Implementors are not required to use these procedures, but they should only depart from them when they have good reasons. The best way to write a user application that uses Viewers is to first write a applications package accessible to client programs and then make a thin veneer over it for users. It is tempting to tailor your implementation to the user interface, but this temptation should be resisted. No matter how user-oriented your program is, some user some day will want to write a program that uses your application directly. It is best that you prepare for that eventuality now. There are four different ways that a particular function in your program could be invoked: notification of an user event through the NotifyProc, a call on a pre-defined function in the Viewers class (such as set, get, and save), invocation through a button or menu item on the viewer, and a client call on an interface you export. If you use more than one of these paths to invoke a function you should be absolutely certain that they all have exactly the same semantics. The best way to do this is to have them all call the same procedure. Thus the NotifyProc that handles special user actions should do no more than gather parameters and dispatch to procedures that are defined in an interface exported by your program. The same should be true of procedures that are invoked with buttons and menus. The functions `create' and `destroy' are special functions in the Viewers world. All of the creation and destruction that is necessary for a particular Viewer class should happen in the class's InitProc and DestroyProc. In particular it is very important that all of the menu construction, sub-viewer creation and private data initialization happen in the InitProc. This allows the Viewers package to create and destroy instances of a class without having to know about interfaces exported by the class's implementation. This is important since opening a desktop sometimes requires the re-creation of a viewer that the user had destroyed. All that the Viewers package can do is call ViewerOps.CreateViewer[viewerFlavor, info: [name: viewerName]] and hope that this is sufficient to create and initialize the viewer. Application tools should create their own viewer's class just so they can have their own InitProc, even if there is never more than one instance of the tool. Similarly, ViewerOps.DestroyViewer[viewer] should be all the Viewers package needs to make sure that the viewer has cleaned up all of its internal data structures. (Cleaning up internal data structures includes breaking circular links so the garbage collector will reclaim the storage.) Mouse and Keyboard Input Locking The ViewerLocks interface defines an interlocking mechanism to arbitrate access to the Viewer window tree, viewer columns, as well as individual viewers. A particular viewer may be locked for reading or for writing. A read request implies that the calling code would like to examine values in the ViewerClasses.ViewerRec and insure that these values are invariant for the duration that the read lock is held. Any number of clients can simultaneously hold read locks on a single viewer. A write lock is an exclusive access to a viewer for the implied operation of modifying one or more of the fields in the ViewerClasses.ViewerRec. Locks are process re-entrant, meaning that a single process may request a lock multiple times without deadlock; i.e. if a process currently has write access to a viewer, it may in addition request read or write access (this obviously doesn't grant any new permissions, but is useful when a client has already locked a viewer and wishes to call code that may attempt a lock). Due to the ordering of the read and write resources, a process holding a read lock may not request a write lock on the same viewer without first releasing the read lock. The viewer tree as well as the particular columns are another set of resources that may be locked. Locking a column is equivalent to (but not implemented as) locking all of the viewers in that column. Locking the viewer tree is equivalent to locking all of the columns for write in addition to the root pointer for the tree. The viewer tree must be locked by the client any time a top level viewer changes size or position in the tree that crosses column boundaries. Since all painting operations are protected by a read lock on the updated viewer, the viewer tree lock will not be granted until painting activity has subsided. The hierarchy of locks in the viewers window package, from highest to lowest is: the viewer tree, a write lock on a column, a write lock on a particular viewer, a read lock on a column, and a read lock on a particular viewer. A client may only request a new lock if is at the same or lower logical level. The documentation in the viewer interfaces has been (will be) updated to show which locks the operation requires, so that the client can avoid deadlocks. A fine point: if a client program needs to lock multiple viewers, it must acquire the lock for each viewer in the proper order. The ViewerLocks interface provides routines that correctly sort the parameter list for up to three viewers; otherwise the client must order the lock requests so as to lock the viewers in ref order (treating the viewer ref as a long cardinal and locking the highest first). Columns must be locked in the order {static, left, right, color}. Users are advised to make use of the call-back operations provided when they need to perform locked operations on viewers, and to refrain from using the locking primitives directly. The suggested usage is to create a local procedure containing the critical code, which is passed to one of the call-back operations in ViewerLocks. The local procedure will then have full access to all of the variables in the outer procedure. Painting Icons Menus Pragmatics Interface Summary Buttons Buttons are a class of viewers that display a text label and call a procedure when the user clicks the mouse over them. This interface provides routines to create and destroy buttons, as well as the ability to change the visual appearance of the text label. Carets Implementors of text editors (and maybe some other types of editors) provide feedback to the user where text insertion will go by means of a blinking caret on the screen. The Carets interface allows a viewer class implementor to display blinking carets without having to worry about issues of timing, clipping, etc. ChoiceButtons When building tools and other applications using Viewers, clients often require user interface abstractions for enumerated types, string parameters the user can edit, and boolean values. ChoiceButtons is a high level interface based on Buttons and ViewerTools that provides these abstractions as primitives. Containers A client often wishes to build a tool that consists of several viewers associated with each other. Containers are a very simple viewer class that permit other viewers to be recursively embedded and scrolled. Routines are provided to constrain embedded viewers to be clipped to the edges of the parent container. Cursors The Cursors interface exports a set of bitmaps suitable for display in the hardware cursor as well as primitives enabling a client to create new cursor bitmaps. The cursor bitmaps have a logical hot spot associated with them that defines the single point in the cursor where the user is pointing; e.g. for a bullseye cursor the hot spot is in the center whereas the text pointer cursor has its hot spot at the upper left. DeskTops A desktop is a configuration of viewers as they are layed out on a screen. DeskTops.Create["name"] will create a new desktop named "name" if none exists. If one of that name exists, it will be moved onto the screen. DeskTops.FlyTo[desktop] will cause the contents of the screen to be saved in the current desktop, to be replaced by the viewers and icons in the new desktop. EndOps A boot file client may register a procedure which will be called synchronously after the Cedar boot sequence is completed, but before user input is enabled and any automatic checkpoint made. HourGlass This interface allows a client to display an hourglass cursor with flowing sand and is used during Cedar booting. IconManager This is a private interface that governs the behavior of icons in response to selection and user type in. Icons The Icons interface exports a set of bitmap pictures used to display iconic viewers. A client may create new icons from either a bitmap or a procedure that makes use of Cedar Graphics to display the image. A client may specify a rectangle within an icon that will be labelled with the name of the particular iconic viewer by Viewers. ImplErrors A rudimentary debugger and signal catcher is exported by ImplErrors that gives the user the choice of rejecting, proceeding, or saving edits and rolling back when an error occurs. General use is not recommended. InputFocus InputFocus controls the destination of mouse and keyboard actions as well as the interpretation of these actions through TIP tables. A viewer class implementor would use this interface to acquire type-in or to inquire to which viewer type-in was currently directed. Labels Labels implements a class of viewers that display a simple text message in a single font. The label may not be selected or edited, but has the advantage over Tioga text viewers of very low data structure overhead. ViewerInputQueue Some applications require a way to serialize user input from buttons, menus, and type-in without tying up the notifier process. ViewerInputQueue permits the application to enqueue and dequeue operations. Menus Menus are a horizontal sequence of text labels, each with an associated procedure called when the user clicks at the menu item. This interface provides low level routines for creating and modifying menus, but not for associating them with a particular viewer or redisplaying a viewer after its menu has changed. MenusPrivate A private interface enabling the window manager to invoke a menu item. MessageWindow The message window is a one-line display area at the top of the black and white display. Clients may use this interface to display prompts or other information to the user. Rules Rules are a simple viewer class that display as a rectangle of a single color on the display. They are useful for creating line graphics when designing the layout of tools. TypeScript This is a low-level interface that captures sequential type-in from the user. Clients are advised to use the IO streams interface instead. VBootOps A client may boot a particular volume or file with this interface. VFonts VFonts maps a font name into the actual representation, with options to synthesize bold and italic as well as to substitute for fonts that are not available. ViewerAdjust A private interface used to control user adjustment of viewer or column size and position. ViewerBLT A viewer class implementor may move displayed bits from one part of a viewer to another, with clipping to the viewer boundaries and almost the performance of the hardware BitBlt instruction. [This interface is not currently supported and should not be used]. ViewerEvents ViewerEvents permits client procedures to be registered that will be called when certain events take place with respect to a viewer, such as save, new edits, open or close, move to a new column, and changes in the input focus. ViewerMenus A private interface containing most of the menu items found in the viewer caption menu. ViewerClasses ViewerClasses is an important interface that defines the viewer class and instance data structures, as well as many of the basic Viewers primitive types. ViewerLocks This interface provides read and write locking of viewers, columns and the entire viewer tree. [This interface is not currently supported and should not be used]. ViewerOps ViewerOps is an important interface defining standard operations on viewers. Routines that manipulate viewer size and position, register new viewer classes, create new viewers, as well as most paint and input notifications live here. ViewerSpecs Screen layout constants are defined in the ViewerSpecs interface. ViewersStallNotifier BugBane uses this private interface to notify Viewers when a process has been suspended, pending handling of a breakpoint of an uncaught signal. ViewerTools ViewerTools provides low level access to the contents of Tioga text viewers and permits clients to control selection and editing within them. See the ChoiceButtons interface for creating text prompt fields and other higher level user interface abstractions. ViewersSnapshot Client invoked Checkpoint and Rollback routines live here. VirtualDesktops This interface is being replaced by "DeskTops". WindowManager WindowManager contains a miscellaneous collection of routines, the most important being the ability to enable an additional column of viewers on a color display. WindowManagerPrivate This is a private interface containing the current viewer tree root and some input notification routines. Ê ½body˜ImemoHeadšœ Ðbx˜#L˜.L˜2L˜8Ilogo˜L˜7L˜MItitle˜head˜ KšœÝ˜Ý˜K˜ÈKšœOÏiœ™žœÃ˜µK˜*—˜K˜žK˜ÔK˜£Kšœnžœ'žœœ˜½KšœˆÏkœ†Ÿœ2˜ÊIdisplayšœO˜OI continuationšœC˜CK˜‹—˜$KšœÜ˜ÜKšœù˜ùšœá˜áKšœž!œ“˜·KšœžGœè˜²KšœžSœó˜É—KšœùŸœ²˜®K˜õKšœŸœŸœŸœ£˜ÊKšœMŸœŸœå˜Ç—˜K˜Ž˜K˜•K˜ÏKšœƒžœÌ˜Õ—˜KšœÖžœ'˜€KšœÀ˜ÀKšœBžœÿ˜ÇKšœ-žEœë˜Ý—˜Kšœ²˜²KšœÂžœ2˜ûKšœ ˜ ˜ãPšžœë˜ûPšžœ‰˜Pšžœ˜¦Pšžœü˜PšžœÜ˜ô—K˜——˜K˜éK˜ºK˜äK˜í—˜6K˜ƒKšœ‰ŸœŸœ˜¥—˜Kšœ¡Ÿœ9˜ÞKšœiŸœ˜p—˜˜K˜é——˜K˜ÐK˜¶Kšœ”˜”K˜ÆKšœ ŸœŽ˜ KšœŸœ¥˜¿——O˜˜ Kšœ€˜€—˜ K˜ÈKšœOžœ™žœÃ˜µKšœ¶žœ=žœM˜Ê—˜ šœ˜KšœÄ˜Äšœýž œž œžœJ˜…K˜ï—Kš œTžœOžœ žœžœŸ˜ôKšœpžœž˜”—˜Kšœž˜žK˜´Kšœš˜šK˜éK˜ä—˜KšœižœŒžœD˜ÇKšœLŸœÃ˜’Kšœ±žœï˜°——˜Iblockšœ6žœ·˜ò šœ^˜^ šœ%Ÿœ˜)RšœK˜K— šœŸœ˜#Ršœ£˜£— šœ!Ÿœ˜%Ršœï˜ï— šœ%Ÿœ˜)Ršœ¼˜¼— šœ#Ÿœ˜'Ršœì˜ì— šœŸœ˜!RšœÆ˜Æ— šœ#Ÿœ˜'Ršœ.ž œ—˜Ð— šœŸœ˜!Rš œsžœžœAžœÑŸœŸœ˜©— šœŸœ˜!Ršœ’žœD˜Ø— šœ#Ÿœ˜'RšœË˜Ë— šœŸœ˜Ršœz˜z— šœ"˜"Ršœ¡žœÁžœÁ˜¯— šœ)˜)Ršœ¼˜¼— šœŸœŸœ˜Ršœ®˜®—— šœ‚˜‚ šœŸœ˜#Ršœ˜— šœŸœ˜#RšœŸ˜Ÿ— šœ%Ÿœ˜)Ršœù˜ù— šœ*˜*RšœßžœP˜²— šœ˜Ršœ˜— šœŸœŸœ˜!RšœŠŸœ»˜È——Ršœ…˜…Ršœþ˜þR˜s—˜K˜ºK˜¦K˜GKšœŸ˜K˜wK˜¡KšœŸœ˜KšœŸœ˜KšœRžœû˜ÓKšœ3˜3šœŸœŸœŸ˜K˜ã—šœ ŸœŸ˜K˜Œ—šœ ŸœŸ˜K˜Û—šœŸœŸ˜K˜“—šœŸ˜ K˜Š—šœŸ˜K˜¿—šœ˜Kšœ¤žœïžœƒ˜¢—šœ#˜#K˜Æ—šœ ŸœŸ˜K˜†—šœŸœŸ˜Kšœžœ”˜®—šœ ŸœŸ˜KšœLž œIž œC˜ì—šœ ŸœŸ˜KšœdžœFžœ@˜ø—šœ ŸœŸ˜Kšœ¤˜¤—šœ Ÿœ˜KšœÜ˜Ü—šœŸ˜"K˜—šœ Ÿ˜#K˜F—šœŸ˜!K˜,—šœŸ˜K˜Ù—K˜šœŸœ˜#Kšœ;žœžœ‘˜ã—šœ ŸœŸ˜K˜‘—šœ ŸœŸ˜Kšœd˜d—šœ ŸœŸ˜K˜Š—šœŸœŸ˜Kšœ!žœ¥žœ˜Ï—šœŸœŸ˜K˜s—šœŸœŸ˜Kšœ;žœz˜¼—šœŸœŸ˜Kšœžœkžœ?˜¼—šœŸ˜ K˜Ò—šœ Ÿœ˜K˜~——˜K˜l˜K˜ó—˜ K˜»—šÏb˜K˜ç—˜K˜Á—˜K˜Â——˜K˜øK˜×K˜¤K˜÷ —˜K˜—˜Kšœú˜úKšœ¡˜¡K˜÷K˜ÌK˜ÕK˜ª—˜K˜—˜K˜—˜K˜—˜ K˜—˜˜K˜‚—˜K˜½—˜ K˜´—˜ K˜¹—˜KšœÄžœÚ˜¦—˜Kšœú˜ú—š ˜K˜¾—˜ K˜q—˜ K˜i—˜K˜Ï—˜ K˜Ô—˜ K˜Š—˜K˜Ö—˜K˜Ì—˜K˜¸—˜ K˜F—˜ K˜­—˜K˜­—˜ K˜‹—˜K˜B—˜K˜—˜ K˜Z—˜ K˜ƒ—˜ K˜â—˜ K˜W—˜ K˜™—˜ K˜£—˜ K˜ê—˜ K˜A—˜K˜—˜ K˜‚—˜K˜:—˜K˜/—˜ K˜¡—˜K˜i———…—š ]