XHIBITION '91 S. Jose, California Work In Progres Report ---------------------- Ralph Swick, Project Athena MIT/ DEC Roman Budzianowski, Project Athena MIT (*) (*) Currently at Horizon Research Inc. Directly-Mapped C++ interface to Xt toolkit intrinsics. ------------------------------------------------------ Contents: 1. Direct interface vs. wrapper approach 2. Advantages of the direct interface 2.1. Subclassing widgets 3. Problems with direct interface 3.1. Memory management issues 3.2 Destroying widgets 3.3 Var args 3.4 Alignment issues 4. Style of programming 4.1 Writing applications 4.2 Subclassing 4.3 SetValues, GetValue and widget creation 5. New resources for C++ subclasses 6. New action routines and translations 7. Implementation Appendix A. C++ bindings for Xt and Athena Widgets Appendix B. Example 1 Appendix C. Example 2 1. Direct interface vs. wrapper approach ---------------------------------------- The Xt toolkit is the most mature of the X based toolkits and it has gained a wide commercial acceptance. The most known of the commercial packages is the Motif tool set. An Xt based implementation of the Open Look toolkit is also available, The C++ on the other hand is gaining popularity as the primary tool for object oriented development. Until a mature C++ based X toolkit is available, there is a need to access the Xt functionality from C++. One popular approach is based on the idea of "wrappers", C++ classes built around Xt widgets. One can build a generic class which provides an interface to any type of widget, or one class per widget type providing more specialized interface. In any case each classe contains a pointer to a widget. The application programmer accesses the toolkit indirectly through the wrapper class. The directly-mapped interface, the idea is due to Charles Haynes, is based on a different idea. It draws from the fact, that the C++ classes (structures) are compatible with C structs. The interface doesn't use the C include files, instead it defines a set of classes in the C++ world which directly map into the structs in the C world. Because of that all operations on a widget performed by the intrinsics are directly reflected in the C++ world and vice versa. The C++ classes have direct access to the widget structure. The reason for building these interfaces, instead of just using X and Xt in C++ is the desire to program in C++ style. 2. Advantages of the direct interface ------------------------------------- Because there is no indirection involved, there is some gain in performence. The major advantage though, is that the return values of the Xt functions are direct pointers to C++ objects, so there is no need to perform a reverse mapping from the C world to the C++ world. In principle the direct interface can be implemented inline, that is with only a set of h files. This implementation has a small additional object code which has to be linked in addition to the normal X, Xt and widget libraries. It provides some new functionality. It may be removed in the future if it's deemed unnecessary. Direct access to the widget structure allows for efficient implementation of various additional functionality, e.g. read access to certain resources bypassing the usual GetValues mechanism. At the same time the internals of the widgets are protected from the application code by the private/protected mechanism of the C++ language. 2.1. Subclassing widgets ------------------------ Very attractive feature of the direct interface is that the widgets can be subclassed and still remain legitimate widgets from the intrinsic's point of view. In this way the application can attach new semantics to the widgets. The widget pointer passed by the intrinsics to a callback or an action is actually the pointer to the new C++ class, so all the new class functionality can be accessed. The traditional method uses the closure argument to the callback or a global variable ( in the case of an action the only way). Attaching new semantics directly to the widget is a more elegant style of programming. Note that in the wrapper approach, the subclassing of the wrapper clases is of course also possible, and an Xt interface can be provided that accepts and returns the wrappers in lieu of widgets. The reverse mapping though would require maintenace of a hash table of all widgets. Since callbacks and actions are called by the intrinsics, the application would have to explicitely invoke the reverse mapping functionality. There is no way, currently, to create new Xt classes in C++. New widgets have to be written in C. The new subclasses described above cannot change, in a substancial way, the graphical semantics associated with the widgets. One limited method to do that is by writing new actions and changing the default translation table in the constructor. It works fine if the new class is intended just for one application, but is not as general a solution as a new Xt widget class, since other users can modify the translations in the resource file. This modification will be performed by the intrinsics before the one in the constructor (the constructor has to create the widget first). Direct interface to the Xt class mechanism is possible too. It would allow the user to create totally new Xt classes, by subclassing the existing ones and changing class methods. 3. Problems with direct interface --------------------------------- In general, the direct interface is more "intimately" based on the intrinsic's and widgets' implementaions. As a consequence, the maintenance requires more effort. The wrapper approach, especially the generic wrapper, may not require any changes, in principle, as long as the exported Xt and widget's interface doesn't change (resources and extern functions). The classes in the direct interface (including the subclasses) cannot use the virtual function mechanism of C++, because the virtual table changes the layout of the structure, making it incompatible with C. There are limits to how much the interface can be made to be in C++ style. The callbacks and actions still have to be written as regular C-style functions. The widget pointers in the callbacks and actions have to be (down)casted to the appropriate widget type from Core*, which breaks the safe type checking mechanism of C++. This limitation applies equally to any approach to building a C++ interface to the Xt toolkit. 3.1. Memory management issues ----------------------------- The intrinsics like to have their own memory management. There is no easy way to accomodate this design from the C++ point of view. The placement syntax of the new operator in C++ is very inconvenient in this case (imagine: LabelWidget lw = new(XtCreateWidget(,,,,)) LabelWidget(,,,)). Some resources have to be set during the creation time so there is no way to avoid passing multiple arguments to the XtCreate routine. Overloading new and calling XtCreate from there doesn't help either, because we still have to pass the arguments to the operator new, and the syntax is equally inconvenient. Besides one wouldn't be able to create widgets on the stack. The only nice solution is to call XtCreate from the constructor, but by then the memory for the C++ object is already allocated. The C++ 2.0 standard made the assignment to 'this' obsolete, so we cannot use the Xt allocated memory. The solution is to provide an interface to the XtCreate routines, which accepts a placement argument. Currently there is a separate object file replicating the intrinsics functionality, but eventually we would only need to reorganize the intrinsics implementation and export the placement XtCreateP routines, e.g. XtCreateWidgetP(void* p, char* name, Widget parent, XtArg* args, Cardinal count). Note that this doesn't require any modifications to the semantics of the intrinsic's interface. The same approach is needed for the AppContext structure. 3.2 Destroying widgets ---------------------- The intrinsics use a two phase destroy algorithm, to allow calls to XtDestroyWidget from callbacks and actions. It relies on the intrinsics having a handle to a valid memory for some time after the XtDestroy has been called. The natural place in our interface to call XtDestroy is in the destructor of the widget classes. However, the operator delete is called immediately after the destructor and the memory is released leaving the intrinsics with a handle to the invalid memory. The solution is to overload the operator delete to be a noop leaving to the intrinsics the actual freeing of the memory. Creation of the widget objects on the stack works too. It is safe to assume that there must be an event dispatch loop in the lexical scope of such an widget. Upon leaving the lexical block, the widget can be destroyed completely, as no outside references are possible. Either the intrinsics or the C++ interface must take care, however, that the memory is not freed (by detecting that the 'this' pointer is on the stack). 3.3 Var args ------------- Using varargs, implemented in the R4 release of the Xt toolkit, is far more natural than the original XtArg method, especially in the C++ world. However vararg interface cannot be implemented inline and is one reason for the need of the additional object file in this interface previously mentioned. One, very crude approach is to define the relevant functions as accepting, say, 20 resource/value pairs with 19 pairs defaulted to NULL, using C++ default mechanism. An attractive aspect of this solution from the user point of view is that she doesn't have to end the list of arguments with NULL, as is needed for varargs. The disadvantage is that it is less general : vararg functions accept another format, the resources which require conversion. 3.4 Alignment issues -------------------- C++ provides very convenient syntactically way of subclassing through enumeration of additional fields (slots) in the structure . Xt, however, subclasses by combining substructures into a widget structure. On some architectures (DEC Station 3100) data types are aligned with reference to the beginning of a substructure. In particular the problem arises with doubles. As a result, the C++ definitions for some widgets, have to follow Xt method. E.g. class PannerWidget : public SimpleWidget { protected: PannerPart panner; public: Constructors(Panner,panner) {} }; 4. Style of programming ----------------------- One of the goals of this interface is to allow for C++ style programming of Xt toolkit applications. We achieved a partial success. The failures should provide valuable information for the work on anew C++ based toolkit. See Appendix B. for example of a simple(dummy) application. 4.1 Writing applications ------------------------ The interface significantly improves style of writing applications. The exception are callbacks, which have to be written in C style and require downcasting. 4.2 Subclassing --------------- This interface doesn't provide the full Xt style subclassing, because there is no interface to Xt classing mechanism. Most of the functionality can be achieved through simple C++ subclassing and XtGetSubresources interface. There is no way to modify Xt class methods. The Xt mechanism relying on two hierarchies, classes and objects, is not natural in the C++ world. Currently to modify the graphical semantics of the widget, one would have to subclass the widget in the C world, and then use it in C++. It is of coursese possible to provide analogous interface to the classing mechanism of Xt. See Appendix B. for example of a LayoutSpace C++ class, which is an interactive widget editor. 4.3 SetValues GetValues and widget cretaion ------------------------------------------- Arg mechanism for SetValues and widget creation in Xt is very inconvenient. A new mechanism is provided for that purpose. SetValues, GetValues and widget constructors accept a reference to a new type: ArgArray. class ArgArray { public: XtArgVal& operator() (const String resource_name) ; ArgArray(short size=2, short increment = 2); }; Usage: ArgArray args; args(XtNx) = XtArgVal(10); args(XtNy) = XtArgVal(20); LabelWidget lw("label",parent,args); 5. New resources for C++ subclasses ----------------------------------- New C++ subclasses can use the resource mechanism of Xt. As in the case of Xt classes it is acomplished through highly stylized coding and the Xt function XtGetSubresources. XtGetSubresources doesn't provide the equivalent effect to standard Xt subclassing. The new C++ class name appears to be a part of the Xt widget. The C++ object name appears to be a child of the widget name (which is the same), because XtGetSubresources doesn't accept the NULL object name if the class name is not NULL. As a result the resources of the Xt widget part should be set as e.g. *Layout.x: 5 and for the new LayoutSpace a subclass of Layout, as *Layout.LayoutSpace.cursor: dot, or *LayoutSpace.cursor: dot In the case of objects names we have: *layout.layout.cursor: dot, or *layout.cursor: dot (note the use of '*' before object name) The major problem here is that adding new resources requires higly stylized and repetitive programming as in the case of Xt subclassing which is syntactically not very attractive. See an example of the C++ widget subclass in appendix C. 6. New action routines and translations --------------------------------------- The above remarks apply to actions routines and new translations. It requires Xt style programming. Additionally the actions are global, unlike in a Xt subclass. As a result the function XtInstallAccelerators don't work. Since actions are not methods of the C++ class, the class has to provide a public interface to some internal functionality, for the actions to be able to acces it. See an example of the C++ widget subclass in appendix C. 7. Implementation ----------------- The implementation consists of a set of h files: Intrinsic.h and one file per class. The classes are organized as follows: class ObjectRec {}; // Object.h class RectObjRec : public ObjectRec {}; // RectObj.h class CompositeObjRec : public RectObjRec {}; // CompObj.h class WindowObjRec : public RectObjRec {}; // WindowObj.h class Core : public WindowObjRec {}; // Core.h typedef Core* Widget; class CompositeWidget : public Core {}; // Composite.h class ConstraintWidget : public CompositeWidget {}; // Constraint.h class ShellWidget : public CompositeWidget {}; // Shell.h class OverrideShellWidget : public ShellWidget {}; class WMShellWidget : public ShellWidget {}; class VendorShellWidget : public WMShellWidget {}; //Vendor.h class TransientShellWidget : public VendorShellWidget{}; // Shell.h class TopLevelShellWidget : public VendorShellWidget {}; class ApplicationShellWidget : public TopLevelShellWidget {}; class ApplicationContext {} typedef ApplicationContext* XtAppContext; The Athena widgets are implemented as subclasses of Core or ConstraintWidget. For example the LabelWidget: extern WidgetClass labelWidgetClass; class LabelWidget : public SimpleWidget { protected: /* resources */ Pixel foreground; XFontStruct *font; char* _label; XtJustify justify; Dimension internal_width; Dimension internal_height; Pixmap pixmap; Boolean resize; Pixmap left_bitmap; private: /* private state */ GC normal_GC; GC gray_GC; Pixmap stipple; Position label_x; Position label_y; Dimension label_width; Dimension label_height; Dimension label_len; int lbm_y; /* where in label */ unsigned int lbm_width, lbm_height; /* size of pixmap */ public: const char* label() { return _label; } Constructors(Label,label){} // no semicolon }; The Constructors macro implements the necessary constructors, and can be used by any widget class implementation. It takes two arguments, both the name of the widget class without the suffix WidgetClass. One argument is lower case, one upper case. The macro expands into the following three constructors: LabelWidget() {}; // needed for subclassing //accepts pointer to parent LabelWidget(String name, Widget parent, ArgList args = NULL, Cardinal numArgs = 0) LabelWidget(ManageState manage, String name, Widget parent, ArgList args = NULL, Cardinal numArgs = 0) LabelWidget(String name, Widget parent, ArgArray& args) LabelWidget(ManageState manage, String name, Widget parent, ArgArray args& args) // accepts reference to parent LableWidget(String name, Core& parent, ArgList args = NULL, CardinalnumArgs = 0) LableWidget(ManageState manage, String name, Core& parent, ArgList args = NULL, Cardinal numArgs = 0) LableWidget(String name, Core& parent, ArgArray& args) LableWidget(ManageState manage, String name, Core& parent, ArgArray& args) Varargs will be added in the future. Appendix A. C++ bindings for Xt and Athena Widgets -------------------------------------------------- class ApplicationContext { public: ApplicationContext() { if(!inited){ XtToolkitInitialize(); inited = True; } XtCreateApplicationContextP(this); run_loop = True; } void ExitLoop() { run_loop = False; } void ProcessEvent(XtInputMask mask) { XtAppProcessEvent(this, mask); } void MainLoop() { XEvent event; while(run_loop){ XtAppNextEvent(this, &event); XtDispatchEvent(&event); } run_loop = True; } XtIntervalId AddTimeOut(unsigned long ms, XtTimerCallbackProc p, XtPointer cd) { return XtAppAddTimeOut(this, ms, p, cd); } void RemoveTimeOut(XtIntervalId id) { XtRemoveTimeOut(id); } XtInputId AddInput(int src, Opaque cnd, XtInputCallbackProc p, XtPointer cd) { return XtAppAddInput(this, src, cnd, p, cd); } void NextEvent(XEvent *event) { XtAppNextEvent(this, event); } Boolean PeekEvent(XEvent *event) { return XtAppPeekEvent(this, event); } XtInputMask Pending() { return XtAppPending(this); } void AddActions(ActionArray& al, Cardinal num) { XtAppAddActions(this, &al[0], num); } void DisplayInitialize( Display *d, String appName, String appClass, XrmOptionDescRec *opts = NULL, Cardinal numOpts = 0, Cardinal *argc = NULL, char **argv = NULL) { XtDisplayInitialize(this, d, appName, appClass, opts, numOpts, argc, argv); } Display *OpenDisplay( String dispName, String appName, String appClass, XrmOptionDescRec *opts = NULL, Cardinal numOpts = 0, Cardinal *argc = NULL, char **argv = NULL) { return XtOpenDisplay( this, dispName, appName, appClass, opts, numOpts, argc, argv); } ~ApplicationContext() { XtDestroyApplicationContext(this); } XtWorkProcId AddWorkProc(XtWorkProc wp, Opaque cd) { return XtAppAddWorkProc(this, wp, cd); } }; class Core : public WindowObjRec { public: enum ManageState { Managed, Unmanaged }; int X() { return x; } int Y() { return y; } int Width() { return width; }; int Height() { return height; }; int BorderWidth() { return border_width; }; int Background() { return background_pixel; }; int Sensitive() { return sensitive && ancestor_sensitive; }; const char* Name() { return name; } Widget Parent() { return parent; }; Display *XtDisplay() { return screen->display; } Screen *XtScreen() { return screen; } Window XtWindow() { return window; } WidgetClass XtClass() { return widget_class; } // WidgetClass XtSuperclass() { return widget_class->XtSuperclass(); } Boolean IsManaged() { return managed; } Boolean IsRealized() { return window != 0; } ~Core() { XtDestroyWidget(this); } void Manage() { XtManageChild(this); } void Unmanage() { XtUnmanageChild(this); } void Map() { XMapWindow(XtDisplay(), XtWindow()); } void Unmap() { XUnmapWindow(XtDisplay(), XtWindow()); } Boolean IsSubclass(WidgetClass wClass) { return XtIsSubclass(this, wClass); } void Realize() { XtRealizeWidget(this); } void Unrealize() { XtUnrealizeWidget(this); } void SetSensitive(Boolean b=TRUE) { XtSetSensitive(this, b); } void MapWhenManaged(Boolean b=TRUE) {XtSetMappedWhenManaged(this,b);} Widget NameToWidget(String name) { return XtNameToWidget(this, name);} void AddCallback(String s, XtCallbackProc p, XtPointer cd) { XtAddCallback(this, s, p, cd); } void AddCallback(String s, XtCallbackProc p, Core* w) { XtAddCallback(this, s, p, (XtPointer)w); } void AddCallback(String s, XtCallbackProc p, Core& w) { XtAddCallback(this, s, p, (XtPointer)&w); } void RemoveCallback(String, XtCallbackProc, caddr_t); void AddCallbacks(String, XtCallbackList); void RemoveCallbacks(String, XtCallbackList); void RemoveCallbacks(String s) { XtRemoveAllCallbacks(this, s); } void CallCallbacks(String s, caddr_t cd) { XtCallCallbacks(this, s, cd); } void OverrideCallback(String, XtCallbackProc); // NASTY!! XtCallbackStatus HasCallbacks(String s) { return XtHasCallbacks(this, s); } XtGeometryResult MakeGeometryRequest(XtWidgetGeometry *, XtWidgetGeometry *); XtGeometryResult MakeResizeRequest(Dimension, Dimension, Dimension*, Dimension*); void TranslateCoords( Position x, Position y, Position &rootX, Position &rootY) { XtTranslateCoords(this, x, y, &rootX, &rootY); } XtGeometryResult QueryGeometry(XtWidgetGeometry*, XtWidgetGeometry*); void Popup(XtGrabKind gk=XtGrabNone) { XtPopup(this, gk); } void CallbackNone(caddr_t, caddr_t); void CallbackNonexclusive(caddr_t, caddr_t); void CallbackExclusive(caddr_t, caddr_t); void Popdown() { XtPopdown(this); } void CallbackPopdown(caddr_t, caddr_t); XtAppContext GetAppContext() { return XtWidgetToApplicationContext(this); } void GetSubresources(caddr_t base, String obj_name, String res_class, XtResourceList res_list, Cardinal res_count, ArgList args=NULL, Cardinal arg_count=0) { XtGetSubresources(this,base,obj_name,res_class,res_list,res_count,args,arg_count); } void SetValues(ArgList a, Cardinal ac) { XtSetValues(this, a, ac); } // void SetValues(String name,XtArgVal val, ...); void SetValues(String name,XtArgVal val, String name2 = (String)NULL, XtArgVal val2 = NULL , String name3 = (String)NULL, XtArgVal val3 = NULL, String name4 = (String)NULL, XtArgVal val4 = NULL) { XtVaSetValues(this,name,val,name2,val2,name3,val3,name4,val4,NULL); } void GetValues(ArgList a, Cardinal ac) { XtGetValues(this, a, ac); } // void GetValues(String name,XtArgVal *val, ...); void GetValues(String n1, XtArgVal* val1, String n2 = (String)NULL, XtArgVal* val2 = NULL) { XtVaGetValues(this, n1, val1, n2, val2, NULL); } void InstallAccelerators(Widget dest) { XtInstallAccelerators(dest,this); } void InstallAllAccelerators(Widget dest) { XtInstallAllAccelerators(dest,this); } void OverrideTranslations(XtTranslations tr) { XtOverrideTranslations(this,tr); } void AugmentTranslations(XtTranslations tr) { XtAugmentTranslations(this,tr); } GC GetGC(XtGCMask mask, XGCValues *values) { return XtGetGC(this, mask, values); } void DestroyGC(GC gc) { XtDestroyGC(gc); } int GrabPointer(Boolean owner, long mask,int pointer_mode,int keyboard_mode, Window confine_to, Cursor cursor, Time time) { return XtGrabPointer(this,owner,(unsigned int)mask,pointer_mode,keyboard_mode,confine_to,cursor,time); } void UngrabPointer(Time time) { XtUngrabPointer(this,time); } int GrabKeyboard(Boolean owner, int pointer_mode,int keyboard_mode, Time time) { return XtGrabKeyboard(this,owner,pointer_mode,keyboard_mode,time); } void UngrabKeyboard(Time time) { XtUngrabKeyboard(this,time); } void UngrabButton(int button, Modifiers modifiers) { XtUngrabButton(this,button,modifiers); } EventMask BuildEventMask() { return XtBuildEventMask(this); } }; Appendix B. Example 1 ---------------------- The example below illustrates the building of a simple application, the use of callbacks, actions, dialogs, nested dispatch loops. Note the use of ExitLoop method of the ApplicationContext to exit the last active MainLoop, instead of a global variable. Exiting the main loop in this manner is important if widgets are allocated on the stack. This ensures that the destructors are run before exiting the program, which may be important. The toolkit is initialized automatically when the Application context is created. #include #include #include #include #include #include #include #include #include #include void Done(Core* , XtPointer , XtPointer); void EditWidget(Core*, XEvent*, String*, Cardinal*); void UserDone(Core* , XtPointer , XtPointer); void QueryUser(Core*, XEvent*, String*, Cardinal*); class Interactor :public TransientShellWidget { private: DialogWidget *dialog; public: Interactor(Widget parent) :TransientShellWidget("interactor", parent) { dialog = new DialogWidget("dialog",this); dialog->AddButton("OK", Done, (XtPointer)this); Realize(); } ~Interactor() { delete dialog; } }; XtActionsRec actions[] = { {"QueryUser", QueryUser }, {"EditWidget", EditWidget } }; void DoEdit(Core* c, XtPointer d, XtPointer) { Interactor *i = (Interactor*)c; char* name = i->dialog->GetValueString(); Core* w = (Core*)d; w->SetValues(XtNlabel,(XtArgVal)name); delete i; } void EditWidget(Core* c, XEvent*, String*, Cardinal*) { Interactor *i = new Interactor(c); i->AddCallback(XtNpopdownCallback,DoEdit,(XtPointer)c); Position x_root, y_root; c->TranslateCoords(c->Width(),c->Height(),x_root,y_root); i->SetValues(XtNx,x_root,XtNy,y_root); i->Popup(XtGrabNonexclusive); } void UserDone(Core* c, XtPointer, XtPointer) { c->GetAppContext()->ExitLoop(); } void QueryUser(Core* c, XEvent*, String*, Cardinal*) { Interactor i(c); i.AddCallback(XtNpopdownCallback,UserDone,(XtPointer)c); Position x_root, y_root; c->TranslateCoords(c->Width(),c->Height(),x_root,y_root); i.SetValues(XtNx,x_root,XtNy,y_root); i.Popup(XtGrabNonexclusive); i.GetAppContext()->MainLoop(); char* name = i.dialog->GetValueString(); c->SetValues(XtNlabel,(XtArgVal)name); } void Done(Core*, XtPointer d, XtPointer) { TransientShellWidget *w = (TransientShellWidget *)d; w->Popdown(); } void QuitCB(Core* w , XtPointer, XtPointer) { w->GetAppContext()->ExitLoop(); } void BeepCB(Core* w , XtPointer d, XtPointer) { TransientShellWidget *ts = (TransientShellWidget *)d; Dimension height = w->Height(); Dimension width = w->Width(); Position x = w->X(); Position y = w->Y(); Position x_root, y_root; w->TranslateCoords(width/2,height/2,x_root,y_root); ts->SetValues(XtNx,x_root,XtNy,y_root); ts->Popup(XtGrabNonexclusive); } main(unsigned int argc, char** argv) { ApplicationContext appContext; appContext.AddActions(actions,XtNumber(actions)); Display *dpy = appContext.OpenDisplay(NULL, argv[0], "Eureka", NULL,0, &argc, argv); ApplicationShellWidget aw( argv[0], "Eureka", dpy); FormWidget form("form", aw); CommandWidget quit("quit", form); quit.AddCallback(XtNcallback, QuitCB, (XtPointer)NULL); TransientShellWidget ds("dshell",aw); DialogWidget dw("dialog", ds); dw.AddButton("OK", Done, (XtPointer)&ds); ds.Realize(); CommandWidget beep("beep", form); beep.AddCallback(XtNcallback, BeepCB, (XtPointer)&ds); ClockWidget clock("clock", form); aw.Realize(); appContext.MainLoop(); } Appendix C. ----------- Example 2. C++ widget LayoutSpace, a subclass of Layout widget: // LayoutSpace.h // This may look like C code, but it is really -*- C++ -*- /*********************************************************** Copyright 1991 by the Massachusetts Institute of Technology Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of M.I.T. not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. M.I.T. makes no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. Author: Roman J. Budzianowski - Project Athena MIT ******************************************************************/ #include #include #include #include #ifndef LayoutSpace_h #define LayoutSpace_h class LayoutSpace; class ObjectViewBase { friend class LayoutSpace; private: String name; public: virtual void Move(XPoint&) = 0; virtual void Resize(XPoint&, Dimension, Dimension) = 0; virtual void Place(XPoint&) = 0; virtual void GetSize(Dimension&,Dimension&) = 0; virtual void GetPosition(Position&,Position&) = 0; virtual void MakeEditable(LayoutSpace*) = 0; ObjectViewBase(String n) : name(n) {} ~ObjectViewBase() {} }; class WidgetObject : public ObjectViewBase { private: Widget widget; public: virtual void Move(XPoint&); virtual void Resize(XPoint&, Dimension, Dimension); virtual void Place(XPoint&); virtual void GetSize(Dimension&,Dimension&); virtual void GetPosition(Position&,Position&); virtual void MakeEditable(LayoutSpace*); WidgetObject(Widget w, String name) : widget(w), ObjectViewBase(name) {} WidgetObject() : ObjectViewBase(NULL), widget(NULL) {} }; class LayoutSpace : public LayoutWidget { public: enum Mode { Edit, Play}; LayoutSpace(Widget parent, ObjectViewBase* holder=NULL); void AddObject(ObjectViewBase* object); void CreateObject(XPoint&); void StartMovingObject(ObjectViewBase*, XPoint&); void PlaceObject(XPoint&); void RubberbandObject(XPoint&); void StartResizingObject(ObjectViewBase*); void RubberbandSize(XPoint&); void ResizeObject(XPoint&); void PlaceNewObject(XPoint&); void AbortCreatingObject(); XPoint GetEventLocation(XEvent*); void GetLastEvent(XEvent*); void InitializeWidget() { XDefineCursor(XtDisplay(), XtWindow(), normal_cursor); } private: static Boolean class_inited; // temporary storage for dynamic operations XPoint current_position; XPoint original_position; Dimension current_width; Dimension current_height; Dimension original_width; Dimension original_height; ObjectViewBase* object_holder; Boolean creating_object; Boolean moving_object; Boolean resizing_object; // X data EventMask grab_mask; GC flip_gc; // draw rubberband // resources Cursor normal_cursor; Cursor placement_cursor; Boolean rubberband; // private state Mode mode; // private methods void InitializeClass(); Boolean CreatingObjectP() { return creating_object; } Boolean PointerInsideP(XPoint&); void DrawRubberbandOfObject(); void EraseRubberbandOfObject() { DrawRubberbandOfObject(); } }; #endif Example 2 continued. // LayoutSpace.C // This may look like C code, but it is really -*- C++ -*- /*********************************************************** Copyright 1991 by the Massachusetts Institute of Technology Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of M.I.T. not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. M.I.T. makes no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. Author: Roman J. Budzianowski - Project Athena MIT ******************************************************************/ #include "LayoutSpace.h" #include "WindowStream.h" extern wstream msg; static void Manager(Core* , XEvent*, String* , Cardinal* ); static void ChildManager(Core* , XEvent*, String* , Cardinal* ); static void Initialize(Core* , XEvent*, String* , Cardinal* ); static char defTranslations[] = " ~Shift : LayoutSpaceManager(MoveObject) \n\ ~Shift : LayoutSpaceManager(StopObject) \n\ Shift : LayoutSpaceManager(ResizingObject) \n\ Shift : LayoutSpaceManager(NewSize) \n\ : LayoutSpaceManager(Ungrab) \n\ : LayoutSpaceManager(PlaceNewObject) \n\ : InitializeLayoutSpace()"; static char childTranslations[] = "#override \n\ ~Shift : LayoutChildManager(StartObject) \n\ Shift : LayoutChildManager(ResizeObject)"; struct LayoutSpace_resources { Cursor normal_cursor; Cursor placement_cursor; XtTranslations translations; XtTranslations child_translations; Boolean rubberband; }; #define XtNnormalCursor "normalCursor" #define XtNplacementCursor "placementCursor" #define XtNlayoutSpaceTranslations "layoutSpaceTranslations" #define XtNlayoutChildTranslations "layoutChildTranslations" #define XtNrubberbandObject "rubberbandObject" typedef LayoutSpace_resources *resource_pointer; static XtActionsRec actions[] = { {"LayoutSpaceManager", Manager}, {"LayoutChildManager", ChildManager}, {"InitializeLayoutSpace", Initialize }, }; #ifdef cfront_not_broken #define offset(field) XtOffset(resource_pointer, field) #else #define offset(field) 0 #endif static XtResource subresources[] = { {XtNnormalCursor, XtCCursor, XtRCursor, sizeof(Cursor), offset(normal_cursor), XtRString, "cross"}, {XtNplacementCursor, XtCCursor, XtRCursor, sizeof(Cursor), offset(placement_cursor), XtRString, "fleur"}, {XtNlayoutSpaceTranslations, XtCTranslations, XtRTranslationTable, sizeof(XtTranslations), offset(translations), XtRString, (caddr_t)defTranslations}, {XtNlayoutChildTranslations, XtCTranslations, XtRTranslationTable, sizeof(XtTranslations), offset(child_translations), XtRString, (caddr_t)childTranslations}, {XtNrubberbandObject, XtCBoolean, XtRBoolean, sizeof(Boolean), offset(rubberband), XtRImmediate, (caddr_t)False} }; #undef offset static void Initialize(Core* c, XEvent* , String* , Cardinal* ) { LayoutSpace *ls = (LayoutSpace *)c; ls->InitializeWidget(); } static void ChildManager(Core* c, XEvent* event, String* args, Cardinal*) { LayoutSpace *ls = (LayoutSpace *)c->Parent(); char action_type = *args[0]; switch(action_type) { case 'S': // change position of Object ls->StartMovingObject(new WidgetObject(c,(String)c->Name()),ls->GetEventLocation(event)); break; case 'R': ls->StartResizingObject(new WidgetObject(c,(String)c->Name())); break; default: cerr << "Child manager: unknown action.\n"; } } static void Manager(Core* c, XEvent* event, String* args, Cardinal*) { LayoutSpace *ls = (LayoutSpace *)c; char action_type = *args[0]; switch(action_type) { case 'U': ls->UngrabPointer(CurrentTime); // remove grab break; case 'M': // moving Object ls->GetLastEvent(event); ls->RubberbandObject(ls->GetEventLocation(event)); break; case 'S': ls->PlaceObject(ls->GetEventLocation(event)); break; case 'P': // place new Object ls->PlaceNewObject(ls->GetEventLocation(event)); break; case 'R': ls->GetLastEvent(event); ls->RubberbandSize(ls->GetEventLocation(event)); break; case 'N': ls->ResizeObject(ls->GetEventLocation(event)); break; default: ls->AbortCreatingObject(); cerr << "LayoutSpaceManager: unknown action\n"; } } Boolean LayoutSpace::class_inited = False; LayoutSpace::LayoutSpace(Widget parent, ObjectViewBase* holder) : LayoutWidget("layoutspace", parent), object_holder(holder) { InitializeClass(); LayoutSpace_resources res; GetSubresources((caddr_t)&res, (String)Name(), "LayoutSpace", subresources, XtNumber(subresources)); normal_cursor = res.normal_cursor; placement_cursor = res.placement_cursor; rubberband = res.rubberband; // XXX // this is temporary: eventually both default_translations and user specified // translations will be loaded in sequence. // tm.translations = res.translations; // ??? cfront dosn't like it SetValues(XtNtranslations,(XtArgVal)res.translations); SetValues(XtNaccelerators,(XtArgVal)res.child_translations); creating_object = False; moving_object = False; grab_mask = ButtonPressMask|ButtonReleaseMask|PointerMotionMask; XtGCMask valuemask; XGCValues values; values.function = GXinvert; values.plane_mask = border_pixel ^ background_pixel; values.subwindow_mode = IncludeInferiors; valuemask = GCPlaneMask | GCFunction | GCSubwindowMode; flip_gc = GetGC(valuemask, &values); } void LayoutSpace::InitializeClass() { if(class_inited == False){ #ifndef cfront_not_broken #include "fixres.h" int i=0; #define offset(field) XtOffset(resource_pointer, field) fix_resource(i,offset(normal_cursor));i++; fix_resource(i,offset(placement_cursor));i++; fix_resource(i,offset(translations));i++; fix_resource(i,offset(child_translations));i++; fix_resource(i,offset(rubberband));i++; #undef offset #endif GetAppContext()->AddActions(actions, XtNumber(actions)); class_inited = True; } } XPoint LayoutSpace::GetEventLocation(XEvent* event) { XPoint point; Position xroot,yroot,x,y; switch (event->xany.type) { case ButtonPress: case ButtonRelease: xroot = event->xbutton.x_root; yroot = event->xbutton.y_root; break; case KeyPress: case KeyRelease: xroot = event->xkey.x_root; yroot = event->xkey.y_root; break; case MotionNotify: xroot = event->xmotion.x_root; yroot = event->xmotion.y_root; break; default: cerr << "LayoutSpace: Cannot handle this event.\n"; xroot=-1000; yroot=-1000; } TranslateCoords(0,0,x,y); point.x = xroot - x; point.y = yroot - y; return point; } void LayoutSpace::GetLastEvent(XEvent* event) { while( XCheckTypedWindowEvent(XtDisplay(),XtWindow(),event->type,event) ) ; } void LayoutSpace::PlaceNewObject(XPoint& point) { if(CreatingObjectP()){ UngrabPointer(CurrentTime); // remove grab if(point.x > 0 && point.x < Width() && point.y > 0 && point.y < Height()) CreateObject(point); else AbortCreatingObject(); } } void LayoutSpace::AddObject(ObjectViewBase* object) { object_holder=object; creating_object = True; int retVal; retVal = GrabPointer(False,grab_mask,GrabModeAsync,GrabModeSync,None, placement_cursor,CurrentTime); if(retVal != GrabSuccess) cerr << "AddObject: cannot grab pointer\n"; } void LayoutSpace::CreateObject(XPoint& point) { msg << "Object: " << object_holder->name; msg << " created.\n" << flush; object_holder->Place(point); object_holder->MakeEditable(this); delete object_holder; creating_object = False; } void LayoutSpace::AbortCreatingObject() { delete object_holder; creating_object = False; XBell(XtDisplay(),50); msg << "** Aborted **\n" << flush; } void LayoutSpace::StartMovingObject(ObjectViewBase* object, XPoint& point) { int retVal; object_holder = object; moving_object = True; object->GetSize(current_width, current_height); current_position.x = -1; current_position.y = -1; object->GetPosition(original_position.x, original_position.y); // store the original relative position of the cursor original_position.x = point.x - original_position.x; original_position.y = point.y - original_position.y; retVal = GrabPointer(False,grab_mask,GrabModeAsync,GrabModeSync,None, placement_cursor,CurrentTime); if(retVal != GrabSuccess) cerr << "StartMovingObject: cannot grab pointer\n"; } void LayoutSpace::RubberbandObject(XPoint& point) { if(moving_object){ XPoint new_position; new_position.x = point.x - original_position.x; new_position.y = point.y - original_position.y; if(rubberband){ if(PointerInsideP(current_position))EraseRubberbandOfObject(); current_position = new_position; if(PointerInsideP(point))DrawRubberbandOfObject(); } else if(PointerInsideP(point)){ object_holder->Move(new_position); } } } void LayoutSpace::PlaceObject(XPoint& point) { if(moving_object){ cerr << "PlaceObject.\n"; UngrabPointer(CurrentTime); // remove grab if(PointerInsideP(current_position)){ if(rubberband) EraseRubberbandOfObject(); if(PointerInsideP(point)){ object_holder->Move(current_position); } } delete object_holder; object_holder = NULL; moving_object = False; } } void LayoutSpace::StartResizingObject(ObjectViewBase* object) { int retVal; object_holder = object; resizing_object = True; object->GetPosition(original_position.x, original_position.y); current_position = original_position; current_width=current_height=0; object->GetSize(original_width, original_height); retVal = GrabPointer(False,grab_mask,GrabModeAsync,GrabModeSync,XtWindow(), placement_cursor,CurrentTime); if(retVal != GrabSuccess) cerr << "StartResizingObject: cannot grab pointer\n"; } void LayoutSpace::RubberbandSize(XPoint& point) { if(resizing_object){ EraseRubberbandOfObject(); if(point.x > original_position.x && point.y > original_position.y){ current_position = original_position; current_position.x += 1; current_position.y += 1; current_width = point.x - current_position.x; current_height = point.y - current_position.y; } else if(point.x > original_position.x + original_width){ current_position.x = original_position.x + 1; current_position.y = point.y; current_width = point.x - original_position.x; current_height = original_position.y + original_height - point.y; } else if(point.y > original_position.y + original_height){ current_position.y = original_position.y + 1; current_position.x = point.x; current_height = point.y - original_position.y; current_width = original_position.x + original_width - point.x; } else { current_position = point; current_width = original_position.x - point.x + original_width; current_height = original_position.y - point.y + original_height; } DrawRubberbandOfObject(); } } void LayoutSpace::ResizeObject(XPoint& point) { if(resizing_object){ UngrabPointer(CurrentTime); // remove grab EraseRubberbandOfObject(); if(point.x > original_position.x && point.y > original_position.y) current_position = original_position; object_holder->Resize(current_position, current_width, current_height); delete object_holder; object_holder = NULL; resizing_object = False; } } Boolean LayoutSpace::PointerInsideP(XPoint& point) { return (point.x >= 0 && point.x < Width() && point.y >= 0 && point.y <= Height()); } void LayoutSpace::DrawRubberbandOfObject() { XDrawRectangle(XtDisplay(), XtWindow(), flip_gc, current_position.x, current_position.y, current_width, current_height); } // default object WidgetObject void WidgetObject::Move(XPoint& p) { widget->SetValues(XtNx, p.x, XtNy, p.y); } void WidgetObject::Resize(XPoint& p, Dimension w, Dimension h) { widget->SetValues(XtNx, p.x, XtNy, p.y, XtNwidth, w, XtNheight, h); } void WidgetObject::Place(XPoint& p) { Move(p); widget->Manage(); } void WidgetObject::GetSize(Dimension& w, Dimension& h) { w = widget->Width(); h = widget->Height(); } void WidgetObject::GetPosition(Position& xpos, Position& ypos) { xpos = widget->X(); ypos = widget->Y(); } void WidgetObject::MakeEditable(LayoutSpace* lay) { // widget->SetValues(XtNtranslations, (XtArgVal)XtParseTranslationTable("")); // lay->InstallAccelerators(widget); // widget->InstallAccelerators(lay); // XtInstallAllAccelerators(lay,lay); // Fake accelerators // Hope that child's translations don't clash with the editing translations // We have both Edit mode and Play mode active // Eventually there will be separate Edit and Play modes ArgArray args; XtTranslations tr; args(XtNaccelerators) = XtArgVal(&tr); lay->GetValues(args); widget->OverrideTranslations(tr); // widget->AugmentTranslations(tr); }