A Graphical User Interface for LUCY

by

UChin Kim

Submitted to the Department of Electrical Engineering and Computer Science in partial fulfillment of the requirements for the degree of

Bachelor of Science in Computer Science and Engineering

at the

MASSACHUSETTS INSTITUTE OF TECHNOLOGY

June 1991

\begin{matrix}c \\ ◯ \end{matrix} UChin Kim, MCMXCI. All rights reserved.

The author hereby grants to MIT permission to reproduce and to distribute copies of this thesis document in whole or in part.

Author . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Department of Electrical Engineering and Computer Science May 20, 1990

Certified by. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Bruce R. Lewis Analyst Programmer II

Thesis Supervisor

Accepted by . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Leonard A. Gould

Chairman, Departmental Committee on Graduate Students

by

UChin Kim

Submitted to the Department of Electrical Engineering and Computer Science

on May 20, 1990, in partial fulfillment of the requirements for the degree of Bachelor of Science in Computer Science and Engineering

Abstract

This paper details the design and implementation of a graphical interface for the LUCY program. This interface consists of the browser, editor, help system, and the error handling system. The browser lets users read about topics of interest to them. The editor allows users to ask potentially sensitive questions. The help system provides online documentation on the LUCY program and the error handling system provides diagnostic information that maintains the element of user control. Suggestions for improvements and extensions to the interface are also discussed.

by

UChin Kim

Submitted to the Department of Electrical Engineering and Computer Science

on May 20, 1990, in partial fulfillment of the requirements for the degree of Bachelor of Science in Computer Science and Engineering

Abstract

This paper details the design and implementation of a graphical interface for the LUCY program. This interface consists of the browser, editor, help system, and the error handling system. The browser lets users read about topics of interest to them. The editor allows users to ask potentially sensitive questions. The help system provides online documentation on the LUCY program and the error handling system provides diagnostic information that maintains the element of user control. Suggestions for improvements and extensions to the interface are also discussed.

Acknowledgments

I would like to thank my parents and my sisters for having supported me through my four years at MIT.

I would also like to thank my supervisor, Bruce Lewis, for his help and support in writing this thesis.

This research was supported by Project Athena.

Contents

1 Introduction 1

2 Design 2 2.1 Browser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 2.2 Editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2.3 Help System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 2.4 Error Handling System . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.5 Privacy Maintenance . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

3 Implementation 7 3.1 Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 3.2 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 3.3 Implementation Notes . . . . . . . . . . . . . . . . . . . . . . . . . . 9

3.3.1 Builder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 3.3.2 HES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 3.3.3 Constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

4 Testing and Evaluation 11

5 Analysis 14 5.1 Design Changes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 5.2 Observations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

6 Conclusion 16 6.1 Constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 6.2 Future Improvements . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

A Sample Help File 19

B alt.personals memo 21

C Code Listings 23 C.1 Header Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 C.2 Modified Builder Code . . . . . . . . . . . . . . . . . . . . . . . . . . 25 C.3 Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 C.4 Viewbrowser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 C.5 Help . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 C.6 Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103

References 105

1 Introduction

LUCY is an Ann Landers type program that allows people in the MIT community to ask questions on sensitive matters such as harassment and discrimination. Users can ask questions and also browse through a selected list of public questions and answers. The answers are provided by a person or persons whom we designate as Lucy.

The importance of LUCY is in that it fills an empty niche in the realm of counseling. While psychiatrists, social workers, advisors, and friends all play their part in helping people to solve their problems, there are some problems that are either too intimate, too embarrassing, or too revealing for many people to talk about. LUCY is designed to fill this role. In providing anonymity to the users, LUCY gives them the freedom to seek out help regarding problems that they had never dared to speak with anyone about.

Also, LUCY is highly available. Users can ask LUCY questions or browse through the LUCY browser at any time of the day, and at their convenience. This is in contrast to the people mentioned above, who have other time committments and schedules. Plus, the fact that LUCY is free to the MIT community adds to LUCY’s availability.

LUCY was originally written by Bruce Lewis during the month of December, 1990, using a text-based interface. It was released informally to the user community, which complained that it was clumsy and difficult to use. As a result of this user feedback, it was decided that the design and implementation of a graphical user interface for LUCY was a necessary step in LUCY’s development.

This paper describes just such an extension to the LUCY system. Specifically, it provides a graphical user interface to LUCY. This graphical user interface was built using Motif, X Windows, the Builder Xcessory from ICS (Integrated Computer Solutions, Inc.), and the C programming language.

The rest of this paper deals with the design, implementation, and testing of the LUCY graphical interface. Aspects of the graphical interface that were designed included the browser, editor, help system, and error handling system. Also, special consideration had to be given for the maintenance of privacy. The underlying strategy for the implementation involved an incremental approach to coding which facilitated debugging. For the testing, besides ensuring that the program functioned correctly, we also needed ways to evaluate the usefulness and quality of the resulting design and implementation.

2 Design

The design of the LUCY graphical interface involved designing the browser, editor, help system, error handling system, as well as dealing with privacy considerations. These design modules are interrelated. For example, the design of the browser and the editor was influenced by attempts to preserve privacy. And the design of the browser was strongly influenced by the editor design, and vice versa.

2.1 Browser

A design goal for the browser was to maintain a simple interface. The browser needed to: display transactions, display the contents of a transaction, allow transactions selects, and display transactions.

A split window approach in the style of Xmh seemed to be a feasible design. The top window displays a list of transactions, and the bottom window displays the contents of the selected transactions. When the user selects a transaction in the top window via a pointer, the contents of the transaction are automatically displayed in the bottom window.

When the user starts the LUCY program, we start out in browser mode. The program displays the list of subject transactions in top window and the contents of one subject transaction in the bottom window. When the user selects a transaction by clicking on it with the mouse, LUCY displays the contents of the transaction in the bottom window. The user can use scrollbars to scan the list of subject headings.

An advantage of this design is that it is similar to Xmh, which users in the MIT community would likely be familiar with. The similarity between mail abstraction and the meetings abstraction are great enough that a user who is familiar with the mail abstraction provided in Xmh will be able to use the LUCY interface without problems.

Another way to implement the LUCY browser interface would have been to create a new window every time the user selected a subject transaction. But creating too many windows would have cluttered the display. Also, the user has to either place each of new windows using the mouse pointer, or the LUCY interface has to do so.

Still another way to implement the browser would have been to have two physically separate windows, displaying the list of transactions in one window and the contents of a transaction in another. But then the relationship between the transactions list and the contents of the particular transaction is lost. It is not clear to the user that the two windows are tied to the same function.

When the browser displays a list of transactions from the public browser, it should be able to display them in both forward and backward chronology. Forward chronology means that the subjects are displayed in the order that they were added to the meeting. Backward chronology means that the subjects are displayed with the most recent subject displayed first and the oldest subject displayed last, with appropriate ordering in between.

Users who are new to the browser may want to read all of the subjects. Forward chronological order is just as good as any for this purpose. but users who have read most of the subjects in the browser will be most interested in the subjects that have been recently added to the browser—hence, the usefulness of backward chronological order.

A possible extension to the browser involves keeping track of a list of meetings in a special file containing a list of available meetings and a corresponding filename for each meeting. Upon entering the browser, instead of displaying the available subjects of the single existing meeting, we display the list of meetings available. Only when the user selects a meeting, do we display the transactions available for the meeting. Thus, the user can selectively limit his or her browsing to the topics of interest and ignore other topics.

The advantage of this approach is that we can group questions according to subjects and place them in separate meeting files. For example, we can have one file for harassment, and a different file for relationships, and so on. This way, the user can visit only the subjects that he or she is interested in. Also the user doesn’t have to page through a lot of unrelated questions to find useful ones.

2.2 Editor

The editor also uses a split screen. The bottom screen contains the editor and the top screen is unused. In fact, the editor and the browser use the same split screen. The reason for this is that we do not want it to be readily apparent whether the user is viewing the browser or entering a question. People will be able to tell the difference if they look long enough, though.

When the user selects ‘ask question’ from the menu, LUCY enters the editor mode. After the user has typed in the question, the user needs to select ‘send question’ from the menu. This pops up three dialog windows, in sequence, to get relevant information, such as whether the user wants a personal reply, and whether the user wants the question posted in the public browser.

It may seem cumbersome to require the user to select ‘send question’ after typing the question into the editor. One reason for this feature is that it wasn’t clear that there was a simple way to exit from the editor. We don’t want the user to have to type in Control-D, for example, or type in a dot on a line by itself, because it’s awkward and not very intuitive. If we provided a separate pushbutton on the screen labelled ‘done’, we would be making the editor and browser look different from each other.

Also, at some point, we have to display dialog windows to gather necessary information from the user. But the longer we have the dialog windows up, the more obvious it will be to a passer-by that the user is asking a question. Having the user select ‘send question’ from the menu to invoke the dialog windows, keeps the dialog windows on the screen for a minimal length of time.

A way to make the ‘send question’ selection clearer to the user is to display a prompt message to the user to select ‘send question’ after typing in the question.

This prompt message will be displayed when the user first selects the ‘ask question’ selection, and will disappear the moment the user starts editing the question, in the spirit of the Emacs notification messages.

2.3 Help System

Requirements for the help system was that it be highly available and that it should provide help on whatever topics the user might need.

The design of the help system is very similar to the design of the browser. The help window is a split screen, in which the top window has a list of help topics, the bottom window displays the contents of the selected help topic. When the user selects a help topic in the top window, the help system displays the relevant help message in the bottom window.

The similarity in design of the browser and the help system suggests that knowing how to use one will help in using the other, since the interface is essentially the same.

Currently, the help system is implemented as a list of ‘windows’, each of which has a body of text associated with it. This list is easily editable using a text editor. And hence it is easy to correct, modify, and extend. (See Appendix A for a sample help file.)

A future extension might use the meetings abstraction to provide help. We would have a meeting containing transactions for each help topic. This provides no particular advantages, except that the design becomes a bit cleaner in that it relies on a smaller base of abstractions.

When the user selects ‘get help’ from the menu, if a help window doesn’t exist, LUCY pops up an independent help window. The user selects the help topic in top window and the related text is displayed in the bottom window. Note that once the help window is on the screen, it can always be accessed. The user doesn’t need to exit the help window to return to the browser or editor, since they run relatively independently of each other.

2.4 Error Handling System

Ideally, an error handler should describe what went wrong, why it went wrong, and what can be done about it (See Section 4). The error handling system will manifest itself in the form of error dialog boxes. It will display text messages relevant to the particular source of error. When an error occurs, the user is notified via an error dialog box. Instead of disappearing on its own, the error dialog box will stay up until the user intentionally cancels it. We want to give the user every chance to see the message.

2.5 Privacy Maintenance

We need to maintain two kinds of privacy: screen privacy, and user privacy. With screen privacy, we don’t want people walking by to read a question that a user might be typing in, because there is a good chance that the question is in regard to a highly sensitive issue. People who choose to ask questions of LUCY are choosing not to pose these same questions to people in their support networks. Why? Because the questions may involve subjects such as abuse, harassment, or discrimination. Or the questions may reveal too much about the person asking the question.

A partial solution is to make the browser and the editor look the same, so people in nearby workstations are less likely to pay attention to someone running the LUCY program. A passing observer would have to look carefully to see that a person was entering a question into LUCY instead of looking through the browser. The fact that the vast majority of workstations are in public clusters makes screen privacy hard to obtain. But it is very important for users to be able to ask questions freely and openly in public clusters without having to hide in a remote corner of the cluster.

In terms of user privacy, ideally, we want LUCY to be able to respond personally to the user without knowing the user’s username. This is possible via the alt.personals server (See Appendix B). A user can send mail anonymously to the alt.personals server and still be able to get a personal reply. The alt.personals server is able to provide anonymity by maintaining a database of users and creating aliases for each user. For example, if the user ukim sent a message via alt.personals, the alt.personals server would strip the username from the message and assign an alias username such as ak76, before forwarding the message.

For now, we are not using the alt.personals server to provide anonymity primarily because of time limitations. For messages requiring personal replies, we are requiring that the user include his or her username. But this brings up problems of authentication. How do we know that a user is not sending us someone else’s username?

The importance of preserving user privacy is that we want the user to feel free to ask the kinds of questions that they have always wanted to ask but never dared to. Also, people are more willing to talk about revealing or highly charged topics if they know that they will not be held accountable for their actions. This may seem to be a mandate for irresponsibility, but the need for having an open and candid electronic forum overrides the issue of accountability.

3 Implementation

The basic programming strategy was to build and debug the fundamental structure of the program, and perform incremental coding and testing to build a fully functional program.

The Builder Xcessory (See [2].) was used to create a basic interface for LUCY. The interface code that the Builder created had to be modified because of inherent limitations in the Builder program. Also, callbacks (See [9] for an explanation of callbacks.) had to be written and inserted into the interface code. (See Appendix C.3 for a listing of the callback routines that were added to the interface.) Finally, the LUCY interface was coalesced with the LUCY meetings code that Bruce Lewis had written.

3.1 Tools

Regarding the choice of software tools, Motif was chosen over other toolkits such as the Andrew toolkit, for several reasons. The most important reason was that the author was familiar with it. Another reason was that it had good, substantial documentation. Many books had been written about it, such as the one by Young [9]. And the OSF/Motif Programmer’s Reference [6] explicitly defined the functions in the Motif toolkit. And the many examples available in Young’s book [9] were important in understanding how these functions were used.

The choice of Motif dictated that the C language also be used. Also, in the course of designing the interface, a prototyping tool called the Builder Xcessory was released by ICS (Integrated Computer Solutions, Inc.). Builder was specifically designed to prototype Motif interfaces. It was determined that its use would speed up the initial coding of the interface, since it automatically generated interface code.

3.2 Code

The LUCY code is composed of five C files and some related include files. Three of C files were initially generated by the Builder and later revised by the author (See Appendices C.2, C.3). Two of the C files were written solely by the author (See Appendices C.4, C.5). A description of the files follows.

The file main-c.c contains the top level function main() which is responsible for creating the application shells and calling the appropriate creation routines. This file was modified to allow two of the shells to be popped up arbitrarily by the callbacks.

File creation-c.c contains the functions responsible for creating most of the widgets. Whenever changes needed to be made to the interface, this file had to be modified by hand to incorporate those changes. Also, most of the widgets that had originally been declared within the functions in creation-c.c were made globally available. This was necessary, because many of the callback routines needed to access these widgets.

The third file callbacks-c.c, as generated by Builder, consisted of the empty callback function declarations. All of this code was written by the author. These callback functions handle user actions, where user actions can be selecting a menu item, entering text, or selecting toggle options.

The file viewbrowser.c contains code to abstract the meetings interface into two routines, Get Subjects() and Get Transaction(). Get Subjects gets a list of subjects from the browser meeting, and Get Transaction retrieves the contents of a subject. These routines rely on special library routines that Bruce Lewis (a programmer at Athena) wrote to hide the details of the meetings interface from the LUCY system.

Finally, the file hes.c contains code to manipulate help screens. Help text is kept in a text file in a simple format that allows screens to be added, deleted or changed without having to perform any compilations. (See Appendix B for a listing of the sample help file.)

3.3 Implementation Notes

3.3.1 Builder

The Builder program allowed the LUCY interface to be created quickly, but it had some severe limitations. Changing the interface destroys any code that may have been added by hand, thus discouraging the use of the program for any serious work. At one point, after having added substantial code to the Builder-generated code, it was necessary to change the interface in a substantial way. After modifying the interface, the author had to obtain listings of the two versions of code, the first version containing the old interface and the substantial code modifications, and the second version containing the new interface with none of the previous code modifications. These two listings had to be merged by hand, because there was no program that could perform the kind of special merging that these listings required. Also, the Builder, by default, specifies its widget resources in the program code, instead of generating a special application defaults file. Although it is possible to change this default for each individual resource, it becomes quickly impractical to change this default for every resource involved. The use of an application defaults file allows users to customize Motif programs to their own particular needs. And it also allows programmers to modify widget parameters without having to undergo a length compile process. Even with these substantial drawbacks, using the Builder saved much time in coding the interface.

3.3.2 HES

The help editing system, hes.c, allows help screens to be modified independently of the program involved. HES parses the specified help file to obtain help screens. A help screen is composed of the name of the screen and the text associated with that name. In the sample help file, the help screen names are enclosed within pipe characters (‘|’) and the help screen text follows the help screen name. In the sample help file, test.help (See Appendix B), the help screen text for a given help name continues until the next help screen name is encountered or the special string ‘|EOF|’ (End Of File) is encountered.

HES allows help screens to be loaded from a text file, and it also provides functions to retrieve the contents of a particular help screen. LUCY uses HES to load help information. Each help screen corresponds to a help topic in LUCY’s onscreen help. If LUCY’s help file is modified in any way (i.e. help screens are added, deleted, or changed.), these changes are reflected in the next invocation of the LUCY program.

A person who had edit access to the LUCY help file could update the onscreen help documentation quickly and easily. He or she would need to know nothing about compiling programs since no re-compilation would be involved. As a result, maintaining and updating the LUCY help messages becomes a trivial task.

3.3.3 Constraints

When the viewbrowser (See Appendix C.4) reads in the subjects in the browser meeting, there is an arbitrarily defined limit of 4096 subjects. If there are more than 4096 subjects, viewbrowser will only retrieve the first 4096. Also, the contents of a transaction is limited to 16384 characters in length (roughly 8 pages of double spaced text). This, again, was an arbitrarily defined limit. These limits made it possible to write the program without having to deal with dynamic allocation.

4 Testing and Evaluation

The use of incremental programming (See [8] for a discussing of incremental programming.) allows code to be written quickly and efficiently. The basic idea behind this approach is to always maintain a working base program from which to add on additional sections of code. At any point in the process, we add only a small section of untested code to the working base. If a bug is discovered there is a high probability that it occurred in the section of the code that was added most recently. Hence, there is a localization of errors, which allows for easier debugging, since discovering the source of error is can otherwise be an arduous debugging task.

No design process would be complete without a way to verify and validate the integrity of the design. Ideally, we want to test out the interface on a sampling of users. We could get their feedback on what is clear and what is confusing. Also, we could get suggestions from them as to how it could be improved and then rewrite the interface with these suggestions and comments in mind.

Although we eventually want to test the resulting design on a broad sampling of users, this is sometimes not practical, because of time constraints. It would be useful to be able to evaluate a user interface design more abstractly. And to do this, we need to determine what constitutes a good user interface.

With regard to design, Norman [5, page 188] writes that:

Design should:

• Make it easy to determine what actions are possible at any moment (make use of constraints).

• Make things visible, including the conceptual model of the system, the alternative actions, and the result of actions.

• Make it easy to evaluate the current state of the system.

• Follow natural mappings between intentions and the required actions; between actions and the resulting effect; and between the information that is visible and the interpretation of the system state.

With regard to the LUCY GUI design, the menu system makes it obvious what is possible (bullet 1). Also, it would be good to gray out undoable actions, making it apparent to the user that certain actions are not possible. When LUCY is in editor mode, we place a blinking cursor in the edit region to show the user that we are in edit mode, thus making use of a natural editing constraint.

Some features of the LUCY GUI are not obvious. For instance, it’s not obvious that, in browser mode, the user should click on the browser selection to view it. It’s also not obvious that Emacs commands are provided in the text editor. And for that matter, Emacs commands are not very intuitive. Fortunately, the arrow keys and delete key work as expected, providing minimal functionality for non-Emacs users.

Most actions in the LUCY GUI provide automatic feedback (bullet 2). The action ‘Send Question’, pops up multiple dialog windows. ‘Help’ pops up a help window if it does not currently exist. Selecting a transaction with the mouse displays the contents of the selected transaction in the lower window of the LUCY GUI output. And, of course, text editing provides plenty of feedback to the user.

The LUCY GUI has two states that look virtually identical. This is in conflict with Norman’s suggestion to make distinct states look different from each other (bullet 3). The reason why the two major modes, the browser and editor modes look virtually identical is because we want privacy on the screen level. This privacy was more important than the guideline Norman stipulates regarding states—hence the deviation.

The use of status messages helps to promote the ease of use that Norman advocates, while maintaining the similarity between editor and browser states. When the user switches from the browser to the editor, we display a status message telling the user how to use the editor. The status message disappears when the user starts to edit. Note that we do not need a status message for the switch from editor to browser; we can simply display the contents of the most recently selected transaction in the bottom output window.

Norman also advocates making actions intuitive (bullet 4). Users who have used Emacs and/or Xmh will probably find the interface intuitive. After all, much of intuition is based on experience. The LUCY GUI assumes that the user is familiar with menus and mice, as well as the concept of e-mail. There is a tradeoff between designing for a user who knows little about the system and designing for a user who knows a great deal. It is easier to design for a user who knows much about the system, because a lot of things that have to be carefully explained to the novice user may be omitted in dealing with an expert user. In the case of the LUCY GUI, we are designing for the novice user.

In regard to what constitutes a good interface, Norman [5, page 200] writes that a good interface: “[allows] the user to recover from errors, to know what was done and what happened, and to reverse any unwanted outcome.”

The LUCY GUI error handling system tries to let the user know what has happened, why it happened, and how to fix or deal with the error. This follows Norman [5] very closely. One exception to Norman guideline occurs because sometimes it may not be possible to reverse an unwanted outcome. For example, if a server is down, preventing access to the LUCY public browser, it is usually not in the user’s power to bring it back up.

Norman [5, page 200] suggestion that one should “Design explorable systems.” is good because it encourages the user to try new things more willingly. When the user knows that his or her mistakes are not fatal, they will be more willing to try different approaches to obtain a desired result. The comprehensive error handling in the LUCY GUI encourages exploration by giving the user a semblance of control over the system.

Joseph Dumas [1, page 50] lists, from highest to lowest priority, a similar set of guidelines:

seven principles of interface design:

• Put the user in control.

• Address the user’s level of skill and experience.

• Be consistent.

• Protect the user from the inner workings of the hardware and software.

• Provide online documentation.

• Minimize the burden on the user’s memory.

• Follow the principles of good graphics design.

The last item in Dumas’ guidelines is worthy of note because it emphasizes aestheticism. By “good graphics design”, Dumas means to make display, especially text, look aesthetically pleasing. This is an important consider. Contrast a typewritten page with a proportionally printed laser print out. Proportionally printed text is much easier to read, as well as being easier on the eyes.

5 Analysis

5.1 Design Changes

The implementation of the error system turned out to be simpler than it was originally planned to be. Originally, it was to use the HES (help editing system, See Appendix C.5) to display error screens. This turned out to be much more than was actually required for the program, because there are not very many errors that can occur. Also, the notion of explaining what happened, why it happened, and what to do about it is good, but is, again, more than was actually required for LUCY.

Originally, LUCY was designed to have two modes, a browser mode and an ask question mode. Due to the use of callbacks, it turned out to be unnecessary to have modes. LUCY can tell from the context of the operation whether it is possible or not. And only possible operations are allowed. For example, if the user tries to send a question, but has not yet entered a question, LUCY displays an error message telling the user what he or she has done wrong, as well as what to do next.

5.2 Observations

In the course of doing incremental programming and testing, due to the use of informal testing, the SendQuestion feature had not been tested for the previous ten edit, compile, and test cycles. When it was finally tested, it had the unfortunate tendency of terminating the program with a ‘Segmentation Error’. It was not immediately obvious when this bug had been added to the code, since it could have been added in any of the previous ten edits. A program was needed to simulate user actions and record the results of the actions in a useful way, i.e. a test driver for Motif programs. For a moderately intricate interface, it is too tedious and error-prone to test each menu item selection and step through all of the available options.

Eventually, the author was able to localize and fix this bug, but the use of regression testing would have detected this bug when it had first cropped up. The use of fprintf statements that were sprinkled throughout the code was a useful diagnostic in locating the source of the bug. Regular printf statements would not have helped in localizing the bug, because UNIX buffers standard output. A message that had been printf’ed before the program crashed might never be seen by the programmer because of this buffering. Fortunately, UNIX does not buffer standard error—hence, the use of fprintf’s to localize bugs.

Sometimes the best way to deal with errors is to work around them. In viewbrowser.c (See Appendix C.4), Get Transaction() calls the discuss routine (See [7] and [10]) dsc get trn() which is supposed to retrieve the text of a transaction. dsc get trn requires the specification of a tfile (See [7]) which is a special type of file. The documentation available [7] lacked examples that would have clarified both dsc get trn and tfiles.

The author was eventually able to use both dsc get trn and tfiles by taking a circuitous route. The process by which this occurs is as follows. The program associates a disk file with standard output. Then it converts standard output into a tfile using the command unix tfile(). The function dsc get trn() gets the contents of the transaction and outputs it to standard output, which has been re-routed to the disk file. This disk file is closed and then opened again. We then read the contents of this disk file to get the text of the transaction. There is a command to directly read a tfile, called tread, but the author could not understand it well enough to use it.

6 Conclusion

6.1 Constraints

Some factors that influenced the design of the interface included: time limitations, privacy considerations, and the existing design of LUCY.

Because of time limitations, the scope of the problem was reduced from providing a dual interface to providing the public interface. The public interface deals with users who ask questions and browse through the answers, whereas the dual interface would also have included the private interface. This private interface would have provided a GUI to the person or persons representing LUCY, and would have mediated such tasks as: answering questions, posting questions and accompanying answers in the public browser, editing questions, archiving questions, and others. While only a few people would interact with the private interface, many people were expected to interact with the public interface—hence, the selection of the public interface for design and implementation.

Regarding privacy considerations, there were two different types of privacy to consider (See [4]). When a user sends a question to LUCY, the user may wish to remain anonymous, especially if the question is in regard to a sensitive matter. Yet the user might want a private reply. The problem is how to have LUCY respond to a user without knowing the identity of the user. The other type of privacy to consider is that, since most workstations are in public clusters, there is a high probability that people walking past terminals will notice that a particular user is running the LUCY program. This can be potentially embarrassing for the user, especially if the user is in the midst of asking a sensitive question.

In designing the graphical interface to LUCY, the existing design of the LUCY program itself had to be considered. In particular, the LUCY system is built on top of a meetings structure, which constrained the types of interactions that were possible. Had LUCY been based upon a different data structure, this would have constrained the types of interactions in other ways.

An example of a different data structure that might have been used is the hierarchical tree structure (as in the UNIX directory structure). While the meeting interface provides only two layers of hierarchy (the meeting name is the top layer and the meeting’s transactions form the bottom layer), the hierarchical tree structure would have provided many layers. Hierarchical structures allows information to be retrieved quickly and efficiently. In order notation, a hierarchical data structure allows information to be retrieved in O(log n) time, whereas the meetings structure allows information to be retrieved in O (n)time, where n is the number of elements in the particular data structure. This is the difference between quickly flipping to the appropriate subject topic in an encyclopedia versus starting from the beginning of the encyclopedia and skimming page after page until the right topic is found.

But there are advantages to using the meetings interface. One advantage is that it provides a solid software interface that has been thoroughly debugged. The similarity between the meetings interface and mail allows easy translation between the two. One can send a transaction by mail or send mail via transactions. Also, the meetings interface allows for ambiguous topics better than the hierarchical structure mentioned above. Whereas, in a hierarchical tree, a question may be filed under one of many nodes, there is no such confusion in the meetings interface. A question is either in the specified meeting or it isn’t.

6.2 Future Improvements

The following are a list of improvements and bugs to be fixed:

• Provide an adequate help file.

• The text window should stay the same size instead of resizing dynamically as the text is being displayed.

• When the user selects ‘Enter Question’, have the text cursor immediately appear on the screen. Currently, the user has to click on the mouse pointer.

• Entering a question: text editor needs more emacs bindings—the current ones are inadequate.

• LUCY help window’s iconified name should be changed from popuphelp to lucyhelp.

A Sample Help File

This is a file containing help messages.
|rotund|
ro.tund \ro--'t*nd, 'ro--.\ \ro--'t*n-d*t-e-\ \-'t*n-dle-, 'ro--.\
\ro--'t*n(d)-n*s, 'ro--.\ aj [L rotundus - more at ROUND] 1: marked by
roundness : ROUNDED 2: FULL, SONOROUS 3: PLUMP, CHUBBY - ro.tun.di.ty n -
ro.tund.ly av - ro.tund.ness n

|parsimonious|
Cross references:
1. stingy
par.si.mo.ni.ous \.pa:r-s*-'mo--ne--*s\ aj : excessively frugal : NIGGARDLY
- par.si.mo.ni.ous.ly av

|courage|
cour.age \'k*r-ij, 'k*-rij\ n [ME corage, fr. OF, fr. cuer heart, fr. L cor
- more at ]HEART : mental or moral strength to venture, persevere, and
withstand danger, fear, or difficulty SYN syn METTLE, SPIRIT, RESOLUTION,
TENACITY: COURAGE implies firmness of mind and will in the face of danger
or extreme difficulty; METTLE suggests an ingrained capacity for meeting
strain or difficulty with fortitude and resilience; SPIRIT also suggests a
quality of temperament enabling one to hold one's own or keep up one's

morale when opposed or threatened; RESOLUTION stresses firm determination
to achieve one's ends; TENACITY adds to RESOLUTION implications of stubborn
persistence and unwillingness to admit defeat

|love|
1. love \'l*v\ n [ME, fr. OE lufu; akin to OHG lupa love, OE le-of dear,
L]lube-re, libe-re to please 1a: affection based on admiration or
benevolence 1b: an assurance of love 2a: warm attachment, enthusiasm, or
devotion {~ of the sea} 2b: the object of such attachment or devotion 3a:
unselfish concern that freely accepts another in loyalty and seeks his good
: 3a1: the fatherly concern of God for man 3a2: brotherly concern for
others 3b: man's adoration of God 4a: the attraction based on sexual desire
: the affection and tenderness felt by lovers 4b: a god or personification
of love 4c: an amorous episode : LOVE AFFAIR 4d: the sexual embrace :
COPULATION 5: a beloved person : DARLING 6: a score of zero in tennis cap,
Christian Science  7: GOD
2. love vt 1: to hold dear : CHERISH 2a: to feel a lover's passion,
devotion, or tenderness for 2b: CARESS 3: to like or desire actively : take
pleasure in {loved to play the violin} 4: to thrive in {the rose ~s
sunlight} : to feel affection or experience desire

|gastronomy|
gas.tron.o.my \-me-\ n [F gastronomie, fr. Gk Gastronomia, title of a 4th
cent. B.C.] poem, fr. gastro- belly + -nomia -nomy 1: good eating or its
lore 2: culinary customs or style

|vulgarity|
vul.gar.i.ty \.v*l-'gar-*t-e-\ n 1: the quality or state of being vulgar 2:
   something vulgar

|EOF|
The end

B alt.personals memo

[0005] tytso@ATHENA.MIT.EDU  Lucy_development  12/11/90 15:26 (42 lines)
Subject: alt.personals software?
My apologies for not attending the Lucy design review; I forgot all
about it until after it was over that day.
I don't know if this is even within the requirements of the problem that
you are setting out to solve, but something which I think would be
really useful is the ability for someone to send a question
anonymously and yet get back a private email message.
A mechanism for doing this can be found in the software used in the
Usenet group alt.personals.  This is how it works:
1) When a user mail a question to the alt.personals server, it checks
the from field to see if the user has ever send mail to the alt.personal

server before.  If the user has not, the server assigns to that user an
alt.personal alias --- "akms@lucy.mit.edu", for example.  The mail is
then stripped of any identifying headers and is sent to the News
software to be posted to alt.personals.  This could easily be adapted
for Lucy by having the mail be sent directly to the list of Lucy
staffers.
2)  Anyone can now send mail to "akms@lucy.mit.edu" and the message will
be sent back to user who originally sent first message.  The lucy
personnel would thus be able to send a response back to the user without
knowing his or her identity.
There are of course, a couple of disadvantages to this method.  First of
all, this system is only as secure as the database on the mail server.
It may be possible to get a ruling from the privacy committee or
whatever that this database must be kept private; otherwise, we may have
a problem when someone from SPODSA(*) shows up at Athena's doorstep and
demands the identity of "akms@lucy.mit.edu".  Secondly, this method is
only as secure as email; all of the problems with dead letters staying
on workstations still apply.
The advantage is that this software is already written and has been
tested quite thoroughly.  I think the added functionality granted by
this software is very valuable --- I can think of several examples,
especially ones involving possible sexual harassment, where the person
asking the question might want to remain anonymous and the question and
responses would best be kept private.
--[0005]--
^L

discuss:

C Code Listings

C.1 Header Files

/* the following widgets are defined to be external, because I need to access some of them from within the callback routines. */

extern Widget lucyform; extern Widget lucymenuBar; extern Widget menuShell; extern Widget filepulldownMenu; extern Widget viewbrowserButton;

extern Widget quitButton; 10

extern Widget lucyButton; extern Widget fileButton; extern Widget menuShell1; extern Widget editpulldownMenu; extern Widget enterquestionButton; extern Widget sendquestionButton; extern Widget editButton; extern Widget menuShell2; extern Widget helppulldownMenu;

extern Widget gethelpButton; 20

extern Widget helpButton; extern Widget lucypanedWindow; extern Widget lucytopframe; extern Widget lucyscrolledWindow; extern Widget lucylist; extern Widget lucybottomframe; extern Widget lucyscrolledWindow2; extern Widget lucytext;

extern Widget  helpform;  30

extern Widget helpmenuBar; extern Widget menuShell3; extern Widget exitpulldownMenu; extern Widget exitpushButton; extern Widget exithelpButton; extern Widget helppanedWindow; extern Widget helptopframe; extern Widget helpscrolledWindow; extern Widget helplist;

extern Widget helpbottomframe; 40

extern Widget helpscrolledWindow2; extern Widget helptext;

extern Widget DialogbulletinBoard; extern Widget dialogbulletinBoard; extern Widget subjectlabel; extern Widget publishQuestionlabel; extern Widget personalReplylabel; extern Widget sendReplytolabel;

extern Widget subjecttext; 50

extern Widget persReplytext; extern Widget publishradioBox; extern Widget publishYestoggle; extern Widget publishNotoggle; extern Widget replyradioBox; extern Widget replyYestoggle; extern Widget replyNotoggle; extern Widget messageframe; extern Widget messageBox;

60

extern Widget Shell000; /* lucy main window */ extern Widget Shell001; /* help window */ extern Widget Shell002; /* dialog window */ /* lucy parameters */

#define BROWSER MAX 4096 /* max number of subjects in browser */ #define LINE SIZE 128 /* size of one subject line in browser */

#define QUESTION SIZE 16384 /* size of question text */ #define MAX HELP 64 /* maximum number of help topics we are going to

display in help window */

#define BX APP NAME "lucy"  10

#define BX APP CLASS "Lucy" #define HELPNAME "lucyhelp"

#define HELPFILE "test.help" /* name of help file */

C.2 Modified Builder Code

/*

* Generated by the ICS builderXcessory (BX). * *

* Builder Xcessory 1.0.1. * */ /*

* REQUIRED MOTIF INCLUDE FILES

*/  10

#include <Xm/Xm.h> #include <X11/Shell.h> #include <Xm/DialogS.h> /*

* USER SUPPLIED INCLUDE FILES */

#include <stdio.h>

#include "widgets.h"  20

#include "params.h"

/* DEFINE SECTION */

/*

* SHELL CALLBACK PROCEDURE DECLARATIONS */

/* GLOBAL WIDGET VARIABLES */  30

Widget Shell000; Widget Lucyform; Widget Createlucyform(); Widget Shell001; Widget Shell002; Widget Helpform; Widget Createhelpform(); Widget DialogbulletinBoard; Widget CreatedialogbulletinBoard();

40

/* more global widget variables */ Widget lucyform; Widget lucymenuBar; Widget menuShell; Widget filepulldownMenu; Widget viewbrowserButton; Widget quitButton; Widget lucyButton;

Widget fileButton; 50

Widget menuShell1; Widget editpulldownMenu; Widget enterquestionButton; Widget sendquestionButton; Widget editButton; Widget menuShell2; Widget helppulldownMenu; Widget gethelpButton; Widget helpButton;

Widget lucypanedWindow; 60

Widget lucytopframe; Widget lucyscrolledWindow; Widget lucylist; Widget lucyscrolledWindow2; Widget lucybottomframe; Widget lucytext;

Widget helpform; Widget helpmenuBar;

Widget menuShell3;  70

Widget exitpulldownMenu; Widget exitpushButton; Widget exithelpButton; Widget helppanedWindow; Widget helptopframe; Widget helpscrolledWindow; Widget helplist; Widget helpbottomframe; Widget helpscrolledWindow2;

Widget helptext;  80

Widget dialogbulletinBoard; Widget subjectlabel; Widget publishQuestionlabel; Widget personalReplylabel; Widget sendReplytolabel; Widget subjecttext; Widget persReplytext; Widget publishradioBox;

Widget publishYestoggle;  90

Widget publishNotoggle; Widget replyradioBox; Widget replyYestoggle; Widget replyNotoggle; Widget messageframe; Widget messageBox;

/* MAIN PROGRAM */  100

main(argc, argv) int argc; char **argv; {

Display *display;

Arg args[256];

int argcnt;

XtAppContext context;

XtToolkitInitialize();  110

context = XtCreateApplicationContext();

display = XtOpenDisplay(context, 0, BX APP NAME, BX APP CLASS,

0, 0, &argc, argv); if(display == NULL) {

        XtWarning("cannot open display");

exit(1); } XmRegisterConverters();

argcnt = 0;  120

XtSetArg(args[argcnt], XmNx, 71); argcnt++; XtSetArg(args[argcnt], XmNy, 322); argcnt++; Shell000 = XtAppCreateShell( BX APP NAME, BX APP CLASS,

applicationShellWidgetClass, display, args, argcnt);

argcnt = 0; XtSetArg(args[argcnt], XmNx, 541); argcnt++; XtSetArg(args[argcnt], XmNy, 485); argcnt++; XtSetArg(args[argcnt], XmNwidth, 400); argcnt++;

XtSetArg(args[argcnt], XmNheight, 400); argcnt++;  130

XtSetArg(args[argcnt], XmNtitle, HELPNAME); argcnt++;

/* Shell001 = XtAppCreateShell( BX APP NAME, BX APP CLASS,

applicationShellWidgetClass, display, args, argcnt); */ Shell001 = XtCreatePopupShell("popuphelp", topLevelShellWidgetClass,

Shell000, args, argcnt);

argcnt = 0; XtSetArg(args[argcnt], XmNallowShellResize, False); argcnt++;

XtSetArg(args[argcnt], XmNx, 37); argcnt++;  140

XtSetArg(args[argcnt], XmNy, 212); argcnt++; /* Shell002 = XtAppCreateShell( BX APP NAME, BX APP CLASS,

applicationShellWidgetClass, display, args, argcnt); */

/* just realized that I don’t need to create an explicit shell */ /* Shell002 = XtCreatePopupShell(”dialog”, xmDialogShellWidgetClass,

Shell000, args, argcnt); */

Lucyform = Createlucyform(Shell000);  150

XtManageChild(Lucyform); Helpform = Createhelpform(Shell001); XtManageChild(Helpform);

DialogbulletinBoard = CreatedialogbulletinBoard(Shell000); /* XtManageChild(DialogbulletinBoard); */

XtRealizeWidget(Shell000);

/* XtRealizeWidget(Shell001); */  160

/* XtRealizeWidget(Shell002); */

XtAppMainLoop(context); }

/*

* Generated by the ICS builderXcessory (BX). * *

* Builder Xcessory 1.0.1. * */

/*

* REQUIRED MOTIF INCLUDE FILES  10

*/ #include <Xm/Xm.h> #include <X11/Shell.h> #include <Xm/ArrowB.h> #include <Xm/ArrowBG.h> #include <Xm/BulletinB.h> #include <Xm/CascadeB.h> #include <Xm/CascadeBG.h> #include <Xm/Command.h>

#include <Xm/CutPaste.h>  20

#include <Xm/DialogS.h> #include <Xm/DrawingA.h> #include <Xm/DrawnB.h> #include <Xm/FileSB.h> #include <Xm/Form.h> #include <Xm/Frame.h> #include <Xm/Label.h> #include <Xm/LabelG.h> #include <Xm/List.h>

#include <Xm/MainW.h>  30

#include <Xm/MenuShell.h> #include <Xm/MessageB.h> #include <Xm/PanedW.h> #include <Xm/PushB.h> #include <Xm/PushBG.h> #include <Xm/RowColumn.h> #include <Xm/Scale.h> #include <Xm/ScrollBar.h> #include <Xm/ScrolledW.h>

#include <Xm/SelectioB.h>  40

#include <Xm/SeparatoG.h> #include <Xm/Separator.h> #include <Xm/Text.h> #include <Xm/ToggleB.h> #include <Xm/ToggleBG.h>

/*

* USER SUPPLIED INCLUDE FILES */

50

#include <stdio.h>

#include "widgets.h"

/*

* SOME CONVENIENCE ROUTINES */ #ifndef IGNORE COLOR static unsigned long COLOR(w, name)

Widget w;  60

char *name; { XrmValue fromVal, toVal; unsigned long *pixel;

fromVal.size = sizeof(char*);

fromVal.addr = name;

XtConvert(w, XmRString, &fromVal, XmRPixel, &toVal);

pixel = (unsigned long*)toVal.addr;  70

if( pixel == NULL )

{ fromVal.addr = XtDefaultBackground; XtConvert(w, XmRString, &fromVal, XmRPixel, &toVal); pixel = (unsigned long*)toVal.addr;

}

return(*pixel); } #endif

#ifndef IGNORE FONT  80

static XmFontList FONT LIST(w, name) Widget w; char *name; { XrmValue fromVal, toVal; XmFontList *fontListPtr;

fromVal.size = sizeof(char*);

fromVal.addr = name;  90

XtConvert(w, XmRString, &fromVal, XmRFontList, &toVal);

fontListPtr = (XmFontList*)toVal.addr;

if(fontListPtr == NULL)

{ fromVal.addr = "fixed"; XtConvert(w, XmRString, &fromVal, XmRFontList, &toVal); fontListPtr = (XmFontList*)toVal.addr;

}

return(*fontListPtr);  100

}

#endif #ifndef IGNORE MENU POST static void MENU POST(p, m, e) Widget p; Widget m; XButtonEvent *e;

{  110

Arg args[2];

int argcnt;

int button;

argcnt = 0;

XtSetArg(args[argcnt], XmNwhichButton, &button);

argcnt++;

XtGetValues(m, args, argcnt);

if( e−>button != button) return;

XmMenuPosition(m, e);  120

XtManageChild(m); } #endif #ifndef IGNORE STRING TABLE #include <varargs.h>

static XmString* STRING TABLE(va alist) va dcl

{  130

va list ap; int count; XmString *array; int i;

va start(ap); count = va arg(ap, int); array = (XmString*)XtMalloc((count + 1) * sizeof(XmString*)); for(i = 0; i < count; i++ )

{  140

array[i] = XmStringCreateLtoR(va arg(ap, char*),

XmSTRING DEFAULT CHARSET);

} array[count] = (XmString)0; va end(ap); return(array);

}

static void

FREE STRING TABLE(table)  150

XmString *table; {

int i;

for( i = 0; table[i] != 0 ; i++) {

XmStringFree(table[i]); } XtFree(table);

}  160

#endif

/*

* CALLBACK PROCEDURE DECLARATIONS */ extern void EnterQuestionCB(); extern void Exit Callback(); extern void GetHelpCallback(); extern void QuitCallback();

extern void SendQuestionCB();  170

extern void ViewBrowserCallback(); extern void DialogCancelCallback(); extern void DialogOKCallback();

extern void HelpItemSelectCallback(); extern void BrowserSelectCallback();

extern void lucyCallback(); /* About Lucy... */

Widget  180

Createlucyform(parent) Widget parent; {

Arg args[512];

int argcnt;

Widget retval;

XmString xmstr[32];

XmString *xmstrTable[8];

XmRegisterConverters();  190

argcnt = 0;

XtSetArg(args[argcnt], XmNx, 0); argcnt++;

XtSetArg(args[argcnt], XmNy, 0); argcnt++;

XtSetArg(args[argcnt], XmNwidth, 450); argcnt++;

XtSetArg(args[argcnt], XmNheight, 412); argcnt++;

lucyform = XtCreateWidget("lucyform", xmFormWidgetClass, parent,

args,  200

argcnt);

retval = lucyform; argcnt = 0; XtSetArg(args[argcnt], XmNleftAttachment, XmATTACH FORM); argcnt++; XtSetArg(args[argcnt], XmNrightAttachment, XmATTACH FORM); argcnt++; XtSetArg(args[argcnt], XmNtopOffset, (int)0); argcnt++;

XtSetArg(args[argcnt], XmNleftOffset, (int)0); argcnt++;  210

XtSetArg(args[argcnt], XmNrightOffset, (int)0); argcnt++; XtSetArg(args[argcnt], XmNx, 0); argcnt++; XtSetArg(args[argcnt], XmNy, 0); argcnt++; XtSetArg(args[argcnt], XmNwidth, 450); argcnt++; XtSetArg(args[argcnt], XmNheight, 31); argcnt++; XtSetArg(args[argcnt], XmNrowColumnType, XmMENU BAR); argcnt++; lucymenuBar = XtCreateWidget("lucymenuBar",

xmRowColumnWidgetClass,

lucyform,

args,  220

argcnt);

XtManageChild(lucymenuBar);

argcnt = 0; XtSetArg(args[argcnt], XmNwidth, 1); argcnt++; XtSetArg(args[argcnt], XmNheight, 1); argcnt++; menuShell = XtCreatePopupShell("menuShell",

xmMenuShellWidgetClass,  230

lucymenuBar,

args, argcnt);

XtManageChild(menuShell);

argcnt = 0; XtSetArg(args[argcnt], XmNx, 0); argcnt++; XtSetArg(args[argcnt], XmNy, 0); argcnt++;

XtSetArg(args[argcnt], XmNwidth, 90); argcnt++;  240

XtSetArg(args[argcnt], XmNheight, 52); argcnt++; XtSetArg(args[argcnt], XmNrowColumnType, XmMENU PULLDOWN); argcnt++; filepulldownMenu = XtCreateWidget("filepulldownMenu",

xmRowColumnWidgetClass,

menuShell,

args, argcnt);

argcnt = 0;  250

XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(

             "View Browser",

XmSTRING DEFAULT CHARSET))); argcnt++;

XtSetArg(args[argcnt], XmNrecomputeSize, False); argcnt++; viewbrowserButton = XtCreateWidget("viewbrowserButton",

xmPushButtonWidgetClass,

filepulldownMenu,

args, argcnt);

XmStringFree( xmstr[0] );  260

XtAddCallback(viewbrowserButton, XmNactivateCallback, ViewBrowserCallback, (caddr t)0); XtManageChild(viewbrowserButton);

/**** About lucy *****/

argcnt = 0;

XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(  270

             "About Lucy...",

XmSTRING DEFAULT CHARSET))); argcnt++;

XtSetArg(args[argcnt], XmNrecomputeSize, False); argcnt++; lucyButton = XtCreateWidget("lucyButton",

xmPushButtonWidgetClass, filepulldownMenu, args, argcnt);

XmStringFree( xmstr[0] );

280

XtAddCallback(lucyButton, XmNactivateCallback, lucyCallback, (caddr t)0); XtManageChild(lucyButton);

argcnt = 0; XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(

             "Quit",

XmSTRING DEFAULT CHARSET))); argcnt++;

XtSetArg(args[argcnt], XmNrecomputeSize, False); argcnt++;  290

quitButton = XtCreateWidget("quitButton", xmPushButtonWidgetClass, filepulldownMenu, args, argcnt);

XmStringFree( xmstr[0] );

XtAddCallback(quitButton, XmNactivateCallback, QuitCallback, (caddr t)0); XtManageChild(quitButton);

300

argcnt = 0; XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(

             "File",

XmSTRING DEFAULT CHARSET))); argcnt++;

XtSetArg(args[argcnt], XmNx, 5); argcnt++; XtSetArg(args[argcnt], XmNy, 5); argcnt++; XtSetArg(args[argcnt], XmNwidth, 40); argcnt++;

XtSetArg(args[argcnt], XmNheight, 21); argcnt++;  310

XtSetArg(args[argcnt], XmNsubMenuId, filepulldownMenu); argcnt++; fileButton = XtCreateWidget("fileButton",

xmCascadeButtonWidgetClass,

lucymenuBar,

args, argcnt);

XmStringFree( xmstr[0] );

XtManageChild(fileButton);

320

argcnt = 0; XtSetArg(args[argcnt], XmNwidth, 1); argcnt++; XtSetArg(args[argcnt], XmNheight, 1); argcnt++; menuShell1 = XtCreatePopupShell("menuShell1",

xmMenuShellWidgetClass,

lucymenuBar,

args,  330

argcnt);

XtManageChild(menuShell1);

argcnt = 0; XtSetArg(args[argcnt], XmNresizeWidth, True); argcnt++; XtSetArg(args[argcnt], XmNorientation, XmVERTICAL); argcnt++; XtSetArg(args[argcnt], XmNx, 0); argcnt++; XtSetArg(args[argcnt], XmNy, 0); argcnt++;

XtSetArg(args[argcnt], XmNwidth, 102); argcnt++;  340

XtSetArg(args[argcnt], XmNheight, 52); argcnt++; XtSetArg(args[argcnt], XmNrowColumnType, XmMENU PULLDOWN); argcnt++; editpulldownMenu = XtCreateWidget("editpulldownMenu",

xmRowColumnWidgetClass,

menuShell1,

args, argcnt);

argcnt = 0;  350

XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(

             "Enter question",

XmSTRING DEFAULT CHARSET))); argcnt++;

enterquestionButton = XtCreateWidget("enterquestionButton", xmPushButtonWidgetClass, editpulldownMenu, args, argcnt);

XmStringFree( xmstr[0] );

360

XtAddCallback(enterquestionButton, XmNactivateCallback, EnterQuestionCB, (caddr t)0); XtManageChild(enterquestionButton);

argcnt = 0; XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(

             "Send question ",

XmSTRING DEFAULT CHARSET))); argcnt++;

XtSetArg(args[argcnt], XmNrecomputeSize, False); argcnt++; sendquestionButton = XtCreateWidget("sendquestionButton",

xmPushButtonWidgetClass,  370

editpulldownMenu,

args, argcnt);

XmStringFree( xmstr[0] );

XtAddCallback(sendquestionButton, XmNactivateCallback, SendQuestionCB, (caddr t)0); XtManageChild(sendquestionButton);

argcnt = 0;

XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(  380

             "Edit",

XmSTRING DEFAULT CHARSET))); argcnt++;

XtSetArg(args[argcnt], XmNx, 45); argcnt++; XtSetArg(args[argcnt], XmNy, 5); argcnt++; XtSetArg(args[argcnt], XmNwidth, 40); argcnt++; XtSetArg(args[argcnt], XmNheight, 21); argcnt++; XtSetArg(args[argcnt], XmNsubMenuId, editpulldownMenu); argcnt++; editButton = XtCreateWidget("editButton",

xmCascadeButtonWidgetClass,

lucymenuBar,  390

args, argcnt);

XmStringFree( xmstr[0] );

XtManageChild(editButton);

argcnt = 0; XtSetArg(args[argcnt], XmNwidth, 1); argcnt++; XtSetArg(args[argcnt], XmNheight, 1); argcnt++;

menuShell2 = XtCreatePopupShell("menuShell2",  400

xmMenuShellWidgetClass,

lucymenuBar,

args, argcnt);

XtManageChild(menuShell2);

argcnt = 0; XtSetArg(args[argcnt], XmNx, 0); argcnt++;

XtSetArg(args[argcnt], XmNy, 0); argcnt++;  410

XtSetArg(args[argcnt], XmNwidth, 66); argcnt++; XtSetArg(args[argcnt], XmNheight, 31); argcnt++; XtSetArg(args[argcnt], XmNrowColumnType, XmMENU PULLDOWN); argcnt++; helppulldownMenu = XtCreateWidget("helppulldownMenu",

xmRowColumnWidgetClass,

menuShell2,

args, argcnt); argcnt = 0; XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(

             "Get Help",

XmSTRING DEFAULT CHARSET))); argcnt++;

gethelpButton = XtCreateWidget("gethelpButton", xmPushButtonWidgetClass, helppulldownMenu, args, argcnt);

XmStringFree( xmstr[0] );  430

XtAddCallback(gethelpButton, XmNactivateCallback, GetHelpCallback, (caddr t)0); XtManageChild(gethelpButton);

argcnt = 0; XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(

             "Help",

XmSTRING DEFAULT CHARSET))); argcnt++;

XtSetArg(args[argcnt], XmNx, 85); argcnt++;

XtSetArg(args[argcnt], XmNy, 5); argcnt++;  440

XtSetArg(args[argcnt], XmNwidth, 40); argcnt++; XtSetArg(args[argcnt], XmNheight, 21); argcnt++; XtSetArg(args[argcnt], XmNsubMenuId, helppulldownMenu); argcnt++; helpButton = XtCreateWidget("helpButton",

xmCascadeButtonWidgetClass,

lucymenuBar,

args, argcnt);

XmStringFree( xmstr[0] );

450

XtManageChild(helpButton);

argcnt = 0; XtSetArg(args[argcnt], XmNtopWidget, (Widget) lucymenuBar); argcnt++; /***/ XtSetArg(args[argcnt], XmNtopAttachment, XmATTACH WIDGET); argcnt++; XtSetArg(args[argcnt], XmNbottomAttachment, XmATTACH FORM); argcnt++; XtSetArg(args[argcnt], XmNleftAttachment, XmATTACH FORM); argcnt++; XtSetArg(args[argcnt], XmNrightAttachment, XmATTACH FORM); argcnt++;

XtSetArg(args[argcnt], XmNtopOffset, (int)0); argcnt++;  460

XtSetArg(args[argcnt], XmNbottomOffset, (int)0); argcnt++; XtSetArg(args[argcnt], XmNleftOffset, (int)0); argcnt++; XtSetArg(args[argcnt], XmNrightOffset, (int)0); argcnt++; XtSetArg(args[argcnt], XmNx, 2); argcnt++; XtSetArg(args[argcnt], XmNwidth, 450); argcnt++; XtSetArg(args[argcnt], XmNheight, 381); argcnt++; lucypanedWindow = XtCreateWidget("lucypanedWindow",

xmPanedWindowWidgetClass,

lucyform,

args,  470

argcnt);

XtManageChild(lucypanedWindow);

argcnt = 0; XtSetArg(args[argcnt], XmNx, 3); argcnt++; XtSetArg(args[argcnt], XmNy, 3); argcnt++; XtSetArg(args[argcnt], XmNwidth, 444); argcnt++;

XtSetArg(args[argcnt], XmNheight, 148); argcnt++;  480

lucytopframe = XtCreateWidget("lucytopframe", xmFrameWidgetClass, lucypanedWindow, args, argcnt);

XtManageChild(lucytopframe);

argcnt = 0;

XtSetArg(args[argcnt], XmNscrollingPolicy, XmAPPLICATION DEFINED); argcnt++;  490

XtSetArg(args[argcnt], XmNvisualPolicy, XmVARIABLE); argcnt++; XtSetArg(args[argcnt], XmNx, 2); argcnt++; XtSetArg(args[argcnt], XmNy, 2); argcnt++; XtSetArg(args[argcnt], XmNwidth, 440); argcnt++; XtSetArg(args[argcnt], XmNheight, 144); argcnt++; lucyscrolledWindow = XtCreateWidget("lucyscrolledWindow",

xmScrolledWindowWidgetClass,

lucytopframe,

args,

argcnt);  500

XtManageChild(lucyscrolledWindow);

argcnt = 0; XtSetArg(args[argcnt], XmNscrollBarDisplayPolicy, XmDYNAMIC); argcnt++; XtSetArg(args[argcnt], XmNx, 0); argcnt++; XtSetArg(args[argcnt], XmNy, 0); argcnt++; XtSetArg(args[argcnt], XmNwidth, 440); argcnt++; XtSetArg(args[argcnt], XmNheight, 144); argcnt++;

XtSetArg(args[argcnt], XmNselectionPolicy, XmBROWSE SELECT); argcnt++;  510

lucylist = XtCreateWidget("lucylist", xmListWidgetClass, lucyscrolledWindow, args, argcnt);

XtAddCallback(lucylist, XmNbrowseSelectionCallback,

BrowserSelectCallback, (caddr t)0);

520

XtManageChild(lucylist);

argcnt = 0; XtSetArg(args[argcnt], XmNx, 3); argcnt++; XtSetArg(args[argcnt], XmNy, 159); argcnt++; XtSetArg(args[argcnt], XmNwidth, 444); argcnt++; XtSetArg(args[argcnt], XmNheight, 170); argcnt++; lucybottomframe = XtCreateWidget("lucybottomframe",

xmFrameWidgetClass,  530

lucypanedWindow,

args, argcnt);

XtManageChild(lucybottomframe);

/*****/

argcnt = 0;

XtSetArg(args[argcnt], XmNscrollingPolicy, XmAUTOMATIC); argcnt++;  540

XtSetArg(args[argcnt], XmNscrollBarDisplayPolicy, XmAS NEEDED); argcnt++; XtSetArg(args[argcnt], XmNx, 2); argcnt++; XtSetArg(args[argcnt], XmNy, 2); argcnt++; XtSetArg(args[argcnt], XmNwidth, 340); argcnt++; XtSetArg(args[argcnt], XmNheight, 166); argcnt++; XtSetArg(args[argcnt], XmNvisualPolicy, XmCONSTANT); argcnt++; lucyscrolledWindow2 = XtCreateWidget("lucyscrolledWindow2",

xmScrolledWindowWidgetClass,

lucybottomframe,

args,  550

argcnt);

XtManageChild(lucyscrolledWindow2);

/******/

argcnt = 0; XtSetArg(args[argcnt], XmNeditMode, XmMULTI LINE EDIT); argcnt++; XtSetArg(args[argcnt], XmNcolumns, (short)71); argcnt++;

XtSetArg(args[argcnt], XmNrows, (short)12); argcnt++;  560

XtSetArg(args[argcnt], XmNx, 2); argcnt++; XtSetArg(args[argcnt], XmNy, 2); argcnt++; XtSetArg(args[argcnt], XmNwordWrap, False); argcnt++; lucytext = XtCreateWidget("lucytext",

xmTextWidgetClass, lucyscrolledWindow2, args, argcnt);

XtManageChild(lucytext);  570

XmTextSetEditable(lucytext, False);

return( retval );

}

Widget Createhelpform(parent) Widget parent;

{  580

Arg args[512]; int argcnt; Widget retval; XmString xmstr[32]; XmString *xmstrTable[8];

XmRegisterConverters();

argcnt = 0;  590

XtSetArg(args[argcnt], XmNx, 0); argcnt++; XtSetArg(args[argcnt], XmNy, 0); argcnt++; XtSetArg(args[argcnt], XmNwidth, 371); argcnt++; XtSetArg(args[argcnt], XmNheight, 370); argcnt++; helpform = XtCreateWidget("helpform",

xmFormWidgetClass, parent, args, argcnt); retval = helpform;

argcnt = 0; XtSetArg(args[argcnt], XmNtopAttachment, XmATTACH FORM); argcnt++; XtSetArg(args[argcnt], XmNleftAttachment, XmATTACH FORM); argcnt++; XtSetArg(args[argcnt], XmNrightAttachment, XmATTACH FORM); argcnt++; XtSetArg(args[argcnt], XmNtopOffset, (int)0); argcnt++; XtSetArg(args[argcnt], XmNleftOffset, (int)0); argcnt++;

XtSetArg(args[argcnt], XmNrightOffset, (int)0); argcnt++;  610

XtSetArg(args[argcnt], XmNx, 0); argcnt++; XtSetArg(args[argcnt], XmNy, 0); argcnt++; XtSetArg(args[argcnt], XmNwidth, 371); argcnt++; XtSetArg(args[argcnt], XmNheight, 31); argcnt++; XtSetArg(args[argcnt], XmNrowColumnType, XmMENU BAR); argcnt++; helpmenuBar = XtCreateWidget("helpmenuBar",

xmRowColumnWidgetClass,

helpform, args,

argcnt);  620

XtManageChild(helpmenuBar);

argcnt = 0; XtSetArg(args[argcnt], XmNwidth, 1); argcnt++; XtSetArg(args[argcnt], XmNheight, 1); argcnt++; menuShell3 = XtCreatePopupShell("menuShell3",

xmMenuShellWidgetClass,

helpmenuBar,

args,  630

argcnt);

XtManageChild(menuShell3);

argcnt = 0; XtSetArg(args[argcnt], XmNx, 0); argcnt++; XtSetArg(args[argcnt], XmNy, 0); argcnt++; XtSetArg(args[argcnt], XmNwidth, 72); argcnt++; XtSetArg(args[argcnt], XmNheight, 31); argcnt++;

XtSetArg(args[argcnt], XmNrowColumnType, XmMENU PULLDOWN); argcnt++;  640

exitpulldownMenu = XtCreateWidget("exitpulldownMenu", xmRowColumnWidgetClass, menuShell3, args, argcnt);

argcnt = 0; XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(

             "Exit Help",  650

XmSTRING DEFAULT CHARSET))); argcnt++;

XtSetArg(args[argcnt], XmNrecomputeSize, False); argcnt++; exitpushButton = XtCreateWidget("exitpushButton",

xmPushButtonWidgetClass,

exitpulldownMenu,

args, argcnt);

XmStringFree( xmstr[0] ); XtAddCallback(exitpushButton, XmNactivateCallback, Exit Callback, (caddr t)0);

XtManageChild(exitpushButton);  660

argcnt = 0; XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(

             "Exit",

XmSTRING DEFAULT CHARSET))); argcnt++;

XtSetArg(args[argcnt], XmNx, 5); argcnt++; XtSetArg(args[argcnt], XmNy, 5); argcnt++; XtSetArg(args[argcnt], XmNwidth, 40); argcnt++; XtSetArg(args[argcnt], XmNheight, 21); argcnt++;

XtSetArg(args[argcnt], XmNsubMenuId, exitpulldownMenu); argcnt++;  670

exithelpButton = XtCreateWidget("exithelpButton", xmCascadeButtonWidgetClass, helpmenuBar, args, argcnt);

XmStringFree( xmstr[0] );

XtManageChild(exithelpButton);

680

argcnt = 0;

XtSetArg(args[argcnt], XmNtopWidget, (Widget) helpmenuBar); argcnt++; XtSetArg(args[argcnt], XmNtopAttachment, XmATTACH WIDGET); argcnt++; XtSetArg(args[argcnt], XmNleftAttachment, XmATTACH FORM); argcnt++; XtSetArg(args[argcnt], XmNrightAttachment, XmATTACH FORM); argcnt++; XtSetArg(args[argcnt], XmNbottomAttachment, XmATTACH FORM); argcnt++; XtSetArg(args[argcnt], XmNtopOffset, (int)0); argcnt++; XtSetArg(args[argcnt], XmNleftOffset, (int)0); argcnt++;

XtSetArg(args[argcnt], XmNrightOffset, (int)0); argcnt++;  690

XtSetArg(args[argcnt], XmNx, 0); argcnt++; XtSetArg(args[argcnt], XmNwidth, 371); argcnt++; XtSetArg(args[argcnt], XmNheight, 332); argcnt++; helppanedWindow = XtCreateWidget("helppanedWindow",

xmPanedWindowWidgetClass, helpform, args, argcnt);

XtManageChild(helppanedWindow);  700

argcnt = 0; XtSetArg(args[argcnt], XmNx, 3); argcnt++; XtSetArg(args[argcnt], XmNy, 3); argcnt++; XtSetArg(args[argcnt], XmNwidth, 344); argcnt++; XtSetArg(args[argcnt], XmNheight, 148); argcnt++; helptopframe = XtCreateWidget("helptopframe",

xmFrameWidgetClass,

helppanedWindow,  710

args, argcnt);

XtManageChild(helptopframe);

argcnt = 0; XtSetArg(args[argcnt], XmNscrollingPolicy, XmAPPLICATION DEFINED); argcnt++; XtSetArg(args[argcnt], XmNvisualPolicy, XmVARIABLE); argcnt++; XtSetArg(args[argcnt], XmNx, 2); argcnt++;

XtSetArg(args[argcnt], XmNy, 2); argcnt++;  720

XtSetArg(args[argcnt], XmNwidth, 340); argcnt++; XtSetArg(args[argcnt], XmNheight, 144); argcnt++; helpscrolledWindow = XtCreateWidget("helpscrolledWindow",

xmScrolledWindowWidgetClass,

helptopframe,

args, argcnt);

XtManageChild(helpscrolledWindow);

730

/* ***** help list stuff ******/

argcnt = 0; XtSetArg(args[argcnt], XmNscrollBarDisplayPolicy, XmDYNAMIC); argcnt++; XtSetArg(args[argcnt], XmNx, 0); argcnt++; XtSetArg(args[argcnt], XmNy, 0); argcnt++; XtSetArg(args[argcnt], XmNwidth, 340); argcnt++; XtSetArg(args[argcnt], XmNheight, 144); argcnt++;

740

XtSetArg(args[argcnt], XmNselectionPolicy, XmBROWSE SELECT); argcnt++; /* XtSetArg(args[argcnt], XmNbrowseSelectionCallback,

HelpItemSelectCallback); argcnt++;

bug is localized to here*/

helplist = XtCreateWidget("helplist", xmListWidgetClass, helpscrolledWindow, args,

argcnt);  750

XtAddCallback(helplist, XmNbrowseSelectionCallback, HelpItemSelectCallback, (caddr t)0);

XtManageChild(helplist);

/*************************/

argcnt = 0;

XtSetArg(args[argcnt], XmNx, 3); argcnt++;  760

XtSetArg(args[argcnt], XmNy, 159); argcnt++; XtSetArg(args[argcnt], XmNwidth, 344); argcnt++; XtSetArg(args[argcnt], XmNheight, 170); argcnt++; helpbottomframe = XtCreateWidget("helpbottomframe",

xmFrameWidgetClass, helppanedWindow, args, argcnt);

XtManageChild(helpbottomframe);  770

argcnt = 0; XtSetArg(args[argcnt], XmNscrollingPolicy, XmAUTOMATIC); argcnt++; XtSetArg(args[argcnt], XmNscrollBarDisplayPolicy, XmAS NEEDED); argcnt++; XtSetArg(args[argcnt], XmNx, 2); argcnt++; XtSetArg(args[argcnt], XmNy, 2); argcnt++; XtSetArg(args[argcnt], XmNwidth, 340); argcnt++; XtSetArg(args[argcnt], XmNheight, 166); argcnt++; helpscrolledWindow2 = XtCreateWidget("helpscrolledWindow2",

xmScrolledWindowWidgetClass,  780

helpbottomframe,

args, argcnt);

XtManageChild(helpscrolledWindow2);

argcnt = 0; XtSetArg(args[argcnt], XmNeditMode, XmMULTI LINE EDIT); XtSetArg(args[argcnt], XmNeditable, False);

790

helptext = XtCreateWidget("helptext", xmTextWidgetClass, helpscrolledWindow2, args, argcnt);

XtManageChild(helptext);

return( retval );  800

}

Widget CreatedialogbulletinBoard(parent)

Widget parent; /* parent is Shell000 */  810

{ Arg args[512]; int argcnt; Widget retval; XmString xmstr[32]; XmString *xmstrTable[8]; Widget separator2; Widget separator; XmRegisterConverters();

820

argcnt = 0; XtSetArg(args[argcnt], XmNresizePolicy, XmRESIZE NONE); argcnt++; XtSetArg(args[argcnt], XmNx, 0); argcnt++; XtSetArg(args[argcnt], XmNy, 0); argcnt++; XtSetArg(args[argcnt], XmNwidth, 544); argcnt++; XtSetArg(args[argcnt], XmNheight, 382); argcnt++;

/*

dialogbulletinBoard = XtCreateWidget(”dialogbulletinBoard”,  830

xmBulletinBoardWidgetClass,

parent, args, argcnt); */

dialogbulletinBoard = XmCreateBulletinBoardDialog(parent, "dialog", args, argcnt);

840

retval = dialogbulletinBoard;

argcnt = 0; XtSetArg(args[argcnt], XmNorientation, XmVERTICAL); argcnt++; XtSetArg(args[argcnt], XmNx, 10); argcnt++; XtSetArg(args[argcnt], XmNy, 50); argcnt++; XtSetArg(args[argcnt], XmNwidth, 20); argcnt++; XtSetArg(args[argcnt], XmNheight, 270); argcnt++; separator = XtCreateWidget("separator",

xmSeparatorWidgetClass,  850

dialogbulletinBoard, args, argcnt);

XtManageChild(separator);

argcnt = 0; XtSetArg(args[argcnt], XmNx, 40); argcnt++; XtSetArg(args[argcnt], XmNy, 180); argcnt++;

XtSetArg(args[argcnt], XmNwidth, 146); argcnt++;  860

XtSetArg(args[argcnt], XmNheight, 20); argcnt++; separator2 = XtCreateWidget("separator2",

xmSeparatorWidgetClass, dialogbulletinBoard, args, argcnt);

XtManageChild(separator2);

argcnt = 0;  870

XtSetArg(args[argcnt], XmNx, 212); argcnt++; XtSetArg(args[argcnt], XmNy, 85); argcnt++; XtSetArg(args[argcnt], XmNwidth, 312); argcnt++; XtSetArg(args[argcnt], XmNheight, 209); argcnt++; messageframe = XtCreateWidget("messageframe",

xmFrameWidgetClass, dialogbulletinBoard, args, argcnt);

880

XtManageChild(messageframe);

argcnt = 0; XtSetArg(args[argcnt], XmNradioBehavior, True); argcnt++; XtSetArg(args[argcnt], XmNradioAlwaysOne, True); argcnt++; XtSetArg(args[argcnt], XmNpacking, XmPACK COLUMN); argcnt++; XtSetArg(args[argcnt], XmNx, 50); argcnt++; XtSetArg(args[argcnt], XmNy, 250); argcnt++; XtSetArg(args[argcnt], XmNwidth, 141); argcnt++;

XtSetArg(args[argcnt], XmNheight, 43); argcnt++;  890

replyradioBox = XtCreateWidget("replyradioBox", xmRowColumnWidgetClass, dialogbulletinBoard, args, argcnt);

XtManageChild(replyradioBox);

argcnt = 0;

XtSetArg(args[argcnt], XmNradioBehavior, True); argcnt++;  900

XtSetArg(args[argcnt], XmNradioAlwaysOne, True); argcnt++; XtSetArg(args[argcnt], XmNpacking, XmPACK COLUMN); argcnt++; XtSetArg(args[argcnt], XmNx, 60); argcnt++; XtSetArg(args[argcnt], XmNy, 130); argcnt++; XtSetArg(args[argcnt], XmNwidth, 105); argcnt++; XtSetArg(args[argcnt], XmNheight, 43); argcnt++; publishradioBox = XtCreateWidget("publishradioBox",

xmRowColumnWidgetClass,

dialogbulletinBoard,

args,  910

argcnt);

XtManageChild(publishradioBox);

argcnt = 0; XtSetArg(args[argcnt], XmNeditMode, XmSINGLE LINE EDIT); argcnt++; XtSetArg(args[argcnt], XmNwordWrap, False); argcnt++; XtSetArg(args[argcnt], XmNcolumns, (short)43); argcnt++; XtSetArg(args[argcnt], XmNrows, (short)1); argcnt++;

XtSetArg(args[argcnt], XmNscrollVertical, False); argcnt++;  920

XtSetArg(args[argcnt], XmNx, 191); argcnt++; XtSetArg(args[argcnt], XmNy, 326); argcnt++; persReplytext = XtCreateWidget("persReplytext", xmTextWidgetClass, dialogbulletinBoard, args, argcnt);

XtManageChild(persReplytext);

930

argcnt = 0; XtSetArg(args[argcnt], XmNeditMode, XmSINGLE LINE EDIT); argcnt++; XtSetArg(args[argcnt], XmNcolumns, (short)43); argcnt++; XtSetArg(args[argcnt], XmNrows, (short)1); argcnt++; XtSetArg(args[argcnt], XmNx, 191); argcnt++; XtSetArg(args[argcnt], XmNy, 34); argcnt++; subjecttext = XtCreateWidget("subjecttext",

xmTextWidgetClass, dialogbulletinBoard,

args,  940

argcnt);

XtManageChild(subjecttext);

argcnt = 0; XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(

             "Send Personal Reply to:",

XmSTRING DEFAULT CHARSET))); argcnt++;

XtSetArg(args[argcnt], XmNrecomputeSize, False); argcnt++;

XtSetArg(args[argcnt], XmNx, 40); argcnt++;  950

XtSetArg(args[argcnt], XmNy, 320); argcnt++; XtSetArg(args[argcnt], XmNwidth, 146); argcnt++; XtSetArg(args[argcnt], XmNheight, 33); argcnt++; XtSetArg(args[argcnt], XmNactivateCallback, NULL); argcnt++; sendReplytolabel = XtCreateWidget("sendReplytolabel",

xmPushButtonWidgetClass,

dialogbulletinBoard,

XmStringFree( xmstr[0] );  960

args, argcnt); XtManageChild(sendReplytolabel);

argcnt = 0; XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(

             "Personal Reply?",

XmSTRING DEFAULT CHARSET))); argcnt++;

XtSetArg(args[argcnt], XmNrecomputeSize, False); argcnt++; XtSetArg(args[argcnt], XmNx, 40); argcnt++;

XtSetArg(args[argcnt], XmNy, 210); argcnt++;  970

XtSetArg(args[argcnt], XmNwidth, 146); argcnt++; XtSetArg(args[argcnt], XmNheight, 33); argcnt++; XtSetArg(args[argcnt], XmNactivateCallback, NULL); argcnt++; personalReplylabel = XtCreateWidget("personalReplylabel",

xmPushButtonWidgetClass,

dialogbulletinBoard,

args, argcnt);

XmStringFree( xmstr[0] );

980

XtManageChild(personalReplylabel);

argcnt = 0; XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(

             "Publish Question?",

XmSTRING DEFAULT CHARSET))); argcnt++;

XtSetArg(args[argcnt], XmNrecomputeSize, False); argcnt++; XtSetArg(args[argcnt], XmNx, 40); argcnt++; XtSetArg(args[argcnt], XmNy, 90); argcnt++;

XtSetArg(args[argcnt], XmNwidth, 146); argcnt++;  990

XtSetArg(args[argcnt], XmNheight, 33); argcnt++; XtSetArg(args[argcnt], XmNactivateCallback, NULL); argcnt++; publishQuestionlabel = XtCreateWidget("publishQuestionlabel",

xmPushButtonWidgetClass,

dialogbulletinBoard, args, argcnt);

XmStringFree( xmstr[0] );

XtManageChild(publishQuestionlabel);  1000

argcnt = 0; XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(

             "Subject:",

XmSTRING DEFAULT CHARSET))); argcnt++;

XtSetArg(args[argcnt], XmNrecomputeSize, False); argcnt++; XtSetArg(args[argcnt], XmNx, 40); argcnt++; XtSetArg(args[argcnt], XmNy, 30); argcnt++; XtSetArg(args[argcnt], XmNwidth, 146); argcnt++;

XtSetArg(args[argcnt], XmNheight, 33); argcnt++;  1010

XtSetArg(args[argcnt], XmNactivateCallback, NULL); argcnt++; subjectlabel = XtCreateWidget("subjectlabel",

xmPushButtonWidgetClass,

dialogbulletinBoard,

args, argcnt);

XmStringFree( xmstr[0] );

XtManageChild(subjectlabel);

1020

argcnt = 0; XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(

             "Publish",

XmSTRING DEFAULT CHARSET))); argcnt++;

XtSetArg(args[argcnt], XmNx, 3); argcnt++; XtSetArg(args[argcnt], XmNy, 3); argcnt++; publishYestoggle = XtCreateWidget("publishYestoggle",

xmToggleButtonWidgetClass,

publishradioBox,

args,  1030

argcnt); XtManageChild(publishYestoggle);

argcnt = 0; XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(

             "Don\'t Publish",

XmSTRING DEFAULT CHARSET))); argcnt++;

XtSetArg(args[argcnt], XmNx, 3); argcnt++;  1040

XtSetArg(args[argcnt], XmNy, 23); argcnt++; publishNotoggle = XtCreateWidget("publishNotoggle",

xmToggleButtonWidgetClass,

publishradioBox,

args, argcnt);

XmStringFree( xmstr[0] );

XtManageChild(publishNotoggle);

1050

argcnt = 0; XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(

             "Want Personal Reply",

XmSTRING DEFAULT CHARSET))); argcnt++;

XtSetArg(args[argcnt], XmNx, 3); argcnt++; XtSetArg(args[argcnt], XmNy, 3); argcnt++; replyYestoggle = XtCreateWidget("replyYestoggle",

xmToggleButtonWidgetClass,

replyradioBox,

args,  1060

argcnt);

XmStringFree( xmstr[0] );

XtManageChild(replyYestoggle);

argcnt = 0; XtSetArg(args[argcnt], XmNlabelString, (xmstr[0]=XmStringCreateLtoR(

             "No Personal Reply",

XmSTRING DEFAULT CHARSET))); argcnt++;

XtSetArg(args[argcnt], XmNx, 3); argcnt++;  1070

XtSetArg(args[argcnt], XmNy, 23); argcnt++; replyNotoggle = XtCreateWidget("replyNotoggle",

xmToggleButtonWidgetClass,

replyradioBox,

args, argcnt);

XmStringFree( xmstr[0] );

XtManageChild(replyNotoggle);

1080

argcnt = 0; XtSetArg(args[argcnt], XmNresizePolicy, XmRESIZE NONE); argcnt++; XtSetArg(args[argcnt], XmNnoResize, True); argcnt++; XtSetArg(args[argcnt], XmNmessageString, (xmstr[0]=XmStringCreateLtoR(

             "When you have answered these questions,\n\
\n\
click the \'OK\' button to send your Question\n\
\n\
to Lucy.    Or, if you change your mind, click\n\

\n\  1090

the \'Cancel\' button to cancel your Question.\n\
\n\
For additional help, click the \'Help\' button.",

XmSTRING DEFAULT CHARSET))); argcnt++;

XtSetArg(args[argcnt], XmNx, 2); argcnt++; XtSetArg(args[argcnt], XmNy, 2); argcnt++; XtSetArg(args[argcnt], XmNwidth, 308); argcnt++; XtSetArg(args[argcnt], XmNheight, 205); argcnt++; messageBox = XtCreateWidget("messageBox",

xmMessageBoxWidgetClass,  1100

messageframe,

args, argcnt); XtAddCallback(messageBox, XmNcancelCallback, DialogCancelCallback, (caddr t)0); XtAddCallback(messageBox, XmNokCallback, DialogOKCallback, (caddr t)0); XtAddCallback(messageBox, XmNhelpCallback, GetHelpCallback, (caddr t)0); XtManageChild(messageBox);

1110

/* removing the following callbacks because we only want to use the following pushbutton widgets as glorified labels. */

XtRemoveAllCallbacks(subjectlabel, XmNactivateCallback); XtRemoveAllCallbacks(publishQuestionlabel, XmNactivateCallback); XtRemoveAllCallbacks(personalReplylabel, XmNactivateCallback); XtRemoveAllCallbacks(sendReplytolabel, XmNactivateCallback);

return( retval );  1120

}

C.3 Callbacks

/*

* Generated by the ICS builderXcessory (BX). * *

* Builder Xcessory 1.0.1. * */ #include <Xm/Xm.h> #include <Text.h>

#include <stdio.h>  10

#include <strings.h>

#include "widgets.h" /* list of globally defined widgets */ #include "errors.h" /* error messages */ #include "params.h" /* lucy−specific constants */

#include "hes.h" /* help editing system */

static Arg args[64]; /* make these global vars local to this file */  20

static int argcnt;

static text status = 0; /* 0 means no text entered.

1 means text has been entered. 2 means that text has been saved. */

extern void Get Subjects(void);

extern void Get Transaction(int, char *, int);  30

extern struct { char subject[LINE SIZE]; int tnum; /* transaction number */ } browser[BROWSER MAX];

extern struct { char name[HELPNAMSIZE];

char buffer[HELPBUFSIZE];  40

} hw[MAXWIND]; /* hw[MAXWIND]; */ /* allow up to MAXWIND help screens */

extern int num browser; /* keeps track of how many subjects are in browser */

static Boolean help on = False; /* true means that help screen is onscreen */

static char text[QUESTION SIZE]; /* text of question */

/* browser−−up to BROWSER MAX topics can be displayed at a time.  50

each topic can be up to 128 characters long. */

static XmString browser xm[BROWSER MAX];

void free strings(strarray, index)

XmString strarray[];

int index;

{

int i;  60

/* call XmFree for each string in the array */

for (i=0; i < index; i++) {

XmStringFree(strarray[i]); } }

void lucyCallback(w, client, call)

Widget w;

caddr t client;  70

caddr t call;

{

static char mess[] = "\n\nLucy \n\

User Interface Created by \n\
UChin Kim, May 1991.\n\
\n\
\n\

With help from:\n\  80

the Builder Xcessory (Integrated Computer Solutions, Inc.)  &&\n\
Bruce R. Lewis.\n\n";

/* display a message containing information about lucy program */ Widget info; XmString xm mess; /* compound string */

Widget bad child; /* use to get rid of help and cancel buttons */  90

void ErrorOKed(); /* callback procedure */

argcnt = 0; xm mess = XmStringCreateLtoR(mess, XmSTRING DEFAULT CHARSET); XtSetArg(args[argcnt], XmNmessageString, xm mess); argcnt++; XtSetArg(args[argcnt], XmNmessageAlignment, XmALIGNMENT CENTER); argcnt++; info = (Widget) XmCreateInformationDialog(w, "error", args, argcnt);

/* get rid of help and cancel buttons because we don’t need them. */

bad child = (Widget) XmMessageBoxGetChild(info, XmDIALOG CANCEL BUTTON);  100

XtUnmanageChild(bad child); bad child = (Widget) XmMessageBoxGetChild(info, XmDIALOG HELP BUTTON); XtUnmanageChild(bad child);

XtAddCallback(info, XmNokCallback, ErrorOKed, NULL); /* free up mem */ XtManageChild(info); XmStringFree(xm mess); /* free the compound string space we have allocated */

}  110

void DialogCancelCallback(w, client, call) Widget w; caddr t client; caddr t call; { #ifdef DEBUG LUCY

fprintf(stderr, "Dialog Cancelled.\n");  120

#endif XtUnmanageChild(DialogbulletinBoard);

}

void ErrorOKed(w, client, call)

Widget w;

caddr t client;

caddr t call;  130

{

XtDestroyWidget(w); /* free up the memory associated with the widget */ }

void ExplainError(w, mess)

Widget w; /* parent widget */

char *mess; /* error message */

{  140

Widget mess box; XmString xm mess; /* compound string */

Widget bad child; /* use to get rid of help and cancel buttons */

#ifdef DEBUG LUCY

fprintf(stderr, mess); #endif

150

/* create an error dialog box */

argcnt = 0; xm mess = XmStringCreateLtoR(mess, XmSTRING DEFAULT CHARSET); XtSetArg(args[argcnt], XmNmessageString, xm mess); argcnt++; mess box = (Widget) XmCreateErrorDialog(w, "error", args, argcnt); XtAddCallback(mess box, XmNokCallback, ErrorOKed, NULL);

XtManageChild(mess box);  160

/* get rid of help and cancel buttons because we don’t need them. */ bad child = (Widget) XmMessageBoxGetChild(mess box, XmDIALOG CANCEL BUTTON); XtUnmanageChild(bad child); bad child = (Widget) XmMessageBoxGetChild(mess box, XmDIALOG HELP BUTTON); XtUnmanageChild(bad child);

XmStringFree(xm mess); /* free the compound string space we have allocated*/

170

#ifdef DEBUG LUCY

fprintf(stderr, "Just managed the error box.  \n");

#endif

}

void  180

DialogOKCallback(w, client, call) Widget w; caddr t client; caddr t call; { /* data from dialog window */ Boolean pubYes, pubNo, replyYes, replyNo; char *edit; /* temp var. */

char subject[LINE SIZE], username[LINE SIZE];  190

char reply[LINE SIZE], publish[LINE SIZE];

Boolean errored;

errored = False; #ifdef DEBUG LUCY fprintf(stderr, "Dialog OKed.\n"); #endif

200

/* get information from dialog box */ /* need to get: subject text, publish toggles, reply toggles, send reply to

text */ /* then, check the data for errors, inconsistencies, −−− error handling. */ /* then, send it by mail, using Bruce’s code */

edit = XmTextGetString(subjecttext); /* get contents of subjecttext widget */ strncpy(subject, edit, LINE SIZE); XtFree(edit);

210

edit = XmTextGetString(persReplytext); strncpy(username, edit, LINE SIZE); XtFree(edit);

pubYes = XmToggleButtonGetState(publishYestoggle); pubNo = XmToggleButtonGetState(publishNotoggle); replyYes = XmToggleButtonGetState(replyYestoggle); replyNo = XmToggleButtonGetState(replyNotoggle);

/*  220

if (pubYes == True) fprintf(stderr, ”PublishYes on.\n”); if (pubNo == True) fprintf(stderr, ”PublishNo on. \n”); if (replyYes == True) fprintf(stderr, ”ReplyYes on.\n”); if (replyNo == True) fprintf(stderr, ”ReplyNo on.\n”); */ /* possible errors:

1. pubNo and replyNo are both True.

2. replyYes is True but username is empty.

3. pubYes and pubNo are both False.

4. replyYes and replyNo are both False.  230

5. No subject is provided. −−− delete this one, not necessary. */

/* handle case 1 */ if ( (pubNo == True) && (replyNo == True) ) { errored = True; ExplainError(DialogbulletinBoard, Error1); }

/* handle case 2 */  240

if ( (replyYes == True) && (strlen(username) == 0) ) { errored = True; ExplainError(DialogbulletinBoard, Error2); }

/* handle case 3 */ if ( (pubYes == False) && (pubNo == False) ) { errored = True; ExplainError(DialogbulletinBoard, Error3);

}  250

/* handle case 4 */ if ( (replyYes == False) && (replyNo == False) ) { errored = True; ExplainError(DialogbulletinBoard, Error4); }

if (errored == False) { /* concatenate all the information */  260

XtUnmanageChild(DialogbulletinBoard);

/* collate everything before sending it */ if (replyYes == True) { strcpy(reply, username); } else {

 strcpy(reply, "ANONYMOUS (NO REPLY)");

}

if (pubYes == True) {  270

strcpy(publish, "Yes."); } else { strcpy(publish, "No."); }

#ifdef DEBUG LUCY

fprintf(stderr, "Subject: %s\n\n", subject); fprintf(stderr, "%s\n", text);

fprintf(stderr, "SEND PERSONAL REPLY TO: %s\n", reply);

fprintf(stderr, "PUBLISH QUESTION/ANSWER IN BROWSER: %s\n", publish);  280

fprintf(stderr, "---END OF FILE---\n");

#endif

luc ask(subject, text, reply, publish);

text status = 0; /* Question is gone, has been sent via mail */ #ifdef DEBUG LUCY

fprintf(stderr, "empty buffer \n");  290

#endif

} /* end of if (errored == False) */ #ifdef DEBUG LUCY

fprintf(stderr, "at end of DialogOKed routine \n");

#endif }

300

void EnterQuestionCB(w, client, call)

Widget w; caddr t client; caddr t call; { static char prompt[] = "Question: "; int plength = strlen(prompt);

310

XmTextSetString(lucytext, prompt); /* display prompt */

/* set the cursor position, make it visible, and make it editable */ argcnt = 0; XtSetArg(args[argcnt], XmNcursorPosition, plength); argcnt++; XtSetArg(args[argcnt], XmNcursorPositionVisible, True); argcnt++; XtSetArg(args[argcnt], XmNmaxLength, QUESTION SIZE); argcnt++;

XtSetArg(args[argcnt], XmNautoShowCursorPosition, True); argcnt++;

/* XtSetArg(args[argcnt], XmNblinkRate, 0); argcnt++; */  320

XtSetValues(lucytext, args, argcnt); XmTextSetEditable(lucytext, True); #ifdef DEBUG LUCY

fprintf(stderr, "Enter text to edit.\n");

#endif

/* now let user edit text */

text status = 1; /* text is being entered */  330

#ifdef DEBUG LUCY

fprintf(stderr, "text is being entered \n");

#endif

}

void Exit Callback(w, client, call)

Widget w;

caddr t client;  340

caddr t call; { /* unmanage the help window */

if (help on == True) {

help on = False;

XtPopdown(Shell001); }

}  350

void GetHelpCallback(w, client, call) Widget w; caddr t client; caddr t call; { static Boolean initialize help = True; /* only load help windows once */ void HelpItemSelectCallback();

static XmString help topics[MAX HELP];  360

static int num topics = 0; /* number of topics available in help */ int i; #ifdef DEBUG LUCY

 fprintf(stderr, "Invoked GetHelp routine.\n");

#endif

/* check to see if Shell001 is displayed. If not, then Pop it up.

*/  370

if (help on == False) {

help on = True;

if (initialize help == True) {

initialize help = False; #ifdef DEBUG LUCY

fprintf(stderr, "Initializing helplist widget.\n");

#endif

380

load windows(HELPFILE); /* hw[] now contains help window names and buffers. */ /* convert help window names to an array of XmStrings */ /* store pointer to array of XmStrings in helplist widget */ /* store number of elements in the array in helplist widget also */

for (i = 0; i < num windows; i++) { help topics[i] = XmStringCreateLtoR(hw[i].name, XmSTRING DEFAULT CHARSET);

num topics++;  390

/* allow only up to MAX HELP topics */

if (i >= MAX HELP) num windows = MAX HELP; }

/* add the following stuff to helplist widget */ /*******/ argcnt = 0; XtSetArg(args[argcnt], XmNitemCount, num topics); argcnt++; XtSetArg(args[argcnt], XmNitems, help topics); argcnt++;

XtSetArg(args[argcnt], XmNvisibleItemCount, 5); argcnt++;  400

XtSetValues(helplist, args, argcnt);

free strings(help topics, num topics); /* free the XmString space */

/* also set the helptext widget to noneditable */ XmTextSetEditable(helptext, False); } /* end of one shot initialization routine for helplist */

XtPopup(Shell001, XtGrabNone);  410

} /* end of handler to put help screen on */ else { ExplainError(w, HelpAlreadyOn); } }

void HelpItemSelectCallback(w, client, call) Widget w; caddr t client;

XmListCallbackStruct *call;  420

{ /* routine to handle when user selects a help item. */ int n; char name[LINE SIZE]; char buffer[HELPBUFSIZE]; /* in hes.h */

n = call−>item position − 1; strncpy(name, hw[n].name, LINE SIZE); strncpy(buffer, hw[n].buffer, HELPBUFSIZE);

#ifdef DEBUG LUCY  430

fprintf(stderr, "Help item selected.\n");
fprintf(stderr, "You selected item:  %s with position number %d \n",

name, n);

fprintf(stderr, "Contents of window:  \n%s\n--End of Window--\n", buffer);

#endif

XmTextSetString(helptext, buffer); /* display text in window */  440

}

void QuitCallback(w, client, call) Widget w; caddr t client; caddr t call; {

450

/* works */

XtCloseDisplay(XtDisplay(w)); /* exit lucy program */ exit(0);

}

void SendQuestionCB(w, client, call)

Widget w;  460

caddr t client; caddr t call; { char *edit;

if (text status == 0) { /* no text has been entered */ #ifdef DEBUG LUCY

fprintf(stderr, "empty buffer \n");

#endif  470

/* pop up an error dialog */

ExplainError(w, ErrorNoQuestionText); #ifdef DEBUG LUCY

   fprintf(stderr, "error popup:  No Text has been entered.  \n");

#endif return; /* don’t continue with SendQuestion */

}  480

if (text status == 1) { /* text entered, but not saved. */ #ifdef DEBUG LUCY

   fprintf(stderr, "text has been entered.  \n");

#endif

XmTextSetEditable(lucytext, False); /* prevent further editing */ edit = (char *) XmTextGetString(lucytext);

/* remember to handle empty string error here */  490

strncpy(text, edit, 10000); XtFree(edit); } /* if text status == 2, then text has previously been saved,

and we need not save it again . */ if (text status == 2) { #ifdef DEBUG LUCY

   fprintf(stderr, "text has been saved to text[] \n");

#endif  500

} #ifdef DEBUG LUCY

fprintf(stderr, "You entered <%s> \n", text); #endif

510

/* and now pop up some dialog window to get additional info */ XtManageChild(DialogbulletinBoard);

}

void get topics list() { /* get topics list gets a list of topics from the meetings server

and places them in browser[].. also sets num browser to the  520

number of topics in the browser array. */

/* currently, this is a stub routine */

/* some old stub code. int i;

for (i=0; i < ; i++) { strncpy(browser[i].subject, hw[i].name, LINE SIZE);

530

#ifdef DEBUG LUCY

fprintf(stderr, ”browser[%d] = %s \n”, i, browser[i].subject); fprintf(stderr, ”hw[%d].name = %s \n”, i, hw[i].name);

#endif num browser++; } */

540

Get Subjects(); }

void ViewBrowserCallback(w, client, call) Widget w; caddr t client;

caddr t call;  550

{ static one shot = True; int i; /* display CB’s for transaction select. add scroll CBs or enable them. add CBs for transaction select. set a flag to indicate that we are in browser mode */

#ifdef DEBUG LUCY

fprintf(stderr, "In View Browser.\n");

#endif  560

if (one shot == True) { one shot = False; get topics list(); /* get the list of topics from the meetings server */ /* list of topics is now stored in browser[]...

and num browser tells us how many topics are in browser */

for (i = 0; i < num browser; i++) {  570

browser xm[i] = XmStringCreateLtoR(browser[i].subject,

XmSTRING DEFAULT CHARSET);

} argcnt = 0; XtSetArg(args[argcnt], XmNitemCount, num browser); argcnt++; XtSetArg(args[argcnt], XmNitems, browser xm); argcnt++; XtSetArg(args[argcnt], XmNvisibleItemCount, 10); argcnt++; XtSetValues(lucylist, args, argcnt); free strings(browser xm, num browser);

XmTextSetEditable(lucytext, False);  580

} /* end of one shot deal */

}

void BrowserSelectCallback(w, client, call)

Widget w; caddr t client;

XmListCallbackStruct *call;  590

{ int n; /* selection number */ int tnum; /* transaction number */ char name[LINE SIZE]; static char buffer[QUESTION SIZE]; char * edit;

if (text status == 1) { /* question has been previously entered, but

not saved. */  600

#ifdef DEBUG LUCY

 fprintf(stderr, "text has been entered but not saved.  \n");

#endif

edit = (char *) XmTextGetString(lucytext); strncpy(text, edit, HELPBUFSIZE); text status = 2; /* question has been saved */

XtFree(edit);  610

}

/* code to handle a browser selection */ #ifdef DEBUG LUCY

fprintf(stderr, "You selected something in the browser \n");

#endif

620

n = call−>item position − 1;

tnum = browser[n].tnum; /* get the transaction number for this item */ bzero(buffer, QUESTION SIZE); /* clear buffer */ Get Transaction(tnum, buffer, QUESTION SIZE); /* old code... strncpy(name, hw[n].name, LINE SIZE); strncpy(buffer, hw[n].buffer); */

#ifdef DEBUG LUCY  630

 fprintf(stderr, "You selected item %d \n", n);

#endif

/* we want to clear any previous text, to reset the window to

beginning of text */ XmTextSetString(lucytext, ""); /* clear the previous text */ XmTextSetString(lucytext, buffer); /* display text in window */

640

}

C.4 Viewbrowser

/* test.c tests out viewbrowser interface.

two critical functions: Get Subjects() −−− gets a list of up to 4096 subjects and their corresponding transaction numbers.

got everything working!!!!

−− ukim */

10

#include <stdio.h> #include <discuss/discuss.h>

#include "lucy.h"
#include "params.h"

#define False 0 #define True 1

typedef int Boolean;  20

extern tfile unix tfile();

struct browse struct { char subject[LINE SIZE]; int tnum; /* transaction number */ } browser[BROWSER MAX]; /* up to 4096 possible transactions handled */

int num browser = 0; /* keeps track of how many subjects are in the browser */  30

void Get Subjects() /* works */ { int i; Boolean exit flag; int cur tnum, next tnum; /* current and next transaction numbers */ ltrn *tran info; /* transaction info structure */

int err code; /* error code */  40

/* initialize luc init() to get in touch with the browser meeting.*/ luc init(); /* initialize lucy discuss meetings */ luv curmtgp = &luv bmtg; /* the current meeting will be the browser */ /* luv curmtgp is a virtual constant after this point */ /* algo: get 1st transaction using luc next(). and also get

trn number for each transaction. iterate until no more transactions or browser is full */

/* am assuming that an error indicates that no more transactions  50

are available */ num browser = 0; /* initialize */ next tnum = 0; /* next transaction number */ exit flag = False; while (exit flag == False) { cur tnum = next tnum; luc next(luv curmtgp, cur tnum, &next tnum); if (next tnum != 0) {

luc trn info(luv curmtgp, next tnum, &tran info);  60

/* assign data to browser */ strncpy(browser[num browser].subject, tran info−>subject, 128); browser[num browser].tnum = next tnum;

num browser++; } else { /* no more transactions available */

#ifdef DEBUG LUCY

fprintf(stderr, "Get_Subjects:  no more transactions available.\n");

#endif  70

exit flag = True; }

if (num browser == BROWSER MAX) { #ifdef DEBUG LUCY

fprintf(stderr, "Get_Subjects:  Browser is full.\n");

#endif

80

exit flag = True; }

} /* end while */

} /* end Get Subject() */ void Get Transaction(int tnum, char buffer[], int buflen) /* retries the contents of a transaction, given the transaction

number. buflen should be maximum size of buffer being passed. Be sure to call Get Subjects() to initialize lucy meetings beforehand. */

{ char tmpfname[LINE SIZE]; /* name of temporary file */ FILE *tempfile; /* temporary file */ tfile tf; /* need a tfile store contents of transaction in */

int terror; /* place to hold error numbers that meetings server  100

might return */

/* creat a tfile :

create a temporary file, and open it.

use unix tfile to translate it into a tfile.

call get trn, then read the temporary file to output to fprintf */

tmpnam(tmpfname); /* create a temporary file name */

tempfile = freopen(tmpfname, "w+", stdout);  110

if (tempfile == NULL) { #ifdef DEBUG LUCY

 fprintf(stderr, "could not create a temporary file.\n");

#endif

exit(1); }

#ifdef DEBUG LUCY  120

fprintf(stderr, "creating unix wrapper \n");

#endif tf = unix tfile(1); /* put a tfile wrapper around the tempfile */ #ifdef DEBUG LUCY

fprintf(stderr, "finished creating unix wrapper \n");

#endif

130

dsc get trn(&(luv curmtgp−>nb), tnum, tf, &terror); #ifdef DEBUG LUCY

fprintf(stderr, "get_trn returned error code %d \n", terror);

#endif

tdestroy(tf); /* free the memory allocated to the tfile */  140

fclose(tempfile); /* flush out to disk before reading it back in */ /* need to close the file, to flush out the data, so that we

can read it back in. */

/* read from buffer */

tempfile = fopen(tmpfname, "r"); fread(buffer, sizeof(char), buflen, tempfile);

150

/* display transaction contents */ #ifdef DEBUG LUCY

fprintf(stderr, "Contents of transaction are \n%s\n--End of File--\n",

buffer);

#endif

fclose(tempfile);

unlink(tmpfname); /* get rid of our temporary file */  160

} /* end of get transaction() */

prev obsolete code() { ltrn *tran info; /* transaction info structure */ char subject[LINE SIZE]; /* subject of transaction */

int next;  170

int tnum; /* transaction number */ tfile tf; /* need a tfile store contents of transaction in */ int terror; /* place to hold error numbers that meetings server

might return */

char buffer[QUESTION SIZE]; /* place to hold contents of transaction */ char text[QUESTION SIZE]; /* buffer for tfile */ char tmpfname[LINE SIZE]; /* name of temporary file */ FILE *tempfile; /* temporary file */

luc init(); /* initialize lucy discuss meetings */  180

luv curmtgp = &luv bmtg; /* the current meeting will be the browser */ luc next(luv curmtgp, 0, &next); /* get first (next) transaction. */ luc trn info(luv curmtgp, next, &tran info);

strcpy(subject, tran info−>subject); tnum = tran info−>current; /* transaction number of current transaction */ #ifdef DEBUG LUCY

fprintf(stderr, "Subject of transaction:  %s \n", subject);

fprintf(stderr, "Transaction # is %d \n", tnum);

#endif  190

/* creat a tfile : create a temporary file, and open it. use unix tfile to translate it into a tfile. call get trn, then read the temporary file to output to fprintf */

200

tmpnam(tmpfname); /* create a temporary file name */

tempfile = freopen(tmpfname, "w+", stdout);

if (tempfile == NULL) { #ifdef DEBUG LUCY

 fprintf(stderr, "could not create a temporary file.\n");

#endif

210

exit(1); }

#ifdef DEBUG LUCY

fprintf(stderr, "creating unix wrapper \n");

#endif

tf = unix tfile(1); /* put a tfile wrapper around the tempfile */

#ifdef DEBUG LUCY  220

fprintf(stderr, "finished creating unix wrapper \n");

#endif

dsc get trn(&(luv curmtgp−>nb), tnum, tf, &terror); #ifdef DEBUG LUCY

fprintf(stderr, "get_trn returned error code %d \n", terror);

#endif

230

tdestroy(tf); /* free the memory allocated to the tfile */

fclose(tempfile); /* flush out to disk before reading it back in */ /* need to close the file, to flush out the data, so that we

can read it back in. */

/* read from buffer */

240

tempfile = fopen(tmpfname, "r"); fread(buffer, sizeof(char), 1024, tempfile);

/* display transaction contents */ #ifdef DEBUG LUCY

fprintf(stderr, "Contents of transaction are \n%s\n--End of File--\n",

buffer);

#endif

250

fclose(tempfile); unlink(tmpfname); /* get rid of our temporary file */

}

/*

main()  260

{ int i; int tnum;

char buffer[QUESTION SIZE]; Get Subjects();

for (i=0; i<num browser; i++) {  270

#ifdef DEBUG LUCY

fprintf(stderr, ”[%d] => %s\n”, browser[i].tnum, browser[i].subject); #endif

}

i = 1;

while (i != 0) {  280

printf(”Enter Transaction #: ”);

scanf(”%d”, &tnum);

Get Transaction(tnum, buffer, 1024); }

} */

290

C.5 Help

#define FALSE 0 #define TRUE 1

#define IN LIMBO 1 #define IN TEXT 2

#define DELIMITER '|' #define EOF END "|EOF|" /* marks the end of a help windows file */

#define HELPBUFSIZE 2048 /* size of one help buffer */  10

#define HELPNAMSIZE 25 /* length of a help keyword */ #define MAXWIND 20 /* maximum number of help windows to allow */ #define LINESIZE 128 /* number of characters in a line */ extern int num windows;

#include <stdio.h> #include "hes.h" /* debugging flags: DEBUG PARSER −−−− aids in debugging the parser in load windows()

*/  10

/* help window editing system −−−− HES written && designed by UChin Kim prototype of things to come. the basic idea behind this is to allow help documentation to be incrementally modified on−line as needed. if a user finds the current

help documentation to be inadequate, he can always add his own  20

(provided that he knows how to use the system). the core of this

idea is that of extensibility. use the tools to make more tools.

granted, in this case, it is limited to making more documentation.

another idea related to this is that of deferment. one of the great ideas in computer science. defer arbitrary decisions as much as possible and/or allow the user to choose if possible. (emacs is a great example of this concept −−− emacs is very customizable) the only thing this particular system allows deferment of is of

the contents of the help windows themselves.  30

Possible future refinements include: allow a tree−like hierarchy of help windows −− so that the user can browse through at his leisure. not sure how to incorporate this so that it is easy to use and intuitive and elegant. one possibility would be to display the tree structure and allow the user to click on whatever topics he wants to see. good thing about this is that user can click on any sublevel of help immediately without having to go through intermediate help levels. (maybe it’s not a tree structure

but a web−structure ?) we could have something like a rogue  40

maze −−− no, not a rogue maze, but a special type of web.

a special type of web that is limited to four (or eight?) connections. think this out later. it might be possible to set up a type of browser −−− a revolutionary type of browser

anyways, this is HES. currently, it will allow a user to do the following:

1. display a list of current help windows.

2. edit a help window.  50

3. save a help window. (or save all help windows?)

4. display a help window.

format of help window file:

help windows can either be edited online or through the use of an editor.

EBNF Syntax:  60

{} encloses 0 or more [] encloses 0 or 1.

help−file: {help−window} EOF−delimiter

help−window: ’|’ help−name ’|’ help−text

EOF−delimiter: ’|EOF|’  70

example:

|Filtering|

filtering is .... ...

...  80

...

|Tracing|

tracing involves the assigning of ... ... ... ...

90

|EOF|

functions/operations:

− edit a window.

− display a window.

− display a windows list  100

− save the windows.

− load windows.

*/

/* { char name[HELPNAMSIZE]; char buffer[HELPBUFSIZE];

}; */  110

struct { char name[HELPNAMSIZE]; char buffer[HELPBUFSIZE]; } hw[MAXWIND]; /* hw[MAXWIND]; */ /* allow up to MAXWIND help screens */

int num windows = 0; /* initially, no windows are loaded */

void init hw()  120

/* initialize the contents of the hw[] array.

specifically, for each element of the array, set

its name to the empty string, and set its buffer

to the empty string */ { int i;

for (i = 0; i < MAXWIND; i++) {

hw[i].name[0] = NULL;  130

hw[i].buffer[0] = NULL; } } char *get keyword(line) char *line;

/* get keyword parses a line of the following format:

’|’ keyword ’|’  140

example: |hello| |goodbye| and so on. the keyword must be separated by ’|’ (or whatever DELIMITER is set to). get keyword will read up to the next ’|’ character. no spaces should be between the delimiters. */

/* seems to work */

{

static char key[HELPNAMSIZE];  150

int index = 0; int exit flag = FALSE;

key[0] = NULL; /* initialize char array */ if (*line == NULL) { #ifdef DEBUG PARSER

 fprintf(stderr, "get_keyword():  being passed a null string.\n");

#endif

160

return(key); }

line++; /* have line point past the first delimiter */ while ( (*line != NULL) && (*line != DELIMITER) && (exit flag == FALSE) ) { key[index] = *line; line++; index++; if (index >= (HELPNAMSIZE − 1)) exit flag = TRUE; /* no more space */

}  170

key[index] = NULL; /* make the string a proper C string */ return(key); }

int int scanf()

{ /* int scanf is an improvement on the regular scanf. for

some reason that i cannot fathom, scanf bombs if you tell  180

it to expect an integer input and the user types in a letter as a reply. int scanf works only for integer input

int scanf reads the input in as a string. then tries to convert it to an integer. if this is not possible, it returns a default value of 0. */

char str[40]; int num;

int scan flag;  190

scan flag = scanf("%s", str); if ( (scan flag == 0) || (scan flag == EOF) ) { return(0); }

scan flag = sscanf(str, "%d", &num); if (scan flag == 1) { return(num);

}  200

else { return(0); } } void load windows(filename) char *filename;

/* debugged */

/* load windows loads a set of windows from a file format is as follows:

(there should be no leading spaces before the keywords)

220

|Filtering|

Text follows . . .

|Tracing|

Text follows  230

. . .

|EOF|

basic strategy is as follows:  240

load windows keeps reading lines until it finds a line starting with ’|’. It assumes a keyword is enclosed with ’|’ on either side (no spaces allowed). Lines following the keyword are assumed to be the help text associated with the keyword. The appearance of the next keyword delimits the text of the previous keyword help text. And so on until the end, when the reserved keyword ’|EOF|’ tells load windows to stop reading any more lines.

250

*/

{

FILE *fp;

char *lineptr, line[LINESIZE];

260

char current help[HELPNAMSIZE]; /* name of current help text */ int current len = 0; /* length of current help buffer */

/* possible states for the parser to be in */

/* #define IN LIMBO 1

#define IN TEXT 2 */

270

/* algorithm: starts out IN LIMBO. reads in a line at a time until it finds a DELIMITER. if DELIMITER is EOF then closes file and returns. otherwise we are IN TEXT and we set current help to the name of the keyword, and then enter IN TEXT. if, while in IN TEXT, we run out of buffer space for the particular keyword, we enter IN LIMBO again. if we encounter a DELIMITER, but we have no more room for keywords, we enter IN LIMBO (or close up?) */

int state = IN LIMBO;  280

int exit flag = FALSE; int defer read = FALSE; int help no = −1; /* keeps track of which help message to

read in */

init hw(); /* need to reset hw[] */

fp = fopen(filename, "r");

if (fp == NULL) {  290

 perror("load_windows():  Can't open the file.");

return; }

/* process a line at a time using fgets().

*/

lineptr = fgets(line, LINESIZE, fp);  300

while ( (lineptr != NULL) && (exit flag == FALSE) ) { /* lev 1 */ if (state == IN LIMBO) { /* lev 2 */ if (line[0] == DELIMITER) { /* lev 3 */

strcpy(current help,get keyword(line));

#ifdef DEBUG PARSER

fprintf(stderr, "parser: added delimiter %s \n", current help);

#endif if (strncmp(line, EOF END, strlen(EOF END)) == 0)

exit flag = TRUE;  310

else { /* lev 4 */ state = IN TEXT; current len = 0; /* reset the current length of the help buffer */

help no = help no + 1; /* point to next help buffer */

#ifdef DEBUG PARSER

fprintf(stderr, "new help buffer # is %d \n", help no);

#endif if (help no > MAXWIND) { exit flag = TRUE;

help no = help no − 1;  320

} else { /* lev 5 */ strcpy(hw[help no].name, current help);

#ifdef DEBUG PARSER

 fprintf(stderr, "assigned delimiter to buffer %d \n", help no);

#endif } /* end lev 5 */ } /* end lev 4 */

} /* end lev 3 */  330

/* otherwise do nothing */ } /* end lev 2 */

else if (state == IN TEXT) { /* lev 2 */ /* checks that the total length of the help text

will not be more than the buffer size for it. if total length is less, then add it, otherwise enter IN LIMBO */

if (line[0] == DELIMITER) { /* lev 3 */

defer read = TRUE; /* reprocess this line */  340

state = IN LIMBO; } /* end lev 3 */

else { /* lev 3 */

current len = current len + strlen(line);

#ifdef DEBUG PARSER

fprintf(stderr, "new text length is %d \n", current len);

#endif if (current len < HELPBUFSIZE) { /* lev 4 */

strcat(hw[help no].buffer, line);  350

} /* end else */ /* end lev 4 */

else { /* we’ve run out of space for that particular buffer (lev 4)*/ defer read = TRUE; /* reprocess this line, for no good reason */ state = IN LIMBO;

#ifdef DEBUG PARSER

         fprintf(stderr, "just entered limbo.\n");

#endif

} /* end lev 4 */

} /* end lev 3 */

} /* end lev 2 */  360

/* now get the next line */

if (defer read == TRUE)

defer read = FALSE;

else {

lineptr = fgets(line, LINESIZE, fp); }

} /* end while loop */ /* end lev 1 */  370

/* by now, we’ve finished reading in everything we needed to read in. close the file, set some global variables and return */

fclose(fp);

num windows = help no + 1;

}  380

int index window(name) char *name;

/* index window finds name in the hw array, and returns the indice corresponding to that name. uses a linear search. better search is not needed because there are usually less than 20 items to search anyways. returns −1 if not found */

{  390

int i = 0; int found = FALSE; int index;

while ( (i < num windows) && (found == FALSE) ) { #ifdef DEBUG PARSER

   fprintf(stderr, "index():  comparing <%s> with <%s> \n", name, hw[i].name);

#endif if (strcmp(name, hw[i].name) == 0) {

index = i;  400

found = TRUE; #ifdef DEBUG PARSER

    fputs("index():  comparison was true.");

#endif } i++; /* point to next item */ }

if (found == TRUE)

return(index);  410

else return(−1); }

void display window(name) char *name;

/* displays the windows in hw[],

displays the windows up to num windows. */  420

{

int i;

i = index window(name); #ifdef DEBUG PARSER

 fprintf(stderr,"display_window():  index of window is %d \n", i);

#endif

430

if (i == −1) { #ifdef DEBUG PARSER

   fprintf(stderr, "display_window():  couldn't find your window.\n");

#endif

} #ifdef DEBUG PARSER

440

fprintf(stderr, "Window : %s \n", hw[i].name); fprintf(stderr, "Text: %s", hw[i].buffer); #endif }

void list window names() /* list window names lists the names of all the windows it knows */

{  450

int i; #ifdef DEBUG PARSER

fprintf(stderr, "\n\n");

 fprintf(stderr, "--- List of Windows ---\n");

for (i = 0; i < num windows; i++) { fprintf(stderr, "%s\n", hw[i].name); }

fprintf(stderr, "\n\n");  460

#endif }

void simple menu() /* simple menu displays a list of choices, lets the user select from them. and executes the appropriate routines */

{

470

int choice; int exit flag = FALSE; int scan flag;

void handle list(), handle edit(), handle load(); void handle display();

do { #ifdef DEBUG PARSER

printf("\n\n\n");  480

printf("--- Le Menu Simplistique ---\n");
printf("1.  list window names.  \n");
printf("2.  edit a window.  \n");
printf("3.  load a windows file.  \n");
printf("4.  display a window screen.  \n");
printf("5.  exit this menu.\n");
printf("\n\nEnter the number corresponding to your choice.\n");

#endif

choice = int scanf();

490

if ( (choice > 0) && (choice < 6) ) { switch(choice) { case 1: handle list(); break; case 2: handle edit(); break; case 3: handle load(); break; case 4: handle display(); break; case 5: exit flag = TRUE; break; } }

500

} while (exit flag == FALSE); }

void handle list() { list window names(); }

void handle edit()  510

{

printf("editor:  Not implemented yet.  Tough luck.\n");

}

void handle load() { char filename[40]; int scan flag;

printf("Enter file name to load:  ");  520

scan flag = scanf("%s", filename); if (scan flag != 1) { filename[0] = '\0'; }

load windows(filename); list window names(); }

void handle display()  530

{

char name[HELPNAMSIZE]; int scan flag;

 printf("Enter name of window to display:  ");

scan flag = scanf("%s", name); if (scan flag != 1) {

name[0] = '\0';  540

}

display window(name);

}

C.6 Errors

/* errors.h contains the text of error messages. */

static char Error1[] =

 "You are providing no way for Lucy to respond to you.\n\
Select either the Publish option or the Reply option.\n";

10

static char Error2[] =

 "You need to provide your username or your actual name\n\
in the Reply section.    Otherwise, Lucy has no way to Reply\n\
to your Question.";

static char Error3[] =

 "You need to select one of the Publish options.\n\
If you want your question to be considered for publication,\n\

select 'Publish Question.'    Otherwise, select 'Don't Publish.'\n";  20

static char Error4[] =

 "You need to select one of the Reply options.\n\
If you want a personal Reply from Lucy, select 'Personal Reply.'\n\
Otherwise, select 'No Reply.'\n";

/******/

static char ErrorNoQuestionText[] =  30

 "Before you Send a Question, you need to Enter a Question.    \n\
Select `Enter Question' from the menu and type in the question.\n\
When you have finished typing in the Question, select `Send Question'\n\
from the menu.\n";

static char HelpAlreadyOn[] =

 "The Help Screen is already on the screen.\n";

References

[1] Joseph S. Dumas. Designing User Interfaces For Software. Prentice-Hall, Inc., Englewood Cliffs, New Jersey, 1988.

[2] Integrated Computer Solutions, Inc. The Builder Xcessory User Guide. Integrated Computer Solutions, Inc. Cambridge, Mass., 1990.

[3] Brian W. Kernighan and Dennis M. Ritchie. The C Programming Language. Prentice-Hall, Inc., Englewood Cliffs, New Jersey, 1988.

[4] Bruce R. Lewis. Design Issues for Lucy. 1990.

[5] Donald Norman. The Design of Everyday Things. Doubleday, New York, 1988.

[6] Open Software Foundation. OSF/Motif Programmer’s Reference Manual. Open Software Foundation, Cambridge, MA, 1989.

[7] Ken Raeburn. Discuss (subroutine foo). 1989.

[8] Herbert Schildt. C, The Complete Reference. Osborne McGraw-Hill, Berkeley, Calif., 1987.

[9] Douglas A. Young. X Window Systems Programming and Applications with Xt. Prentice-Hall, Inc., Englewood Cliffs, New Jersey, 1989.

[10] Stan Zanarotti, Ken Raeburn, and Nancy Gilman. Using Discuss. MIT SIPB, Cambridge, Mass., 1988.