310 likes | 742 Views
Using Motif with C++. X and Motif are written in C C++ is backward compatible with C, provided that the function prototypes are correctly given So it is straightforward to call X and Motif from C++ programs
E N D
Using Motif with C++ • X and Motif are written in C • C++ is backward compatible with C, provided that the function prototypes are correctly given • So it is straightforward to call X and Motif from C++ programs • But all of our examples so far just look like C programs with strong typing (no class-oriented design)
Classes in C++ and Motif • Motif is designed with a class hierarchy and inheritance in mind (see next slide) • But the approaches to classes used by Motif and C++ are incompatible: • C++ programmers often work with object-oriented libraries by creating new specialized subclasses of the library • However, although Motif widgets are described as classes, there is no way to create a C++ class that is a subclass of a Motif widget
Motif and Xt Widget Classes Label PushButton . . . DrawnButton Primitive . . Text RowColumn Core Constraint Manager Form BulletinBoard Composite . . . Shell . . .
Approaches to Mixing Motif and C++ • Do not use OO features of C++ and just write applications in a style similar to C • misses out on the benefit of OOP • Wrap each Motif widget class in a C++ class • public domain and commercial wrapper sets are available • disadvantages: additional code, support, portability • Create higher-level user interface components in C++ which use Motif widgets as data attributes • use C++ classes to describe software architecture • do not force Motif widgets to be C++ classes • we will emphasize this approach
Example: A Tic-Tac-Toe Program Board Game game canvas: Widget display: Display gc: GC state: CharArray count: Integer playerX: Player board game game board startd endd summaryd Main Dialog shell: Widget app: XtAppContext dialog: Widget *
Notes on TTT Class Diagram • Only the class attributes are shown • All classes have singleton members except Dialog • The role of Motif widgets is to be attributes of C++ classes: • Main::shell, a shell widget • Board::canvas, an XmDrawingArea widget • Dialog::dialog, an XmDialogShell widget
Notes on TTT Class Diagram (cont'd) • The Board class manages the game display: • Detects clicks, displays marks • Needs the game object to access internal state • The Game class runs the game and keeps an internal representation • Needs the board object to update the display • The Dialog class manages the start, end, and summary dialogs • Needs the game object to return results of dialog • The Main class creates all of the above and manages the Xt connection
The Game Class Recall that Game also has a Board as an attribute as shown in the class diagram.
The Board Class Recall that Board also has a Game as an attribute as shown in the class diagram.
Board Class Methods • drawGrid, drawX, and drawO are the subject of a lab exercise • manage and unManage allow for exposing and hiding the drawing area • asciiDisplay is for debugging purposes • drawBoardCallback responds to any expose event generated for the drawing area • boardClickCallback responds to any mouse input event generated for the drawing area
Callbacks Available for Drawing Areas • XmNexposeCallback • Triggered when part of the widget is exposed. • We must provide a callback that will ``repaint'' the tic-tac-toe board when this is triggered • XmNinputCallback • Triggered when the widget receives a keyboard or mouse event. • We must provide a callback that will determine the coordinates of the clicked square and ask the board to draw an appropriate mark there • XmNresizeCallback • Triggered when the widget is resized. (We will not act on resizings.)
BoardInfo Constructor At the time we construct a new board, we will also add the callbacks for it: BoardInfo::BoardInfo(Widget parent, Game g) { ... canvas = XtVaCreateManagedWidget ( "canvas", xmDrawingAreaWidgetClass, parent, XmNheight, 300, XmNwidth, 300, NULL ); ... XtAddCallback(canvas, XmNexposeCallback, &BoardInfo::drawBoardCallback, ...); XtAddCallback(canvas, XmNinputCallback, &BoardInfo::boardClickCallback, ...); } Since the callbacks are static class methods, they must be qualified by the class name and begun with ``&''.
Writing the Draw Board Callback When an expose event occurs for the drawing area, the action called for is straightforward: drawGrid(); for (Integer x = 0; x < 3; x++) // Draw the Xs & Os for (Integer y = 0; y < 3; y++) { if (game->getState(x,y) == 'X') drawX(x, y); else if (game->getState(x,y) == 'O') drawO(x, y); }
Writing the Draw Board Callback (cont'd) Q: So, will this work? void BoardInfo::drawBoardCallback(Widget, XtPointer, XtPointer) { drawGrid(); for (Integer x = 0; x < 3; x++) // Draw the Xs & Os for (Integer y = 0; y < 3; y++) { if (game->getState(x,y) == 'X') drawX(x, y); else if (game->getState(x,y) == 'O') drawO(x, y); } } A: No, because: - callbacks are straight C functions and thus are static - static methods cannot access objects like game
Solution: Using Client Data Recall the XtAddCallback function prototype: void XtAddCallback ( Widget widget, //1 const String callbackName, //2 XtCallbackProc proc, //3 XtPointer clientData); //4 Recall the actual callback function prototype: void <funcname> (Widget w, // 1 XtPointer clientData, // 2 XtPointer callData); // 3
Using Client Data • The clientdata argument is specified by the call to XtAddCallback. The system remembers it and passes it along to the callback when appropriate. • XtPointer is a generic pointer type defined by Xt. It can be type cast to any pointer type. • So use it to obtain a pointer to an object that knows about the game object, i.e., the board object.
Strategy • When adding the callback to draw the grid, include the this pointer (a pointer to this board) as clientData • Write the (static) callback to use the clientData to get the board object for which the callback was added • The (static) callback does nothing but call a nonstatic method on the board object that does the work of drawing the grid
BoardInfo Constructor Again BoardInfo::BoardInfo(Widget parent, Game g) { ... canvas = XtVaCreateManagedWidget ( "canvas", xmDrawingAreaWidgetClass, parent, XmNheight, 300, XmNwidth, 300, NULL ); ... XtAddCallback(canvas, XmNexposeCallback, &BoardInfo::drawBoardCallback, (XtPointer) this); XtAddCallback(canvas, XmNinputCallback, &BoardInfo::boardClickCallback, (XtPointer) this); } • Since the clientData argument is of type XtPointer, the this pointer, which is of type BoardInfo*, must be type cast
BoardInfo Class Declaration class BoardInfo { private: Game game; // the game state Widget canvas; // drawing area Display* display; // display device for app GC gc; // graphics context public: BoardInfo(Widget parent, Game g); void drawGrid(); // draw game grid void drawX ( Integer x, Integer y); // Draw an X at (x,y) void drawO ( Integer x, Integer y); // Draw an O at (x,y) void manage(); // expose drawing area void unManage(); // hide the drawing area void asciiDisplay(); // display for debugging private: static void drawBoardCallback(Widget, XtPointer clientData, XtPointer); void drawBoard(); // draw board when it is exposed static void boardClickCallback(Widget, XtPointer clientData, XtPointer); void boardClick(); // get board coords when clicked }
Draw Board Callback Methods void BoardInfo::drawBoardCallback(Widget, XtPointer clientData, XtPointer) { Board * b = (Board *)clientData; b->drawBoard(); } void BoardInfo::drawBoard() { drawGrid(); for (Integer x = 0; x < 3; x++) // Draw the Xs & Os for (Integer y = 0; y < 3; y++) { if (game->getState(x,y) == 'X') drawX(x, y); else if (game->getState(x,y) == 'O') drawO(x, y); } }
Macros to Handle C++ Callbacks Since it's a hassle to define two methods for every callback, we can define macros to simplify both the declaration and the implementation of callbacks. #define name2(a,b) a ## b #define DECL_CALLBACK(func) \ private: \ static void name2(func,Callback) (Widget, \ XtPointer, \ XtPointer); \ protected: \ virtual void func ( Widget, XtPointer)
Callback Declaration Macro • Whenever the preprocessor sees name2(a,b), it plugs in the concatenation of a and b: • For example, name2(drawBoard,Callback) becomes drawBoardCallback • Whenever the preprocessor sees DECL_CALLBACK(func) it plugs in two method declarations. • For example, DECL_CALLBACK(drawBoard) becomes: private: \ static void drawBoardCallback (Widget, \ XtPointer, \ XtPointer); \ protected: \ virtual void drawBoard ( Widget, XtPointer)
BoardInfo Class Declaration Again class BoardInfo { private: Game game; // the game state Widget canvas; // drawing area Display* display; // display device for app GC gc; // graphics context public: BoardInfo(Widget parent, Game g); void drawGrid(); // draw game grid void drawX ( Integer x, Integer y); // Draw an X at (x,y) void drawO ( Integer x, Integer y); // Draw an O at (x,y) void manage(); // expose drawing area void unManage(); // hide the drawing area void asciiDisplay(); // display for debugging private: DECL_CALLBACK(drawBoard); // draw board when it is exposed DECL_CALLBACK(boardClick); // get board coords when clicked }
Callback Implementation Macro We can also define a macro to simplify the implementation (definition) of callbacks: #define IMPL_CALLBACK(cls, func) \ void cls::name2(func,Callback) (Widget w, \ XtPointer clientData, \ XtPointer callData) \ { \ ((cls *)clientData)->func(w, callData); \ } \ \ void cls::func(Widget w, XtPointer callData) This macro will hide the details of the communication between the static and nonstatic class methods.
Simplified Implementation of Draw Board Callback IMPL_CALLBACK(BoardInfo, drawBoard) { drawGrid(); for (Integer x = 0; x < 3; x++) for (Integer y = 0; y < 3; y++) { if (game->getState(x,y) == 'X') drawX(x, y); else if (game->getState(x,y) == 'O') drawO(x, y); } } The callback macros are in the file CallbackMacros.h in the Tic-Tac-Toe directory.
Board Click Callback • When a mouse click is detected in the board's drawing area, what must be done? • The (x,y) pixel coordinates of the click location must be determined • From these coordinates the indices of the 3x3 character array (internal representation) must be calculated • The game object must be told to process a move given these indices • To retrieve the (x,y) pixel coordinates, we must make use of the drawing area widget's call data
Using Call Data Recall the callback function prototype: void <funcname> (Widget w, // 1 XtPointer clientData, // 2 XtPointer callData); // 3 The third argument is a pointer to a data structure holding information from the widget. At a minimum: typedef struct { int reason; // coded reason for callback XEvent *event; // event causing callback } XmAnyCallbackStruct This structure can be extended in widget-specific ways.
Using Call Data (cont'd) • When call data is needed in a callback, first do: • XmAnyCallbackStruct *cbs = (XmAnyCallbackStruct *) callData; • Now we can get the general event causing the callback with: • cbs->event • From the general event we can get the specific button event: • cbs->event->xbutton • From the button event we can get the x and y pixel coordinates: • cbs->event->xbutton.x • cbs->event->xbutton.y
Board Click Callback Implementation // get board coordinates when clicked and process move IMPL_CALLBACK(BoardInfo, boardClick) { XmAnyCallbackStruct *cbs = (XmAnyCallbackStruct *) callData; Integer x = (Integer) floor(cbs->event->xbutton.x/100); Integer y = (Integer) floor(cbs->event->xbutton.y/100); game->processMove(x,y); } Since a tic-tac-toe square is 100 x 100 pixels, this will translate pixel coordinates to game board coordinates in a 3 x 3 character array. For example: (150,150) -> (1,1)