Logo Search packages:      
Sourcecode: ddd version File versions  Download package

MakeMenu.C

// $Id$
// Create custom Motif Menus

// Copyright (C) 1995-1998 Technische Universitaet Braunschweig, Germany.
// Written by Andreas Zeller <zeller@gnu.org>.
// 
// This file is part of DDD.
// 
// DDD is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
// 
// DDD is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with DDD -- see the file COPYING.
// If not, write to the Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// 
// DDD is the data display debugger.
// For details, see the DDD World-Wide-Web page, 
// `http://www.gnu.org/software/ddd/',
// or send a mail to the DDD developers <ddd@gnu.org>.

char MakeMenu_rcsid[] = 
    "$Id$";

#include "MakeMenu.h"

#include "assert.h"
#include "strclass.h"
#include "MString.h"
#include "TimeOut.h"
#include "misc.h"
#include "string-fun.h"
#include "charsets.h"
#include "wm.h"

#include <stdlib.h>
#include <Xm/Xm.h>
#include <Xm/RowColumn.h>
#include <Xm/CascadeB.h>
#include <Xm/PushB.h>
#include <Xm/ToggleB.h>
#include <Xm/ArrowB.h>
#include <Xm/CascadeB.h>
#include <Xm/Separator.h>
#include <Xm/Scale.h>
#include <Xm/TextF.h>
#include <Xm/Label.h>
#include <Xm/List.h>
#include <Xm/MenuShell.h>
#include <X11/Xutil.h>

#include "LessTifH.h"
#include "bool.h"
#include "verify.h"
#include "findParent.h"
#include "frame.h"
#include "ComboBox.h"
#include "SpinBox.h"
#include "AutoRaise.h"

#ifndef LOG_FLATTENING
#define LOG_FLATTENING 0
#endif

#ifndef LOG_PUSH_MENUS
#define LOG_PUSH_MENUS 0
#endif


// Pushmenu callbacks
static void ArmPushMenuCB(Widget, XtPointer, XtPointer);
static void RedrawPushMenuCB(Widget, XtPointer, XtPointer);
static void PopupPushMenuAct(Widget w, XEvent* e, String *, Cardinal *);
static void DecoratePushMenuAct(Widget w, XEvent* e, String *, Cardinal *);

static XtActionsRec actions [] = {
    {XTARECSTR("popup-push-menu"),            PopupPushMenuAct },
    {XTARECSTR("decorate-push-menu"),         DecoratePushMenuAct },
};

static const char *pushMenuTranslations = 
    "<Expose>:          decorate-push-menu()\n"
;

static const char *lesstif_pushMenuTranslations = 
    "None<Btn3Down>:    popup-push-menu()\n"
;

00096 struct PushMenuInfo {
    Widget widget;            // The PushButton
    Widget subMenu;           // Submenu of this PushButton
    bool flat;                // Whether the PushButton is flattened
    XtIntervalId timer;       // Timer while waiting

    PushMenuInfo(Widget w, Widget s, bool f)
      : widget(w), subMenu(s), flat(f), timer(0)
    {}

private:
    PushMenuInfo(const PushMenuInfo&);
    PushMenuInfo& operator= (const PushMenuInfo&);
};

//-----------------------------------------------------------------------
// Flat buttons
//-----------------------------------------------------------------------

// The currently unflattened button
static Widget active_button = 0;

static void flatten_button(Widget w, bool switch_colors = true)
{
    Pixel background;
    Pixmap highlight_pixmap, label_pixmap;
    Pixmap bottom_shadow_pixmap;
    XtVaGetValues(w,
              XmNbackground,         &background,
              XmNlabelPixmap,        &label_pixmap,
              XmNhighlightPixmap,    &highlight_pixmap,
              XmNbottomShadowPixmap, &bottom_shadow_pixmap,
              XtPointer(0));

    if (bottom_shadow_pixmap == XmUNSPECIFIED_PIXMAP)
    {
#if LOG_FLATTENING
      std::clog << "Flattening " << XtName(w) << "\n";
#endif

      Arg args[10];
      Cardinal arg = 0;

      Pixmap empty = XmGetPixmap(XtScreen(w), XMST("background"), 
                           background, background);

      XtSetArg(args[arg], XmNbottomShadowPixmap, empty); arg++;
      XtSetArg(args[arg], XmNtopShadowPixmap,    empty); arg++;

      if (switch_colors)
      {
          XtSetArg(args[arg], XmNlabelPixmap, highlight_pixmap); arg++;
          XtSetArg(args[arg], XmNhighlightPixmap, label_pixmap); arg++;
      }

      XtSetValues(w, args, arg);
    }
}

static void unflatten_button(Widget w, bool switch_colors = true)
{
    Pixel background;
    Pixmap highlight_pixmap, label_pixmap;
    Pixmap bottom_shadow_pixmap;
    XtVaGetValues(w,
              XmNbackground,         &background,
              XmNlabelPixmap,        &label_pixmap,
              XmNhighlightPixmap,    &highlight_pixmap,
              XmNbottomShadowPixmap, &bottom_shadow_pixmap,
              XtPointer(0));

    if (bottom_shadow_pixmap != XmUNSPECIFIED_PIXMAP)
    {
#if LOG_FLATTENING
      std::clog << "Unflattening " << XtName(w) << "\n";
#endif

      Arg args[10];
      Cardinal arg = 0;

      XtSetArg(args[arg], XmNbottomShadowPixmap, 
             XmUNSPECIFIED_PIXMAP); arg++;
      XtSetArg(args[arg], XmNtopShadowPixmap,
             XmUNSPECIFIED_PIXMAP); arg++;

      if (switch_colors)
      {
          XtSetArg(args[arg], XmNlabelPixmap, highlight_pixmap); arg++;
          XtSetArg(args[arg], XmNhighlightPixmap, label_pixmap); arg++;
      }

      XtSetValues(w, args, arg);
    }
}


static void FlattenEH(Widget w,
                  XtPointer /* client_data */,
                  XEvent *event, 
                  Boolean * /* continue_to_dispatch */)
{
    if (event->xcrossing.state & (Button1Mask | Button2Mask | Button3Mask | 
                          Button4Mask | Button5Mask))
      return;                 // Button is still pressed

    switch (event->type)
    {
    case EnterNotify:
    {
#if LOG_FLATTENING
      std::clog << "Entering " << XtName(w) << "\n";
#endif

      unflatten_button(w);
      active_button = w;
      break;
    }

    case LeaveNotify:
    {
#if LOG_FLATTENING
      std::clog << "Leaving " << XtName(w) << "\n";
#endif

      flatten_button(w);
      active_button = 0;
      break;
    }
    }
}

// Handle Arm() and Disarm() actions
static void FlattenCB(Widget w, XtPointer client_data, XtPointer)
{
    if (w == active_button)
    {
      // We have already entered it -- don't interfere
      return;
    }

    bool set = bool(client_data);
    if (set)
    {
      // std::clog << "Arming " << XtName(w) << "\n";

      flatten_button(w, false);
    }
    else
    {
      // std::clog << "Disarming " << XtName(w) << "\n";

      unflatten_button(w, false);
    }
}

static void ReflattenButtonCB(Widget /* shell */, XtPointer client_data, 
                        XtPointer = 0)
{
    Widget w = (Widget)client_data;
    EventMask event_mask = EnterWindowMask | LeaveWindowMask;
    XtAddEventHandler(w, event_mask, False, FlattenEH, XtPointer(0));
    XtAddCallback(w, XmNarmCallback,    FlattenCB, XtPointer(False));
    XtAddCallback(w, XmNdisarmCallback, FlattenCB, XtPointer(True));
    flatten_button(w);
}


//-----------------------------------------------------------------------
// Add items
//-----------------------------------------------------------------------

// Add items to shell.  If IGNORE_SEPS is set, all separators are ignored.
void MMaddItems(Widget shell, MMDesc items[], bool ignore_seps)
{
    static bool actions_added = false;
            
    if (!actions_added)
    {
      XtAppAddActions(XtWidgetToApplicationContext(shell), 
                  actions, XtNumber(actions));
      actions_added = true;
    }

    Arg args[10];
    int arg;

    static const string textName  = "text";
    static const string labelName = "label";
    // Create lots of buttons...
    for (MMDesc *item = items; item != 0 && item->name != 0; item++)
    {
      const char * const name = item->name;
      MMType flags            = item->type;
      MMType type             = flags & MMTypeMask;
      Widget& widget          = item->widget;
      Widget *widgetptr       = item->widgetptr;
      MMDesc *subitems        = item->items;
      Widget& label           = item->label;

      if (flags & MMIgnore)
          continue;           // Don't create

      const string subMenuName = string(name) + "Menu";
      Widget subMenu = 0;
      Widget panel   = 0;
      bool flat = false;
      label = 0;
      widget = 0;

      switch(type) 
      {
      case MMFlatPush:
      {
          flat = true;
          // FALL THROUGH
      }

      case MMPush:
      {
          // Create a PushButton
          arg = 0;
          if (flat)
          {
            Pixel background;
            XtVaGetValues(shell, XmNbackground, &background, XtPointer(0));
            Pixmap empty = XmGetPixmap(XtScreen(shell), 
                                 XMST("background"), 
                                 background, background);

            XtSetArg(args[arg], XmNbottomShadowPixmap, empty); arg++;
            XtSetArg(args[arg], XmNtopShadowPixmap,    empty); arg++;
            XtSetArg(args[arg], XmNhighlightThickness, 0);     arg++;
            XtSetArg(args[arg], XmNshadowThickness,    2);     arg++;
          }
          else
          {
            XtSetArg(args[arg], 
                   XmNhighlightPixmap, XmUNSPECIFIED_PIXMAP); arg++;
          }

          PushMenuInfo *info = 0;
          if (lesstif_version <= 84)
          {
            // LessTif 0.84 and earlier wants the PushButton as
            // parent of the menu
            widget = verify(XmCreatePushButton(shell, XMST(name), args, arg));

            if (subitems != 0)
            {
                subMenu = MMcreatePushMenu(widget, subMenuName.chars(), subitems);
                info = new PushMenuInfo(widget, subMenu, flat);
                XtVaSetValues(widget,
                          XmNuserData, XtPointer(info),
                          XtPointer(0));
            }
          }
          else
          {
            // Motif wants the shell as parent of the menu
            if (subitems != 0)
            {
                subMenu = MMcreatePushMenu(shell, subMenuName.chars(), subitems);
                info = new PushMenuInfo(0, subMenu, flat);
                XtSetArg(args[arg], XmNuserData, XtPointer(info)); arg++;
            }

            widget = verify(XmCreatePushButton(shell, XMST(name), args, arg));

            if (info != 0)
                info->widget = widget;
          }
          break;
      }

      case MMToggle:
      {
          // Create a ToggleButton
          assert(subitems == 0);

          arg = 0;
          widget = verify(XmCreateToggleButton(shell, XMST(name), args, arg));
          break;
      }

      case MMLabel:
      {
          // Create a Label
          assert(subitems == 0);

          arg = 0;
          widget = verify(XmCreateLabel(shell, XMST(name), args, arg));
          break;
      }

      case MMArrow:
      {
          // Create an arrow
          assert(subitems == 0);

          arg = 0;
          widget = verify(XmCreateArrowButton(shell, XMST(name), args, arg));
          break;
      }

      case MMMenu:
      {
          // Create a CascadeButton and a new PulldownMenu
          assert(subitems != 0);

          subMenu = MMcreatePulldownMenu(shell, subMenuName.chars(), subitems);

          arg = 0;
          XtSetArg(args[arg], XmNsubMenuId, subMenu); arg++;
          widget = verify(XmCreateCascadeButton(shell, XMST(name), args, arg));

            if (lesstif_version <= 79)
          {
            // LessTif 0.79 and earlier has a very tight packing
            // of menu items; place a few spaces around the labels
            // to increase item distance.
            XmString old_label;
            XtVaGetValues(widget, 
                        XmNlabelString, &old_label,
                        XtPointer(0));
            MString new_label(old_label, true);
            XmStringFree(old_label);

            if (!new_label.isNull())
            {
                new_label = MString("  ") + new_label + MString("  ");
                XtVaSetValues(widget, 
                          XmNlabelString, new_label.xmstring(), 
                          XtPointer(0));
            }

            // Same applies to accelerator texts.
            XmString old_acc;
            XtVaGetValues(widget,
                        XmNacceleratorText, &old_acc,
                        XtPointer(0));
            MString new_acc(old_acc, true);
            XmStringFree(old_acc);

            if (!new_acc.isNull())
            {
                new_acc = MString("  ") + new_acc;
                XtVaSetValues(widget, 
                          XmNacceleratorText, new_acc.xmstring(), 
                          XtPointer(0));
            }
          }
          break;
      }

      case MMRadioMenu:
      {
          // Create a CascadeButton and a new PulldownMenu
          assert(subitems != 0);

          subMenu = MMcreateRadioPulldownMenu(shell, subMenuName.chars(), subitems);

          arg = 0;
          XtSetArg(args[arg], XmNsubMenuId, subMenu); arg++;
          widget = verify(XmCreateCascadeButton(shell, XMST(name), args, arg));
          break;
      }

      case MMOptionMenu:
      {
          // Create an option menu
          assert(subitems != 0);

          subMenu = MMcreatePulldownMenu(shell, subMenuName.chars(), subitems);

          arg = 0;
          XtSetArg(args[arg], XmNsubMenuId, subMenu); arg++;
          widget = verify(XmCreateOptionMenu(shell, XMST(name), args, arg));
          break;
      }

      case MMPanel:
      case MMRadioPanel:
      case MMButtonPanel:
      {
          // Create a label with an associated panel
          assert(subitems != 0);
          const string panelName   = string(name) + "Panel";

          bool have_label = 
            (name[0] != '\0' && (flags & MMUnmanagedLabel) == 0);

          arg = 0;
          XtSetArg(args[arg], XmNorientation, XmHORIZONTAL); arg++;
          XtSetArg(args[arg], XmNborderWidth,     0); arg++;
          XtSetArg(args[arg], XmNentryBorder,     0); arg++;
          XtSetArg(args[arg], XmNspacing,         0); arg++;
          XtSetArg(args[arg], XmNmarginWidth,     0); arg++;
          XtSetArg(args[arg], XmNmarginHeight,    0); arg++;
          XtSetArg(args[arg], XmNshadowThickness, 0); arg++;

          widget = verify(XmCreateRowColumn(shell, XMST(panelName.chars()), args, arg));

          arg = 0;
          label = verify(XmCreateLabel(widget, XMST(name), args, arg));
          if (have_label)
            XtManageChild(label);

          Widget (*create_panel)(Widget, const _XtString, MMDesc[], 
                           ArgList, Cardinal) = 0;

          switch (type)
          {
          case MMPanel:
            create_panel = MMcreatePanel;
            break;

          case MMRadioPanel:
            create_panel = MMcreateRadioPanel;
            break;

          case MMButtonPanel:
            create_panel = MMcreateButtonPanel;
            break;

          default:
            assert(0);
            abort();
          }

          arg = 0;
          XtSetArg(args[arg], XmNorientation, 
                 (flags & MMVertical) ? XmVERTICAL : XmHORIZONTAL); arg++;

          if (!have_label)
          {
            XtSetArg(args[arg], XmNborderWidth,     0); arg++;
            XtSetArg(args[arg], XmNentryBorder,     0); arg++;
            XtSetArg(args[arg], XmNspacing,         0); arg++;
            XtSetArg(args[arg], XmNmarginWidth,     0); arg++;
            XtSetArg(args[arg], XmNmarginHeight,    0); arg++;
            XtSetArg(args[arg], XmNshadowThickness, 0); arg++;
          }

          subMenu = create_panel(widget, subMenuName.chars(), subitems, args, arg);

          XtManageChild(subMenu);
          break;
      }

      case MMScale:
      {
          // Create a scale
          assert(subitems == 0);

          arg = 0;
          widget = verify(XmCreateScale(shell, XMST(name), args, arg));
          break;
      }

      case MMSpinBox:
      case MMComboBox:
      case MMTextField:
      case MMEnterField:
      {
          // Create a label with an associated text field
          assert(subitems == 0);

          arg = 0;
          XtSetArg(args[arg], XmNorientation,     XmHORIZONTAL); arg++;
          XtSetArg(args[arg], XmNborderWidth,     0); arg++;
          XtSetArg(args[arg], XmNentryBorder,     0); arg++;
          XtSetArg(args[arg], XmNspacing,         0); arg++;
          XtSetArg(args[arg], XmNmarginWidth,     0); arg++;
          XtSetArg(args[arg], XmNmarginHeight,    0); arg++;
          XtSetArg(args[arg], XmNshadowThickness, 0); arg++;

          panel = verify(XmCreateRowColumn(shell, XMST(name), args, arg));

          arg = 0;
          label = verify(XmCreateLabel(panel, XMST(labelName.chars()), args, arg));
          if (name[0] != '\0' && (flags & MMUnmanagedLabel) == 0)
            XtManageChild(label);

          switch (type)
          {
          case MMSpinBox:
            arg = 0;
            widget = CreateSpinBox(panel, textName.chars(), args, arg);
            break;

          case MMComboBox:
            arg = 0;
            widget = CreateComboBox(panel, textName.chars(), args, arg);
            break;

          case MMTextField:
          case MMEnterField:
            arg = 0;
            widget = verify(XmCreateTextField(panel, XMST(textName.chars()), args, arg));
            XtManageChild(widget);
            break;
          }
          break;
      }

      case MMSeparator:
      {
          // Create a separator
          assert(subitems == 0);

          if (ignore_seps)
            continue;
          arg = 0;
          widget = verify(XmCreateSeparator(shell, XMST(name), args, arg));
          break;
      }

      default:
          // Invalid type
          assert(0);
          abort();
      }

      if (flags & MMHelp)
      {
          arg = 0;
          XtSetArg(args[arg], XmNmenuHelpWidget, item->widget); arg++;
          XtSetValues(shell, args, arg);
      }

      if (panel == 0)
          panel = widget;

      if (flags & MMInsensitive)
          set_sensitive(panel, false);

      if (!(flags & MMUnmanaged))
          XtManageChild(panel);

      if (widgetptr != 0)
          *widgetptr = widget;
    }
}


//-----------------------------------------------------------------------
// Custom menu creation
//-----------------------------------------------------------------------

// Create pulldown menu from items
Widget MMcreatePulldownMenu(Widget parent, const _XtString name, MMDesc items[],
                      ArgList args, Cardinal arg)
{
    Widget menu = verify(XmCreatePulldownMenu(parent, XMST(name), args, arg));
    MMaddItems(menu, items);
    auto_raise(XtParent(menu));

    return menu;
}

// Create radio pulldown menu from items
Widget MMcreateRadioPulldownMenu(Widget parent, const _XtString name, MMDesc items[],
                         ArgList _args, Cardinal _arg)
{
    ArgList args = new Arg[_arg + 10];
    Cardinal arg = 0;

    XtSetArg(args[arg], XmNisHomogeneous, True); arg++;
    XtSetArg(args[arg], XmNentryClass, xmToggleButtonWidgetClass); arg++;
    XtSetArg(args[arg], XmNradioBehavior, True); arg++;

    for (Cardinal i = 0; i < _arg; i++)
      args[arg++] = _args[i];

    Widget w = MMcreatePulldownMenu(parent, name, items, args, arg);

    delete[] args;
    return w;
}

// Create popup menu from items
Widget MMcreatePopupMenu(Widget parent, const _XtString name, MMDesc items[],
                   ArgList args, Cardinal arg)
{
    Widget menu = verify(XmCreatePopupMenu(parent, XMST(name), args, arg));
    MMaddItems(menu, items);
    auto_raise(XtParent(menu));

    return menu;
}


// Create menu bar from items
Widget MMcreateMenuBar(Widget parent, const _XtString name, MMDesc items[],
                   ArgList args, Cardinal arg)
{
    Widget bar = verify(XmCreateMenuBar(parent, XMST(name), args, arg));
    MMaddItems(bar, items);
    XtManageChild(bar);

    return bar;
}

// Create work area from items
Widget MMcreateWorkArea(Widget parent, const _XtString name, MMDesc items[],
                  ArgList args, Cardinal arg)
{
    Widget bar = verify(XmCreateWorkArea(parent, XMST(name), args, arg));
    MMaddItems(bar, items, true);
    XtManageChild(bar);

    return bar;
}

// Create panel from items
Widget MMcreatePanel(Widget parent, const _XtString name, MMDesc items[],
                 ArgList args, Cardinal arg)
{
    Widget panel = verify(XmCreateWorkArea(parent, XMST(name), args, arg));
    MMaddItems(panel, items);
    XtManageChild(panel);

    return panel;
}

void MMadjustPanel(const MMDesc items[], Dimension space)
{
    // Align panel labels
    Dimension max_label_width = 0;
    const MMDesc *item;
    for (item = items; item != 0 && item->name != 0; item++)
    {
      if (item->label == 0)
          continue;

      XtWidgetGeometry size;
      size.request_mode = CWWidth;
      XtQueryGeometry(item->label, (XtWidgetGeometry *)0, &size);
      max_label_width = max(max_label_width, size.width);
    }

    // Leave some extra space
    max_label_width += space;

    for (item = items; item != 0 && item->name != 0; item++)
    {
      if (item->label == 0)
          continue;

      XtVaSetValues(item->label,
                  XmNrecomputeSize, False,
                  XmNwidth, max_label_width,
                  XtPointer(0));
    }
}

// Create radio panel from items
Widget MMcreateRadioPanel(Widget parent, const _XtString name, MMDesc items[],
                    ArgList _args, Cardinal _arg)
{
    ArgList args = new Arg[_arg + 10];
    Cardinal arg = 0;

    XtSetArg(args[arg], XmNisHomogeneous, True);                      arg++;
    XtSetArg(args[arg], XmNentryClass,    xmToggleButtonWidgetClass); arg++;
    XtSetArg(args[arg], XmNradioBehavior, True);                      arg++;

    for (Cardinal i = 0; i < _arg; i++)
      args[arg++] = _args[i];

    Widget panel = verify(XmCreateRowColumn(parent, XMST(name), args, arg));
    MMaddItems(panel, items);
    XtManageChild(panel);

    delete[] args;
    return panel;
}

// Create button panel from items
Widget MMcreateButtonPanel(Widget parent, const _XtString name, MMDesc items[],
                     ArgList args, Cardinal arg)
{
    Widget panel = verify(XmCreateRowColumn(parent, XMST(name), args, arg));
    MMaddItems(panel, items);
    XtManageChild(panel);

    return panel;
}

// Perform proc on items
void MMonItems(const MMDesc items[], MMItemProc proc, XtPointer closure, int depth)
{
    if (depth == 0)
      return;

    for (const MMDesc *item = items; item != 0 && item->name != 0; item++)
    {
      if (item->type & MMIgnore)
          continue;

      proc(item, closure);

      if (item->items)
          MMonItems(item->items, proc, closure, depth - 1);
    }
}


//-----------------------------------------------------------------------
// Callbacks
//-----------------------------------------------------------------------

// Add callbacks to items
static void addCallback(const MMDesc *item, XtPointer default_closure)
{
    MMType flags            = item->type;
    MMType type             = flags & MMTypeMask;
    Widget widget           = item->widget;
    XtCallbackRec callback  = item->callback;
    
    if (callback.closure == 0)
      callback.closure = default_closure;

    bool flat = false;

    switch(type) 
    {
    case MMFlatPush:
    {
      flat = true;
      // FALL THROUGH
    }

    case MMPush:
    {
      void *userData = 0;
      XtVaGetValues(widget, XmNuserData, &userData, XtPointer(0));

      if (userData != 0)
      {
          PushMenuInfo *info = (PushMenuInfo *)userData;

          // A 'push menu' is a menu associated with a push button.
          // It pops up after pressing the button a certain time.
          XtAddCallback(widget, XmNarmCallback,    ArmPushMenuCB, info);
          XtAddCallback(widget, XmNarmCallback,    RedrawPushMenuCB, 0);
          XtAddCallback(widget, XmNdisarmCallback, RedrawPushMenuCB, 0);

          static XtTranslations translations =
                XtParseTranslationTable(pushMenuTranslations);
          XtAugmentTranslations(widget, translations);

          if (lesstif_version <= 81)
          {
            // In LessTif 0.81 and earlier, one must use button 3
            // to pop up push menus
            static XtTranslations lesstif_translations =
                XtParseTranslationTable(lesstif_pushMenuTranslations);
            XtAugmentTranslations(widget, lesstif_translations);
          }
      }

      if (flat)
      {
          ReflattenButtonCB(widget, XtPointer(widget));
      }

      // FALL THROUGH
    }

    case MMArrow:
    {
      if (callback.callback != 0)
          XtAddCallback(widget, 
                    XmNactivateCallback,
                    callback.callback, 
                    callback.closure);
      else
          set_sensitive(widget, false);
      break;
    }

    case MMToggle:
    case MMScale:
    {
      if (callback.callback != 0)
          XtAddCallback(widget,
                    XmNvalueChangedCallback,
                    callback.callback, 
                    callback.closure);
      else
          set_sensitive(widget, false);
      break;
    }

    case MMComboBox:
    {
      if (callback.callback != 0)
      {
          Widget list = ComboBoxList(widget);

          XtAddCallback(list, XmNbrowseSelectionCallback,
                    callback.callback, callback.closure);
          XtAddCallback(list, XmNsingleSelectionCallback,
                    callback.callback, callback.closure);
          XtAddCallback(list, XmNmultipleSelectionCallback,
                    callback.callback, callback.closure);
          XtAddCallback(list, XmNextendedSelectionCallback,
                    callback.callback, callback.closure);
      }

      // FALL THROUGH
    }

    case MMSpinBox:
    case MMTextField:
    {
      if (callback.callback != 0)
          XtAddCallback(widget,
                    XmNvalueChangedCallback,
                    callback.callback, 
                    callback.closure);

      if (type == MMTextField)
          break;
      // FALL THROUGH
    }

    case MMEnterField:
    {
      if (callback.callback != 0)
          XtAddCallback(widget,
                    XmNactivateCallback,
                    callback.callback, 
                    callback.closure);
      break;
    }

    case MMMenu:
    case MMRadioMenu:
    case MMOptionMenu:
    {
      Widget subMenu = 0;
      XtVaGetValues(widget, XmNsubMenuId, &subMenu, XtPointer(0));

      if (subMenu != 0 && callback.callback != 0)
      {
          XtAddCallback(subMenu,
                    XmNmapCallback,
                    callback.callback, 
                    callback.closure);
          XtAddCallback(subMenu,
                    XmNunmapCallback,
                    callback.callback, 
                    callback.closure);
#if XmVersion >= 1002
          XtAddCallback(subMenu,
                    XmNtearOffMenuActivateCallback,
                    callback.callback, 
                    callback.closure);
          XtAddCallback(subMenu,
                    XmNtearOffMenuDeactivateCallback,
                    callback.callback, 
                    callback.closure);
#endif
      }
      break;
    }

    case MMLabel:
    case MMSeparator:
    case MMPanel:
    case MMRadioPanel:
    case MMButtonPanel:
      assert(callback.callback == 0);
      break;

    default:
      // invalid type
      assert(0);
      abort();
    }
}

void MMaddCallbacks(const MMDesc items[], XtPointer default_closure, int depth)
{
    MMonItems(items, addCallback, default_closure, depth);
}

// Add help callback
00986 struct addHelpCallback_t {
  XtCallbackProc proc;
};

static void addHelpCallback(const MMDesc *item, XtPointer closure)
{
    Widget widget       = item->widget;
    XtCallbackProc proc = STATIC_CAST(addHelpCallback_t*,closure)->proc;

    XtAddCallback(widget, XmNhelpCallback, proc, XtPointer(0));
}

void MMaddHelpCallback(const MMDesc items[], XtCallbackProc proc, int depth)
{
    addHelpCallback_t proc_ = { proc };
    MMonItems(items, addHelpCallback, XtPointer(&proc_), depth);
}



//-----------------------------------------------------------------------
// PushMenus
//-----------------------------------------------------------------------

// Create pushmenu from items
Widget MMcreatePushMenu(Widget parent, const _XtString name, MMDesc items[],
                  ArgList _args, Cardinal _arg)
{
    ArgList args = new Arg[_arg + 10];
    Cardinal arg = 0;

    // By default, PushButton menus are activated using Button 1.
    if (XmVersion < 1002 || lesstif_version <= 84)
    {
      // Setting the menuPost resource is required by Motif 1.1 and
      // LessTif 0.84 and earlier.  However, OSF/Motif 2.0 (and
      // OSF/Motif 1.2, according to Roy Dragseth
      // <royd@math.uit.no>) choke on this line - buttons become
      // entirely insensitive.
      XtSetArg(args[arg], XmNmenuPost, "<Btn1Down>"); arg++;
    }

#if XmVersion >= 1002
    // Tear-off push menus don't work well - in LessTif, they cause
    // frequent X errors, and in Motif, they disable the old menus
    // once torn off.  So, we explicitly disable them.
    XtSetArg(args[arg], XmNtearOffModel, XmTEAR_OFF_DISABLED); arg++;
#endif

    for (Cardinal i = 0; i < _arg; i++)
      args[arg++] = _args[i];
    
    Widget menu = verify(XmCreatePopupMenu(parent, XMST(name), args, arg));
    MMaddItems(menu, items);
    auto_raise(XtParent(menu));

    // LessTif places a passive grab on the parent, such that the
    // pointer is grabbed as soon as the menuPost event occurs.  This
    // grab breaks PushMenus, so we cancel it.  Motif places a passive
    // grab on button 3, such that the pointer is grabbed as soon as
    // button 3 is pressed.  In Motif 1.1, it even remains grabbed!
    // This breaks any X session, so we cancel it.
    XtUngrabButton(parent, AnyButton, AnyModifier);

    delete[] args;
    return menu;
}




static XEvent last_push_menu_event; // Just save it

// Remove time out again
static void CancelPopupPushMenuCB(Widget w, XtPointer client_data, 
                          XtPointer call_data)
{
    (void) call_data;         // Use it

#if LOG_PUSH_MENUS
    XmPushButtonCallbackStruct *cbs = (XmPushButtonCallbackStruct *)call_data;
#endif

    PushMenuInfo *info = (PushMenuInfo *)client_data;

    if (info->timer != 0)
    {
#if LOG_PUSH_MENUS
      std::clog << "canceling (reason " << cbs->reason << ")\n";
#endif

      XtRemoveTimeOut(info->timer);
      info->timer = 0;
    }

    XtRemoveCallback(w, XmNdisarmCallback,
                 CancelPopupPushMenuCB, XtPointer(info));
    XtRemoveCallback(w, XmNactivateCallback,
                 CancelPopupPushMenuCB, XtPointer(info));

    XtUnmanageChild(info->subMenu);
    Widget shell = XtParent(info->subMenu);
    XtPopdown(shell);
}

// Popup menu right now
static void PopupPushMenuCB(XtPointer client_data, XtIntervalId *id)
{
    (void) id;                // Use it

    PushMenuInfo *info = (PushMenuInfo *)client_data;
    Widget w = info->widget;

    assert(info->timer == *id);
    info->timer = 0;

#if LOG_PUSH_MENUS
    std::clog << "popping up\n";
#endif

    XtRemoveCallback(w, XmNdisarmCallback,
                 CancelPopupPushMenuCB, XtPointer(info));
    XtRemoveCallback(w, XmNactivateCallback,
                 CancelPopupPushMenuCB, XtPointer(info));

    // Popup the menu
    XtCallActionProc(w, "popup-push-menu", &last_push_menu_event, 0, 0);

    // Unactivate pushbutton
    XtCallActionProc(w, "Disarm",          &last_push_menu_event, 0, 0);

#if XmVersion < 1002
    if (XtIsRealized(w))
    {
      // In Motif 1.1, the PushButton does not redisplay after being disarmed
      XClearArea(XtDisplay(w), XtWindow(w), 0, 0, 0, 0, True);
    }
#endif
}

static void ReflattenButtonEH(Widget shell, XtPointer client_data, 
                        XEvent *event, Boolean *)
{
    switch (event->type)
    {
    case UnmapNotify:
      ReflattenButtonCB(shell, client_data);
      break;

    default:
      break;
    }
}


static void PopupPushMenuAct(Widget w, XEvent *event, String *, Cardinal *)
{
    if (!XmIsPushButton(w))
      return;

    void *userData = 0;
    XtVaGetValues(w, XmNuserData, &userData, XtPointer(0));
    if (userData == 0)
      return;

    PushMenuInfo *info = (PushMenuInfo *)userData;

    Widget shell = XtParent(info->subMenu);

    // Attempt to place menu below button
    Position button_x, button_y;
    XtTranslateCoords(w, 0, 0, &button_x, &button_y);

    Dimension height = 0;
    XtVaGetValues(w, XmNheight, &height, XtPointer(0));

    Position x = button_x;
    Position y = button_y + height;

    event->xbutton.x_root = x;
    event->xbutton.y_root = y;
    XmMenuPosition(info->subMenu, &event->xbutton);

    if (info->flat)
    {
      // Don't have the PushMenu interfere with flattening.  Disable
      // flattening until the menu is popped down again.
      EventMask event_mask = EnterWindowMask | LeaveWindowMask;
      XtRemoveEventHandler(w, event_mask, False, FlattenEH, XtPointer(0));
      XtRemoveCallback(w, XmNarmCallback,    FlattenCB, XtPointer(False));
      XtRemoveCallback(w, XmNdisarmCallback, FlattenCB, XtPointer(True));
#if 0
      XtAddCallback(shell, XtNpopdownCallback, ReflattenButtonCB, 
                  XtPointer(w));
#else
      // XtNpopDownCallback isn't called by LessTif and unknown in
      // Motif 1.1.  *Sigh*.
      XtAddEventHandler(shell, StructureNotifyMask, False, 
                    ReflattenButtonEH, XtPointer(w));
#endif
    }

    XtManageChild(info->subMenu);
    XtPopup(shell, XtGrabNone);
}

static void DecoratePushMenuAct(Widget w, XEvent */* event */, 
                        String *, Cardinal *)
{
    if (!XmIsPushButton(w) || !XtIsRealized(w))
      return;

    // std::clog << "Redraw " << XtName(w) << "\n";

    // Draw a little triangle in upper right corner
    XPoint points[4] = {
      {-6, 1}, {5, 0}, {-3, 3}, {-2, -3}
    };

    Dimension width;
    Dimension highlightThickness;
    Dimension shadowThickness;
    Pixel foreground;
    Pixel background;
    Colormap colormap;
    XtVaGetValues(w,
              XmNwidth, &width,
              XmNhighlightThickness, &highlightThickness, 
              XmNshadowThickness, &shadowThickness,
              XmNforeground, &foreground,
              XmNbackground, &background,
              XmNcolormap, &colormap,
              XtPointer(0));

    points[0].x += width - highlightThickness - shadowThickness;
    points[0].y += highlightThickness + shadowThickness;

    Pixel color;
    XmGetColors(XtScreen(w), colormap, background, 0, 0, &color, 0);

    XGCValues xgc;
    xgc.foreground = color;
    xgc.background = background;
    GC gc = XtGetGC(w, GCForeground | GCBackground, &xgc);

    XFillPolygon(XtDisplay(w), XtWindow(w), gc, points, XtNumber(points), 
             Convex, CoordModePrevious);
}



// Popup menu after some delay
01238 struct subresource_values {
    int push_menu_popup_time; // Delay before popping up menu
};

static XtResource subresources[] = {
    {
      XTRESSTR(XtNpushMenuPopupTime),
      XTRESSTR(XtCPushMenuPopupTime),
      XmRInt,
      sizeof(int),
      XtOffsetOf(subresource_values, push_menu_popup_time), 
      XmRImmediate,
      XtPointer(500)
    }
};

static void ArmPushMenuCB(Widget w, XtPointer client_data, XtPointer call_data)
{
    PushMenuInfo *info = (PushMenuInfo *)client_data;

    subresource_values values;
    XtGetApplicationResources(w, &values, 
                        subresources, XtNumber(subresources), 
                        ArgList(0), 0);

    XmPushButtonCallbackStruct *cbs = (XmPushButtonCallbackStruct *)call_data;
    if (cbs && cbs->event)
      last_push_menu_event = *cbs->event;

    if (info->timer != 0)
      XtRemoveTimeOut(info->timer);

    info->timer = XtAppAddTimeOut(XtWidgetToApplicationContext(w), 
                          values.push_menu_popup_time, 
                          PopupPushMenuCB, XtPointer(info));

#if LOG_PUSH_MENUS
    std::clog << "Waiting for " << XtName(w) << " menu " 
            << "(timer " << info->timer << ")...";
#endif

    // If we're disarmed or activated within the delay, don't popup.
    XtAddCallback(w, XmNdisarmCallback,   
              CancelPopupPushMenuCB, XtPointer(info));
    XtAddCallback(w, XmNactivateCallback, 
              CancelPopupPushMenuCB, XtPointer(info));
}

static void RedrawPushMenuCB(Widget w, XtPointer, XtPointer)
{
    XtCallActionProc(w, "decorate-push-menu", 0, 0, 0);
}


void set_sensitive(Widget w, bool state)
{
    if (w != 0)
    {
      XtSetSensitive(w, state?True:False);

      if (!state && w == active_button)
      {
          // We won't get the LeaveWindow event, since W is now
          // insensitive.  Flatten button explicitly.
          flatten_button(w);
      }
    }
}

Generated by  Doxygen 1.6.0   Back to index