/* -*- c++ -*- FILE: WidgetsX.cpp RCS REVISION: $Revision: 1.33 $ COPYRIGHT: (c) 1999 -- 2003 Melinda Green, Don Hatch, and Jay Berkenbilt - Superliminal Software LICENSE: Free to use and modify for non-commercial purposes as long as the following conditions are adhered to: 1) Obvious credit for the source of this code and the designs it embodies are clearly made, and 2) Ports and derived versions of 4D Magic Cube programs are not distributed without the express written permission of the authors. DESCRIPTION: This file does the menus and buttons and stuff using Athena Widgets. */ #include "WidgetsX.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for dbx */ #include "MagicCube.h" #include "EventHandler.h" #include "MacroManager.h" // XXX std::map WidgetsX::apply_to_ui_data; std::map WidgetsX::invert_to_ui_data; void WidgetsX::macroApply_cb(Widget widget, XtPointer arg, XtPointer) { EventHandler* e = (EventHandler *) arg; MacroUIData* which = apply_to_ui_data[widget]; e->applyMacro_cb((void*) which); } void WidgetsX::macroInvert_cb(Widget widget, XtPointer arg, XtPointer) { EventHandler* e = (EventHandler *) arg; MacroUIData* which = invert_to_ui_data[widget]; e->invertMacro_cb((void*) which); } void WidgetsX::callCallback(Widget, XtPointer arg, XtPointer) { EventHandler::Callback *callback = (EventHandler::Callback *) arg; callback->apply(); } void WidgetsX::createMacrosForm() { // FIX THIS -- hard-coded values static char const *macroslabel = "Macros:"; static char const *addlabel = "Create"; static char const *deletelabel = "Delete"; /* * Create the macro box */ bool macros_on_right = preferences.getBoolProperty(M4D_MACROS_ON_RIGHT); if (macros_on_right) { macrosform = XtVaCreateManagedWidget( "macrosform", formWidgetClass, bigform, XtNdefaultDistance, 0, /* since all children are boxes */ XtNwidth, 100, XtNheight, 100, XtNresizable, TRUE, XtNfromHoriz, drawingwindow, XtNvertDistance, 0, /* * Chain it to the top right of the form, so it doesn't * change size with the form */ XtNtop, XtChainTop, XtNbottom, XtChainTop, XtNleft, XtChainRight, XtNright, XtChainRight, NULL); } else { macrosform = XtVaCreateManagedWidget( "macrosform", formWidgetClass, bigform, XtNdefaultDistance, 0, /* since all children are boxes */ XtNresizable, TRUE, XtNfromVert, buttonbox, XtNvertDistance, 0, /* * Chain it to the bottom left of the form, so it doesn't * change size with the form */ XtNtop, XtChainBottom, XtNbottom, XtChainBottom, XtNleft, XtChainLeft, XtNright, XtChainLeft, NULL); } Widget thismacrobox = XtVaCreateManagedWidget("thismacrobox", boxWidgetClass, macrosform, XtNresizable, TRUE, XtNorientation, XtorientHorizontal, XtNborderWidth, 0, NULL); /* * Create the label and permanent buttons in the macro box */ (void)XtVaCreateManagedWidget("macros label", labelWidgetClass, thismacrobox, XtNlabel, macroslabel, XtNborderWidth, 0, NULL); Widget addbutton = XtVaCreateManagedWidget("add button", commandWidgetClass, thismacrobox, XtNlabel, addlabel, NULL); XtAddCallback(addbutton, XtNcallback, callCallback, event_handler->createCallback(&EventHandler::add_cb, 0)); deletebutton = XtVaCreateWidget( /* not managed! */ "delete button", commandWidgetClass, thismacrobox, XtNlabel, deletelabel, NULL); XtAddCallback(deletebutton, XtNcallback, callCallback, event_handler->createCallback(&EventHandler::delete_cb, 0)); int fast_automoves = preferences.getBoolProperty(M4D_FAST_AUTOMOVES); fast_toggle = XtVaCreateManagedWidget("fast", toggleWidgetClass, thismacrobox, XtNlabel, "fast", XtNstate, fast_automoves, NULL); XtAddCallback(fast_toggle, XtNcallback, callCallback, event_handler->createCallback(&EventHandler::toggleFast_cb, 0)); this->macrocontrols = thismacrobox; if (fast_automoves) { this->event_handler->toggleFast_cb(0); } } /* * Note: this is to be called from machine_init, NOT main! */ WidgetsX::WidgetsX(Preferences& p, EventHandler* event_handler, Widget toplevel, Widget& drawingwindow) : Widgets(p), event_handler(event_handler), buttonbox(0), macrosform(0), toplevel(toplevel), bigform(0), drawingwindow(0), deletebutton(0), macrocontrols(0), fast_toggle(0), cur_transientshell(-1) { int i; for (i = 0; i < MAX_DIALOGS; ++i) { transientshells[i] = 0; dialog_callbacks[i] = 0; } Widget toplevelform; /* * Toplevelform is a "buffer" between toplevel and bigform. * Its purpose is to allow bigform to always take on its preferred size * (since toplevel denies resize requests). * Then when we know what that size is, we can resize toplevel * explicitly by calling resize_toplevel_to_match_bigform(). */ toplevelform = XtVaCreateManagedWidget("toplevelform", formWidgetClass, toplevel, XtNborderWidth, 0, XtNdefaultDistance, 0, NULL); /* * Create the big form in which to put everything */ bigform = XtVaCreateManagedWidget("bigform", formWidgetClass, toplevelform, XtNborderWidth, 0, XtNresizable, TRUE, XtNtop, XtChainTop, XtNbottom, XtChainBottom, XtNleft, XtChainLeft, XtNright, XtChainRight, NULL); /* * Make the drawing window a square window in the upper left */ drawingwindow = XtVaCreateManagedWidget("drawingwindow", coreWidgetClass, bigform, XtNtop, XtChainTop, XtNbottom, XtChainBottom, XtNleft, XtChainLeft, XtNright, XtChainRight, NULL); this->drawingwindow = drawingwindow; if (preferences.getBoolProperty(M4D_NO_BUTTONS)) { return; } /* * Create the main button box below the drawing window */ buttonbox = XtVaCreateManagedWidget("buttonbox", boxWidgetClass, bigform, XtNorientation, XtorientHorizontal, XtNborderWidth, 0, XtNresizable, TRUE, XtNhorizDistance, 0, XtNvertDistance, 0, XtNfromVert, drawingwindow, /* * Chain it to the bottom left * of the form, so it doesn't * change size with the form */ XtNtop, XtChainBottom, XtNbottom, XtChainBottom, XtNleft, XtChainLeft, XtNright, XtChainLeft, NULL); /* * Buttons will be added to buttonbox by using widgets_add_button(). */ createMacrosForm(); } WidgetsX::~WidgetsX() { // event_handler is externally controlled int i; for (i = 0; i < MAX_DIALOGS; ++i) { delete [] dialog_callbacks[i]; } } extern void WidgetsX::addMenuButton(char *name, int nbuttons, ButtonData button_data[]) { if (preferences.getBoolProperty(M4D_NO_BUTTONS)) { return; } char namebuf[80]; // bounds checking is performed Widget menu, entry; /* * Often the label ends in "...", which confuses * the menu-finding mechanism. Delete that part when naming the menu. */ namebuf[sizeof(namebuf) - 1] = '\0'; strncpy(namebuf, name, sizeof(namebuf) - 1); while (*namebuf && namebuf[strlen(namebuf) - 1] == '.') namebuf[strlen(namebuf) - 1] = '\0'; /* * Create the menu */ menu = XtVaCreatePopupShell(namebuf, /* without the "..." */ simpleMenuWidgetClass, toplevel, NULL); /* * Create the menu entries */ int i; for (i = 0; i < nbuttons; ++i) { if (button_data[i].label) { entry = XtVaCreateManagedWidget(button_data[i].label, smeBSBObjectClass, menu, NULL); XtAddCallback(entry, XtNcallback, callCallback, event_handler->createCallback( button_data[i].callback, button_data[i].cb_arg)); } } /* * Create the menu button and add it to the main button box */ (void)XtVaCreateManagedWidget( "menu button", menuButtonWidgetClass, buttonbox, XtNlabel, name, /* maybe with "..." */ XtNmenuName, preferences.getBoolProperty(M4D_NO_STRDUP_FIX) ? namebuf : strdup(namebuf), /* * I don't know why the hell I needed to do that, * but if I just use namebuf, I get "Can't find menu named ." * when the menu button is hit. * FIX THIS-- make sure it is a widget bug and not mine. */ NULL); } void WidgetsX::addButton(ButtonData button_data) { if (preferences.getBoolProperty(M4D_NO_BUTTONS)) { return; } Widget thebutton; thebutton = XtVaCreateManagedWidget("button", commandWidgetClass, buttonbox, XtNlabel, button_data.label, NULL); XtAddCallback(thebutton, XtNcallback, callCallback, event_handler->createCallback( button_data.callback, button_data.cb_arg)); } void WidgetsX::resizeToplevelToMatchBigform() { #if 0 Dimension width, height; XtVaGetValues(bigform, XtNwidth, &width, XtNheight, &height, NULL); /* * Chain bigform to top left of toplevelform so it doesn't move or * resize */ XtVaSetValues(bigform, XtNtop, XtChainTop, XtNbottom, XtChainTop, XtNleft, XtChainLeft, XtNright, XtChainLeft, NULL); XtVaSetValues(toplevel, XtNwidth, width, XtNheight, height, NULL); /* * Chain bigform back again so that it will resize if toplevel * resizes later */ XtVaSetValues(bigform, XtNtop, XtChainTop, XtNbottom, XtChainBottom, XtNleft, XtChainLeft, XtNright, XtChainRight, NULL); #endif } /* * Use this function to get the size * of a widget that's not managed yet. */ void WidgetsX::XtForceDimensionsOfUnmanagedWidget(Widget w) { Arg args[2]; int n; n = 0; XtSetArg(args[n], XtNmappedWhenManaged, False); n++; XtSetValues(w, args, n); XtManageChild(w); XtUnmanageChild(w); n = 0; XtSetArg(args[n], XtNmappedWhenManaged, True); n++; XtSetValues(w, args, n); } void WidgetsX::XtGetLowerRight(Widget w, Position *x, Position *y) { Dimension width, height; XtVaGetValues(w, XtNwidth, &width, XtNheight, &height, NULL); XtTranslateCoords(w, width, height, x, y); } void WidgetsX::XtSetLowerRight(Widget w, Position x, Position y) { Dimension width, height; XtVaGetValues(w, XtNwidth, &width, XtNheight, &height, NULL); XtVaSetValues(w, XtNx, x - width, XtNy, y - height, NULL); } void WidgetsX::XtGetCenter(Widget w, Position *x, Position *y) { Dimension width, height; XtVaGetValues(w, XtNwidth, &width, XtNheight, &height, NULL); XtTranslateCoords(w, width / 2, height / 2, x, y); } void WidgetsX::XtSetCenter(Widget w, Position x, Position y) { Dimension width, height; XtVaGetValues(w, XtNwidth, &width, XtNheight, &height, NULL); XtVaSetValues(w, XtNx, x - width / 2, XtNy, y - height / 2, NULL); } char* WidgetsX::getMacroName(int i) { if (macrosform == 0) { return 0; } Cardinal numchildren; WidgetList children; String str; // "string" may conflict with STL... XtVaGetValues(macrosform, XtNchildren, &children, XtNnumChildren, &numchildren, NULL); if (!INRANGE(0 <=, i, <(int)numchildren - 1)) { fprintf(stderr, "widgets_get_macro_name: tried to get name %d out of %d\n", i, numchildren); return 0; } /* * We are interested in child number 2 of the macro box. */ XtVaGetValues(children[i + 1], XtNchildren, &children, NULL); XtVaGetValues(children[2], XtNstring, &str, NULL); return str; /* maybe should use XawAsciiSourceFreeString? Nahhh. */ } void* WidgetsX::addMacro(char *name, void* prev_ui_data) { if (macrosform == 0) { // ignore macro display in no-buttons mode return 0; } Widget thismacrobox, callbutton, callinvbutton, macroname; MacroUIData* ui_data = (MacroUIData*) prev_ui_data; Widget prev = (ui_data ? ui_data->box : this->macrocontrols); thismacrobox = XtVaCreateWidget("thismacrobox", boxWidgetClass, macrosform, XtNorientation, XtorientHorizontal, XtNborderWidth, 0, XtNfromVert, prev, XtNresizable, FALSE, NULL); callbutton = XtVaCreateManagedWidget("call a macro", commandWidgetClass, thismacrobox, XtNlabel, "Apply", NULL); XtAddCallback(callbutton, XtNcallback, macroApply_cb, (void*) event_handler); callinvbutton = XtVaCreateManagedWidget("call inverse of a macro", commandWidgetClass, thismacrobox, XtNlabel, "Inverse", NULL); XtAddCallback(callinvbutton, XtNcallback, macroInvert_cb, (void*) event_handler); ui_data = new MacroUIData(thismacrobox, callbutton, callinvbutton, ui_data); if (ui_data->prev) { ui_data->prev->next = ui_data; } this->apply_to_ui_data[callbutton] = ui_data; this->invert_to_ui_data[callinvbutton] = ui_data; macroname = XtVaCreateManagedWidget("name of a macro", asciiTextWidgetClass, thismacrobox, XtNstring, name, XtNinsertPosition, strlen(name), XtNeditType, XawtextEdit, XtNresize, XawtextResizeBoth, XtNresizable, FALSE, /* tell parent it's OK */ NULL); /* * (The following is partially stolen from xgc and xedit) */ /* * Disable keys which would cause the cursor to go off the single * line that we want to display. * Also disable the "@" key because that's the delimiter in the log file. * Also set ^U and ^W to do what I like, because it's my program. */ XtOverrideTranslations(macroname, XtParseTranslationTable("\ CtrlJ: end-of-line()\n\ CtrlM: end-of-line()\n\ Linefeed: end-of-line()\n\ Return: end-of-line()\n\ CtrlO: end-of-line()\n\ MetaI: end-of-line()\n\ CtrlN: end-of-line()\n\ CtrlP: end-of-line()\n\ CtrlZ: end-of-line()\n\ MetaZ: end-of-line()\n\ CtrlV: end-of-line()\n\ MetaV: end-of-line()\n\ Shift@: no-op()\n\ CtrlU: beginning-of-line() kill-to-end-of-line()\n\ CtrlW: backward-kill-word()\n\ ")); /* * A quirk of the text widget makes it so that the "resize" resource * doesn't take effect until something about the widget is changed. * Need to figure out how to trigger it immediately. * The following didn't do it. I don't know how to do it. */ /* XawTextSetInsertionPoint(macroname, (XawTextPosition)strlen(name)); */ /* XtSetValues(macroname, XtNstring, name, NULL); */ /* XtForceDimensionsOfUnmanagedWidget(thismacrobox); */ /// { /// WidgetList children; /// Cardinal nchildren; /// XtVaGetValues(this->macrosform, /// XtNchildren, &children, XtNnumChildren, &nchildren, NULL); /// XtUnmanageChildren(children, nchildren); /// XtManageChildren(children, nchildren); /// } XtManageChild(deletebutton); XtManageChild(thismacrobox); resizeToplevelToMatchBigform(); return (void*) ui_data; } void WidgetsX::removeMacro(void* ui_data_v) { MacroUIData* ui_data = (MacroUIData*) ui_data_v; if (ui_data->next) { ui_data->next->prev = ui_data->prev; } if (ui_data->prev) { ui_data->prev->next = ui_data->next; } Cardinal numchildren; XtVaGetValues(macrosform, XtNnumChildren, &numchildren, NULL); // Actually need to unmanage the child as well as destroy it, // Since destroying doesn't do anything until the callbacks // are completed, and we need the big form to redo its layout // right now. XawFormDoLayout(bigform, False); XawFormDoLayout(macrosform, False); XtUnmanageChild(ui_data->box); if (numchildren == 2) /* if it was 2 */ XtUnmanageChild(deletebutton); if (ui_data->next) { if (ui_data->prev) { XtVaSetValues(ui_data->next->box, XtNfromVert, ui_data->prev->box, NULL); } else { XtVaSetValues(ui_data->next->box, XtNfromVert, this->macrocontrols, NULL); } } XawFormDoLayout(macrosform, True); XawFormDoLayout(bigform, True); resizeToplevelToMatchBigform(); XtDestroyWidget(ui_data->box); this->apply_to_ui_data.erase(ui_data->apply); this->invert_to_ui_data.erase(ui_data->invert); delete ui_data; } void WidgetsX::updateFastButton(int fast_automoves) { XtVaSetValues(this->fast_toggle, XtNstate, fast_automoves, NULL); } void WidgetsX::createDialog(char *s, int nbuttons, ButtonData* button_data) { if (cur_transientshell == (MAX_DIALOGS - 1)) { // FIX THIS if it's worth it fprintf(stderr, "need to increase MAX_DIALOGS in WidgetsX.cpp\n"); return; } Position centerx, centery; Widget dialog; Widget transientshell = XtVaCreatePopupShell( "", /* (gasp) this string actually appears! */ transientShellWidgetClass, toplevel, NULL); dialog = XtVaCreateManagedWidget("dialog", dialogWidgetClass, transientshell, XtNlabel, s, XtNborderWidth, 0, XtRGravity, XtESouthEast, NULL); ++cur_transientshell; dialog_callbacks[cur_transientshell] = new EventHandler::Callback*[nbuttons]; int i; for (i = 0; i < nbuttons; ++i) { dialog_callbacks[cur_transientshell][i] = event_handler->createCallback( button_data[i].callback, button_data[i].cb_arg); XawDialogAddButton(dialog, button_data[i].label, callCallback, dialog_callbacks[cur_transientshell][i]); } XtForceDimensionsOfUnmanagedWidget(transientshell); XtGetLowerRight(toplevel, ¢erx, ¢ery); // FIX THIS: forcibly setting coordinates here disregards window // manager decorations. The attempt to use XtRGravity above // didn't help. We'll just kludge around it by substracting some // safe number of pixels. XtSetLowerRight(transientshell, centerx - 30, centery - 30); XtPopup(transientshell, XtGrabNone); transientshells[cur_transientshell] = transientshell; } void WidgetsX::destroyDialog() { if (cur_transientshell >= 0) { if (cur_transientshell < MAX_DIALOGS) { Widget transientshell = transientshells[cur_transientshell]; if (transientshell) { XtUnmapWidget(transientshell); XtDestroyWidget(transientshell); transientshells[cur_transientshell] = 0; delete dialog_callbacks[cur_transientshell]; dialog_callbacks[cur_transientshell] = 0; } } --cur_transientshell; } } void WidgetsX::debuggingHack(MacroManager* macromgr) { static int i = 0; WidgetList children; Cardinal nchildren; if (this->macrosform) { XtVaGetValues(this->macrosform, XtNchildren, &children, XtNnumChildren, &nchildren, NULL); } void* prev_ui_data = 0; for (int i = 0; i < macromgr->getNMacros(); ++i) { void* ui_data = this->addMacro( macromgr->getMacroName(i), prev_ui_data); macromgr->setUIData(i, ui_data); prev_ui_data = ui_data; } i = (1 + i) % 4; } #ifdef hpux #warning "This stuff shouldn't be ifdef hpux" /* * Stubs for things used by libXaw.a on certain hp machines * but not defined anywhere. * Hopefully they are never called... */ Bool XShapeQueryExtension(Display * dpy, int *event_basep, int *error_basep) { fprintf(stderr, "In XShapeQueryExtension stub\n"); return False; /* don't know what I'm doing */ } void XShapeCombineMask() { fprintf(stderr, "In XShapeCombineMask stub\n"); } #endif /* hpux */ // Local Variables: // c-basic-offset: 4 // c-comment-only-line-offset: 0 // c-file-offsets: ((defun-block-intro . +) (block-open . 0) (substatement-open . 0) (statement-cont . +) (statement-case-open . +4) (arglist-intro . +) (arglist-close . +) (inline-open . 0)) // indent-tabs-mode: nil // End: