WMApp DockApp Library -- FAQ (version 0.0.4.3) Starred items below are new or significantly updated for the latest release of WMApp, 0.0.4, or successive bug-fix releases. 1. Widget Layout 1.1. Can I specify widget positions in absolute coordinates? *1.2. Help! My layout is all messed up! 1.3. Widgets that are all supposed to be the same size aren't. 1.4. How do I keep a frame from having transparent padding? *1.5. How do I get the program to behave correctly as a (WindowMaker | AfterStep) (dockapp | normal program)? 2. Callbacks and timed-execution functions *2.1. How do I write a callback function? 2.2. Can I attach a callback to widgets other than the WMButton? 2.3. Which mouse button was pressed? 2.4. At what location was the widget clicked? 2.5. What is the story with callbacks on a WMWindow? (a.k.a. executing functions at regular intervals) 3. Specific widget questions *3.1. How to use a WMCanvas? *3.2. How to use a WMEllipse? *3.3. How to create other shapes of widget? 4. Memory issues 4.1. Why do I get a (segmentation fault | runtime error saying "pure virtual function called") in my program? 1.1. Can I specify widget positions in absolute coordinates? Yes, if you really must. Make sure the widget fits inside its parent widget, or there will probably be a mess. The easiest way to do this is probably to set its position relative to its parent's: widget.setposition(WMRectangle(parent.left()+wleft, parent.top()+wtop, wwidth, wheight)); where wleft, wtop are the desired coordinates of the widget relative to the top left corner of its parent. Remember that the 56x56 pixel WMWindow will have a different offset in Afterstep vs. WindowMaker mode, so at least operate relative to a previously declared WMWindow in all of your coordinates. If you are using both absolute positioning and automatic layout with setaspectratios(), you may get unexpected results. See 1.3 for a better way. 1.2. Help! My layout is all messed up! Always lay out widgets in the order from parent to child: window.addchild(frame1); window.addchild(frame2); window.setaspectratios(2, 3); frame1.addchild(textbar); frame1.addchild(led); frame1.addchild(meterbar); textbar.setwidth(24); frame1.setaspectratios(0, 4, 1); // etc. Always set the border and padding for a frame before calling setaspectratios() on it. [As of WMApp 0.0.4, you no longer have to call setaspectratios() explicitly when a frame has only one child, or when you want all children of a frame to be of equal size.] And don't forget that borders around any widgets are included in their dimensions. 1.3. Widgets that are all supposed to be the same size aren't. This is due to rounding in the process of setting widget aspect ratios. All accumulated rounding error ends up on the last widget in the frame. (TODO: spread out the rounding error more evenly) If you are a stickler for accuracy, you can draw your widget layout on a 56x56 grid. Use the grid to determine the exact sizes, then in all calls to setaspectratios(), use these sizes. 1.4. How do I keep a frame from having transparent padding? frame.settransparency(false); Note: In order for a frame to _have_ transparent padding, its border thickness must also be zero. 1.5. How do I get the program to behave correctly as a (WindowMaker | AfterStep) (dockapp | normal program)? There are default command-line options built into the WMApp library. Currently they are as follows: -w Put the program into a "withdrawn" state suitable for use as a dockapp. This is the default in WMApp version 0.0.4.2 and later. -n Do not put the program into a "withdrawn" state; i.e., make it a normal X window. This option did not exist prior to WMApp version 0.0.4.2. -a Use a smaller window (for AfterStep Wharf) -- Do not treat any arguments starting with "-" after this argument as WMApp command-line flags. So to run your dockapp under AfterStep, for instance, you will want to say (e.g.) "./wmexample -a"; under a window manager not supporting dockapps, you will want "./wmexample -n". Under WindowMaker or BlackBox, no command-line options should be necessary. At some point in the future I will write methods that allow you to better integrate your own command-line options with these, and have a "--help" option that will print all possible dockapp arguments. 2.1. How do I write a callback function? There are two types of callback, with the prototypes void callback1(const WMApp *a, void *data) void callback2(const WMApp *a, WMWidget *w, void *data) First you write a function with one of these prototypes. For instance, to exit the application, the callback function is simply void halt(const WMApp *a, void *ignored) { a->stop(); } Any additional data needed by the callback function may be passed as a pointer to void. For convenience, you may pass in the address of one WMWidget to the second callback prototype without dealing with pointers to void. Dynamic casting in the body of the callback function may be necessary; see the example program code in example/window0.cc for details. Given this, you may attach callbacks to a WMButton or other WMCallback object as follows: button.addcallback(stop, 0 /* no additional data needed */); button.addcallback(another_callback, &other_widget, &some_data); (Notice that the name changed from setcallback to addcallback!) When a WMButton is pressed, all the callbacks will be executed in the order in which they were attached. As of version 0.0.4.3, thanks to Dick Middleton, you may attach an arbitrary pointer to a WMWidget or WMApp via its setuserdata(void *) member, and retrieve it with the getuserdata() member. This can obviate the need to pass pointers to callback functions. 2.2. Can I attach a callback to widgets other than the WMButton or WMWindow? Yes! Define a new class that inherits from both the desired widget and from WMCallback. For example, the following code lets you clear a WMHistory widget by clicking on it. Note: if you are using a pointer to the inherited class as the "void *" argument of a callback function, be sure to first statically cast it to a "WMWidget *". Dynamic casts from "void *" aren't guaranteed to work in C++. // Define the new class class WMHistoryCallback : public WMCallback, public WMHistory { public: WMHistoryCallback() : WMCallback(), WMHistory() { } }; // Callback to clear a WMHistory widget when it's clicked on void clearhistory(const WMApp *a, WMWidget *w, void *ignored) { WMHistoryCallback *h = dynamic_cast(w); // check that dynamic_cast doesn't return null pointer! maybe you // even want "std::assert(h)" instead of "if (h)" if (h) h->clear(); } int main(int argc, char **argv) { WMApp::initialize(argc, argv); WMHistoryCallback h; h.addcallback(clearhistory, &h, 0); // ... } 2.3. Which mouse button was pressed? This can be retrieved from the application class. The following code fragment is a callback that will execute only when mouse button 1 is released: void callback(WMApp *a, void *) { if (a->mouseclick().button != Button1) return; // body of function goes here } Recall that in most cases (if XFree86 has been set up correctly), the "up" and "down" directions of a mouse scroll wheel correspond to Button4 and Button5. But don't forget while designing your program interface that not everyone has a scroll-wheel mouse, or even a middle mouse button! 2.4. At what location was the widget clicked? Position of the mouse click relative to the _application_ may be retrieved within a callback via a->mouseclick().x and a->mouseclick().y . Note that these are the coordinates of the cursor when the mouse button is _released_, which is when the callbacks are executed. To make these coordinates useful, you probably want to use a->mouseclick().relative_to(w).x and .y, where w is a pointer to the widget executing the callback. This gives the coordinates of the mouseclick relative to the left and top bounds of the widget. You can get the coordinates relative only to the part of the widget INSIDE its border using the b_relative_to() function. The most recent mouse button release may also be examined outside any callback functions. Keep in mind that this will not necessarily correspond to the execution of any callback functions, since the mouse may have been clicked over a widget with no callbacks. 2.5. What is the story with callbacks on a WMWindow? (a.k.a. executing functions at regular intervals) In version 0.0.1, there existed a WMWindow::setcallback() method. This actually didn't set callbacks, but instead set functions to execute at regular intervals (for use in clocks, timing out internet connections, etc.). Since the name was confusing, in 0.0.2 I've replaced it by the WMWindow::add_timed_function() method. As with callbacks, there are two types of possible timed-execution functions: void timed_function1(WMApp *, WMWidget *); void timed_function2(WMApp *, WMWidget *, void * data); They should be attached to a window as follows: window.add_timed_function(period, timed_function1, &some_data); window.add_timed_function(period, timed_function2, &a_widget, &data); where "period" is an integer that specifies how often the timed-execution function should run, in centiseconds (e.g. to run a function every 5 seconds, use a period of 500). You can change this base time interval of 10 millisec using the WMWindow::setupdatefreq() member function. Of course, these regular intervals are not exact, since they do not take into account the times needed to redraw widgets, execute callback functions, and execute the timed-execution functions themselves. Don't rely on them for air traffic control. (Of course, Jason went ahead and did just that -- see the code in the example2 directory, or just "make wmatc") For an example use, see the code for the clock in example/window0.cc. 3.1. How to use a WMCanvas? The canvas widget may be used in two modes: buffered and unbuffered. To switch between them: wmcanvas_ptr->setbuffered(true) [or false, whichever]. In unbuffered mode (the default), any drawings upon the widget will immediately be copied to the WMWindow pixmap, and will show up the next time the WMApp::repaint() method is called. In buffered mode, changes to the WMCanvas will not be copied to the WMWindow's pixmap until the WMCanvas::display() method is called. The advantage to buffered mode is that it uses less CPU and it lets you make a number of changes that display all at once. There are a number of drawing functions available. WMCanvas is stateful, so you must set the drawing color with WMCanvas::setcolor each time you want to draw in a different color. Having set the desired color (if unset, it defaults to the traditional WMApp foreground turquoise-ish color), you may use various drawing methods. The x and y coordinates in these drawing methods are always relative to the INSIDE of the WMCanvas widget (i.e. not including the border). setcolor(Color c) Set the current drawing color to c. draw_point(int x, int y) Draw a [1-pixel wide] point at position (x,y). draw_line(int x1, int y1, int x2, int y2) Draw a line from (x1,y1) to (x2,y2). draw_lines(const vector & points) Draw a set of connected lines (think connect-the-dots) from point to point. The full list of drawing methods may of course be found in wmcanvas.h. For a somewhat trivial example use of a WMCanvas with an attached callback function (the world's dumbest paint program), see the code located in example1/window1.cc. A more interesting use is prototyped in example2/wmradar.h. 3.2. How to use a WMEllipse? To create an elliptical widget, make a new class that inherits from both the desired widget and from the WMEllipse. This will usually work trivially as shown in the two example programs. 3.3. How to create other shapes of widget? Using wmellipse.h and wmellipse.cc as models, you just have to write a "contains" method that specifies which points are inside the borders of your shape, and a "draw_border" method that tells how to draw a border for your shape. Inherit from both your shape and from some WMWidget. Clipping, execution of callbacks when the mouse is clicked inside your shape, and so on, will be done automagically by the library. 4.1. Why do I get a (segmentation fault | runtime error saying "pure virtual function called") in my program? If you write a function that returns a WMFrame or WMWindow complete with all its child widgets set up, the child widgets must have been either declared static inside that function, or else allocated on the heap with "new". Otherwise they do not exist in memory at the time the WMApp tries to display them. This is clearly bad :-) I presume that the misleading "pure virtual function called" error occurs because the run-time type identification of the C++ program sees garbage at a memory location which is supposed to be a class inherited from WMWidget. Then, having no idea what to make of the garbage, the program casts it to the base class WMWidget, resulting in a call to WMWidget::real_display(), a pure virtual function. Or something like that.