/* 1605, Thu 17 Feb 00

   X_OB.C:  X/Motif outer block routines for x_nm_rc.c

   Copyright (C) 1996-2002 by Nevil Brownlee,
   CAIDA | University of Auckland

   x_nm_rc development began with O'Reilly code, as noted below */

/* Written by Dan Heller and Paula Ferguson.  
 * Copyright 1994, O'Reilly & Associates, Inc.
 * Permission to use, copy, and modify this program without
 * restriction is hereby granted, as long as this copyright
 * notice appears in each copy of the program source code.
 * This program is freely distributable without licensing fees and
 * is provided without guarantee or warrantee expressed or implied.
 * This program is -not- in the public domain.
 */

/*
 * $Log: x_ob.c,v $
 * Revision 1.1.1.2.2.10  2002/02/23 01:57:24  nevil
 * Moving srl examples to examples/ directory.  Modified examples/Makefile.in
 *
 * Revision 1.1.1.2.2.6  2000/08/08 19:44:47  nevil
 * 44b8 release
 *
 * Revision 1.1.1.2.2.4  2000/06/06 03:38:15  nevil
 * Combine NEW_ATR with TCP_ATR, various bug fixes
 *
 * Revision 1.1.1.2.2.1  1999/11/29 00:17:23  nevil
 * Make changes to support NetBSD on an Alpha (see version.history for details)
 *
 * Revision 1.1.1.2  1999/10/03 21:06:21  nevil
 * *** empty log message ***
 *
 * Revision 1.1.1.1.2.3  1999/09/29 22:29:13  nevil
 * Changes (mainly changing // to /* comments) for Irix
 *
 * Revision 1.1.1.1.2.2  1999/05/25 22:30:51  nevil
 * Make sure IPv6 managers interwork properly with IPv4 meters
 * - Determine meter's RULE_ADDR_SIZE by reading mask from rule 1
 *   of default ruleset.
 * - Print warning if meter rulesize < manager rulesize.
 * - Use smaller of these when downloading rules.
 *
 * Revision 1.1.1.1.2.1  1999/01/08 01:38:34  nevil
 * Distribution file for 4.3b7
 *
 * Revision 1.1.1.1  1998/11/16 03:57:28  nevil
 * Import of NeTraMet 4.3b3
 *
 * Revision 1.1.1.1  1998/11/16 03:22:01  nevil
 * Import of release 4.3b3
 *
 * Revision 1.1.1.1  1998/10/28 20:31:26  nevil
 * Import of NeTraMet 4.3b1
 *
 * Revision 1.1.3.2.2.1  1998/10/27 04:39:15  nevil
 * 4.3b1 release
 *
 * Revision 1.1.3.2  1998/10/18 23:44:16  nevil
 * Added Nicolai's patches, some 'tidying up' of the source
 *
 * Revision 1.1.3.1  1998/10/13 02:48:31  nevil
 * Import of Nicolai's 4.2.2
 *
 * Revision 1.1.1.1  1998/08/24 12:09:29  nguba
 * NetraMet 4.2 Original Distribution
 *
 * Revision 1.3  1998/05/07 04:28:53  rtfm
 * Implement NetFlowMet, the Cisco NetFlow RTFM meter
 */

#if HAVE_CONFIG_H
#include <ntm_conf.h>
#else
#define ver_str  "test"
#endif

#include <Xm/MainW.h>
#include <Xm/Form.h>
#include <Xm/DrawingA.h>
#include <Xm/Label.h>

#include <Xm/PushBG.h>
#include <Xm/PushB.h>
#include <Xm/RowColumn.h>
#include <Xm/CascadeBG.h>
#include <Xm/ToggleB.h>
#include <Xm/ToggleBG.h>

#include <Xm/Xm.h>
#include <X11/cursorfont.h>

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h>
#if HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#include "ausnmp.h"  /* SNMP include files needed by nmc.h */
#include "asn1.h"
#include "snmp.h"
#include "snmpimpl.h"
#include "snmpapi.h"
#include "snmpclnt.h"
#include "mib.h"

#include "nmc.h"
#include "n_plot.h"
#include "x_nm_rc.h"

#define noW_DEBUG

String fallback_resources[] = { 
   "*title: nifty: Nevil's RTFM Network Flow Analyser",
   NULL };

GC gc;
Pixmap pixmap;
Dimension da_width,da_height;  /* Current size of the DrawingArea */

Widget main_w, drawing_a, label;

void configure(), draw(), expose(), destroy(), clear_it();
void set_color(Widget widget, XtPointer client_data, XtPointer call_data);

typedef struct _menu_item {
   char        *label;          /* Label for the item */
   WidgetClass *class;          /* Pushbutton, label, separator... */
   char        mnemonic;        /* Mnemonic; 0 if none */
   char        *accelerator;    /* Accelerator; NULL if none */
   char        *accel_text;     /* To be converted to compound string */
   void        (*callback)();   /* Routine to call; NULL if none */
   XtPointer   callback_data;   /* Client_data for callback() */
   struct _menu_item *subitems; /* Pullright menu items, if not NULL */
   } MenuItem;

/* Pulldown menus are built from cascade buttons, so this function
 * also includes pullright menus.  Create the menu, the cascade button
 * that owns the menu, and then the submenu items.
 */
Widget BuildPulldownMenu(Widget parent, 
   char *menu_title,char menu_mnemonic, 
   Boolean tear_off, MenuItem *items)
{
   Widget PullDown, cascade, widget;
   int i;
   XmString str;

   PullDown = XmCreatePulldownMenu(parent, "_pulldown", NULL, 0);
   if (tear_off)
      XtVaSetValues(PullDown, XmNtearOffModel, XmTEAR_OFF_ENABLED, NULL);
   str = XmStringCreateLocalized(menu_title);
   cascade = XtVaCreateManagedWidget(menu_title,
      xmCascadeButtonGadgetClass, parent,
      XmNsubMenuId,   PullDown,
      XmNlabelString, str,
      XmNmnemonic,    menu_mnemonic,
      NULL);
   XmStringFree(str);

   /* Now add the menu items */
   for (i = 0; items[i].label != NULL; i++) {
      /* If subitems exist, create the pull-right menu by calling this
       * function recursively.  Since the function returns a cascade
       * button, the widget returned is used..
       */
      if (items[i].subitems)
         widget = BuildPulldownMenu(PullDown, items[i].label, 
            items[i].mnemonic, tear_off, items[i].subitems);
         else
            widget = XtVaCreateManagedWidget(items[i].label,
               *items[i].class, PullDown,
               NULL);
         /* Whether the item is a real item or a cascade button with a
          * menu, it can still have a mnemonic.
          */
         if (items[i].mnemonic)
            XtVaSetValues(widget, XmNmnemonic, items[i].mnemonic, NULL);
         /* any item can have an accelerator, except cascade menus. But,
          * we don't worry about that; we know better in our declarations.
          */
         if (items[i].accelerator) {
            str = XmStringCreateLocalized(items[i].accel_text);
            XtVaSetValues(widget,
               XmNaccelerator, items[i].accelerator,
               XmNacceleratorText, str,
               NULL);
            XmStringFree(str);
         }
         if (items[i].callback)
            XtAddCallback(widget,
               (items[i].class == &xmToggleButtonWidgetClass ||
               items[i].class == &xmToggleButtonGadgetClass) ?
               XmNvalueChangedCallback : /* ToggleButton class */
               XmNactivateCallback,      /* PushButton class */
               items[i].callback, items[i].callback_data);
      }
   return cascade;
   }

/* Callback functions for menu items declared below */

static void set_log(Widget w, XtPointer client_data, XtPointer call_data)
{
   set_log_type(client_data);
   }

static void set_name(Widget w, XtPointer client_data, XtPointer call_data)
{
   set_name_type(client_data);
   }

static void set_ordinate(Widget w, XtPointer client_data, XtPointer call_data)
{
   set_ordinate_type(client_data);
   configure(w);
   }

static void set_metric(Widget w, XtPointer client_data, XtPointer call_data)
{
   set_metric_type(client_data);
   configure(w);
   }

static void set_select(Widget w, XtPointer client_data, XtPointer call_data)
{
   set_select_type(client_data);
   configure(w);
   }

static void set_axis(Widget w, XtPointer client_data, XtPointer call_data)
{
   set_axis_range(client_data);
   configure(w);
   }

static void quit_action(Widget w, XtPointer client_data, XtPointer call_data)
{
   shutdown_nifty();
   }


MenuItem log_menu[] = {
   {"Sample", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_log, (XtPointer)LGSAMPLE, (MenuItem *)NULL},
   {"Points", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_log, (XtPointer)LGPOINT, (MenuItem *)NULL},
   {"None", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_log, (XtPointer)LGNONE, (MenuItem *)NULL},
   NULL
   };

MenuItem peer_addr_menu[] = {
   {"IP Address", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_name, (XtPointer)0, (MenuItem *)NULL},
   {"Domain Name", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_name, (XtPointer)1, (MenuItem *)NULL},
   NULL
   };


MenuItem ordinate_menu[] = {
   {"Bytes", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_ordinate, (XtPointer)PQBYTES, (MenuItem *)NULL},
   {"Packets", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_ordinate, (XtPointer)PQPKTS, (MenuItem *)NULL},
   NULL
   };

MenuItem metric_menu[] = {
   {"Rate", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_metric, (XtPointer)PTSAMPKB, (MenuItem *)NULL},
   {"Count", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_metric, (XtPointer)PTFLOWKB, (MenuItem *)NULL},
   {"Percent", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_metric, (XtPointer)PTSAMPPC, (MenuItem *)NULL},
   NULL
   };

MenuItem select_menu[] = {
   {"Last sample", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_select, (XtPointer)STLAST, (MenuItem *)NULL},
   {"Recent samples", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_select, (XtPointer)STRECENT, (MenuItem *)NULL},
   {"All samples", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_select, (XtPointer)STALL, (MenuItem *)NULL},
   NULL
   };


MenuItem xaxis_menu[] = {
   {"100 s", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_axis, (XtPointer)SX100S, (MenuItem *)NULL},
   {"15 m", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_axis, (XtPointer)SX15M, (MenuItem *)NULL},
   {"2 h", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_axis, (XtPointer)SX2H, (MenuItem *)NULL},
   NULL
   };

MenuItem yaxis_menu[] = {
   {"40", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_axis, (XtPointer)SY40P, (MenuItem *)NULL},
   {"900", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_axis, (XtPointer)SY900, (MenuItem *)NULL},
   {"9k", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_axis, (XtPointer)SY9K, (MenuItem *)NULL},
   {"90k", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_axis, (XtPointer)SY90K, (MenuItem *)NULL},
   {"900k", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_axis, (XtPointer)SY900K, (MenuItem *)NULL},
   {"9M", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_axis, (XtPointer)SY9M, (MenuItem *)NULL},
   {"90M", &xmPushButtonGadgetClass, 0, NULL, NULL,
      set_axis, (XtPointer)SY90M, (MenuItem *)NULL},
   NULL
   };


MenuItem file_menus[] = {
   {"Logging", &xmCascadeButtonGadgetClass, 0, NULL, NULL,
      0, 0, log_menu},
   {"Peer Address", &xmCascadeButtonGadgetClass, 0, NULL, NULL,
      0, 0, peer_addr_menu},
   {"Quit", &xmCascadeButtonGadgetClass, 0, NULL, NULL,
      quit_action, 0, (MenuItem *)NULL },
   NULL
   };

MenuItem option_menus[] = {
   {"Ordinate", &xmCascadeButtonGadgetClass, 0, NULL, NULL,
      0, 0, ordinate_menu},
   {"Metric", &xmCascadeButtonGadgetClass, 0, NULL, NULL,
      0, 0, metric_menu},
   {"Selection", &xmCascadeButtonGadgetClass, 0, NULL, NULL,
      0, 0, select_menu},
   NULL
   };

MenuItem plot_menus[] = {
   {"X axis", &xmCascadeButtonGadgetClass, 0, NULL, NULL,
      0, 0, xaxis_menu},
   {"Y axis", &xmCascadeButtonGadgetClass, 0, NULL, NULL,
      0, 0, yaxis_menu},
   NULL
   };


void change_label_text(Widget lbl, char *msg)
{
   XmString label_str;
   if (msg[0] != '\0') {
      XtVaGetValues(lbl, XmNlabelString, &label_str, NULL);
      XmStringFree(label_str);
      label_str = XmStringCreateLtoR(msg,XmSTRING_DEFAULT_CHARSET);
      XtVaSetValues(lbl, XmNlabelString, label_str, NULL);
      }
   }

XtIntervalId timer;
static int need_new_points;

void t_handler(Widget w, XtIntervalId *id)
{
   Display *display;
   Window window;
   Cursor cursor;
   int ms;
   XmString label_str;
   char msg[250];
#ifdef W_DEBUG
   printf("t_handler():\n");
#endif

   if (request_stop)  /* Set by sigint_handler() */
      shutdown_nifty();

   display = XtDisplay(main_w);
   window = XtWindow(main_w);
   if (need_new_points) {
     cursor = XCreateFontCursor(display,XC_watch);  /* Wristwatch cursor */
      XDefineCursor(display, window, cursor);
      need_new_points = 0;  ms = 10;  /* X idle loop will display cursor */
      }
   else {
      ms = 1000*nm_timer_callback(msg,1);  /* Generate the set of points */
      nm_select_points();
      nm_compute_points();
      if (pixmap != (Pixmap)NULL) XCopyArea(XtDisplay(drawing_a),
         pixmap, XtWindow(drawing_a), gc,
         0,0, da_width,da_height, 0,0);
      nm_plot_points();  /* Plot the points */
      change_label_text(label,msg);
      XUndefineCursor(display, window);  /* Back to arrow cursor */
      need_new_points = 1;
      }

   timer = XtAppAddTimeOut(XtWidgetToApplicationContext(w),
      ms, (XtTimerCallbackProc)t_handler, w);
   }

void start_timer(Widget w, int seconds)
{
   char msg[250];
   seconds = nm_timer_callback(msg,0);  /* Main X loop not running yet! */
   timer = XtAppAddTimeOut(XtWidgetToApplicationContext(w),
      seconds*1000, (XtTimerCallbackProc)t_handler, w);
   need_new_points = 1;
   }

static int nifty_running;  /* Set after first plot has been drawn */

void nm_x_startup(int argc, char *argv[], int next_event_s)
{
   Widget toplevel;
   XEvent x_event;
   XtAppContext app_context;
   XGCValues gcv;

   Widget menubar;
   Arg args[20];  Cardinal nargs;

   XtActionsRec actions[] = {
      {"configure", configure},
      {"draw", draw},
      };
   String translations = /* for the DrawingArea widget */
      "<Configure>:  configure()\n"
      "<Btn1Down>:   draw(b1-down)\n"
      "<Btn1Up>:     draw(b1-up)\n"
      "<Btn2Down>:   draw(b2-down)\n"
      "<Btn3Down>:   draw(b3-down)";

   XtSetLanguageProc (NULL, NULL, NULL);

   toplevel = XtVaAppInitialize(&app_context, "toplevel", NULL, 0, 
      &argc, argv, fallback_resources, NULL);

   /* Create a MainWindow to contain the drawing area */
   main_w = XtVaCreateManagedWidget ("main_w",
         xmFormWidgetClass, toplevel, 
      XmNwidth,            N_MAIN_WIDTH,
      XmNheight,           N_MAIN_HEIGHT,
      NULL);

   /* Create a GC for drawing (callback).  Used a lot -- make global */
   gcv.foreground = WhitePixelOfScreen (XtScreen (main_w));
   gc = XCreateGC(XtDisplay(main_w),
      RootWindowOfScreen(XtScreen(main_w)), GCForeground, &gcv);

   nargs = 0;
   XtSetArg(args[nargs], XmNmarginHeight,   0);  ++nargs;
   XtSetArg(args[nargs], XmNbackground,
      WhitePixelOfScreen(XtScreen (main_w)));  ++nargs;
   XtSetArg(args[nargs], XmNtopAttachment,  XmATTACH_FORM);  ++nargs;
   XtSetArg(args[nargs], XmNtopOffset,      0);  ++nargs;
   XtSetArg(args[nargs], XmNleftAttachment, XmATTACH_FORM);  ++nargs;
   XtSetArg(args[nargs], XmNleftOffset,     0);  ++nargs;
   XtSetArg(args[nargs], XmNrightAttachment,XmATTACH_FORM);  ++nargs;
   XtSetArg(args[nargs], XmNrightOffset,    0);  ++nargs;
   menubar = XmCreateMenuBar(main_w, "menubar", 
      (ArgList)args, nargs);
   BuildPulldownMenu(menubar, "File", 0, False, file_menus);
   BuildPulldownMenu(menubar, "Options", 0, False, option_menus);
   BuildPulldownMenu(menubar, "Plot", 0, False, plot_menus);
   XtManageChild(menubar);

   label = XtVaCreateManagedWidget("label",
         xmLabelWidgetClass, main_w,
      XmNbackground,       WhitePixelOfScreen(XtScreen (main_w)),
      XmNlabelString,      XmStringCreateLtoR( "nifty " ver_str
         /* Check on meter version done in nmc_snmp.c */
         ", (C) 1996-2002 by Nevil Brownlee, CAIDA | University of Auckland",
         XmSTRING_DEFAULT_CHARSET),
      XmNalignment,        XmALIGNMENT_BEGINNING,
      XmNheight,           N_LABEL_HEIGHT,
      XmNmarginLeft,       N_LABEL_LEFT_MARGIN,
      XmNbottomAttachment, XmATTACH_FORM,
      XmNbottomOffset,     N_BOTTOM_BORDER,
      XmNleftAttachment,   XmATTACH_FORM,
      XmNleftOffset,       N_LEFT_BORDER,
      XmNrightAttachment,  XmATTACH_FORM,
      XmNrightOffset,      N_RIGHT_BORDER,
      NULL);

   drawing_a = XtVaCreateManagedWidget ("drawing_a",
         xmDrawingAreaWidgetClass, main_w,
      XmNtranslations,     XtParseTranslationTable (translations), 
      XmNbackground,       WhitePixelOfScreen (XtScreen (main_w)),
      XmNresizePolicy,     XmRESIZE_ANY,
      XmNtopAttachment,    XmATTACH_WIDGET,
      XmNtopWidget,        menubar,
      XmNtopOffset,        N_TOP_BORDER,
      XmNleftAttachment,   XmATTACH_FORM,
      XmNleftOffset,       N_LEFT_BORDER,
      XmNrightAttachment,  XmATTACH_FORM,
      XmNrightOffset,      N_RIGHT_BORDER,
      XmNbottomAttachment, XmATTACH_WIDGET,
      XmNbottomWidget,     label,
      XmNbottomOffset,     N_MID_SPACE,
      NULL);
   XtAddCallback(drawing_a, XmNexposeCallback, expose, NULL);

   XtAppAddActions(app_context, actions, XtNumber(actions));
   da_width = da_height = 0;
   XtRealizeWidget(toplevel);

   set_initial_parameters();

   pixmap = (Pixmap)NULL;
   configure(drawing_a);  /* Don't know size until window is realised! */

   start_timer(drawing_a, next_event_s);

   nifty_running = 0;
   for (;;) {  /* X App main loop */
      XtAppNextEvent(app_context, &x_event);
      if (x_event.type == ClientMessage &&
            x_event.xany.window == XtWindow(toplevel)) {
	 if (nifty_running) shutdown_nifty();
            /* Some X window managers send the message when
               windows open, as well as when they close */
         }
      else XtDispatchEvent(&x_event);
      }
   }

void configure(Widget w, XEvent *event, String *args, int *num_args)
{
   Dimension new_width,new_height;

   XtVaGetValues(drawing_a,  /* Get new window size */
      XmNwidth,&new_width, XmNheight,&new_height, NULL);
#ifdef W_DEBUG
   printf("configure(): pixmap=%u, %d,%d -> %d,%d\n",
      pixmap, da_width,da_height, new_width,new_height);
#endif
   if (pixmap != (Pixmap)NULL)
      XFreePixmap(XtDisplay(drawing_a), pixmap);
   /* Create a pixmap the same size as the drawing area. */
   pixmap = XCreatePixmap(XtDisplay(drawing_a),
      RootWindowOfScreen(XtScreen(drawing_a)),
      new_width,new_height,
      DefaultDepthOfScreen(XtScreen(drawing_a)));
   /* Clear pixmap with white */
   set_color(drawing_a, "White", NULL);
   XFillRectangle(XtDisplay(drawing_a), pixmap, gc,
      0,0, new_width,new_height);
   set_color(drawing_a, "Black", NULL);
   da_width = new_width, da_height = new_height;

   plot_graph();  /* Set up the plot environment */
   XCopyArea(XtDisplay(drawing_a),  /* Display the pixmap */
      pixmap, XtWindow(drawing_a), gc,
      0,0, da_width,da_height, 0,0);
   nm_plot_points();  /* Plot them */
   }

/* Action procedure to respond to any of the events from the
 * translation table declared in main().  This function is called
 * in response to events like Button Down, Up and Motion */

static int np;  /* Index of nearest point (for button-1) */

void draw(Widget w, XEvent *event, String *args, int *num_args)
{
   XButtonEvent *bevent = (XButtonEvent *) event;
   Display *display = XtDisplay(main_w);
   Window window = XtWindow(main_w);
   Cursor cursor;
   char msg[500];

#ifdef W_DEBUG
   printf("draw(%d): %s\n", *num_args, args[0]);
#endif
   if (*num_args != 1) XtError ("draw(): Wrong number of args!");

   if (strcmp(args[0],"b1-down") == 0) {
      if ((np = nm_nearest_flow(bevent->x, da_height-bevent->y)) < 0)
         return;  /* Not near a point */
      if (show_names) {
         cursor = XCreateFontCursor(display,XC_crosshair);  /* Change cursor */
         XDefineCursor(display, window, cursor);
         }
      return;
      }
   else if (strcmp(args[0],"b1-up") == 0) {
      if (np < 0) return;
      nm_flow_details(msg, 1, np);  /* Use np from b1-down */
      if (show_names)
         XUndefineCursor(display, window);  /* Back to arrow cursor */
      }
   else {
      if ((np = nm_nearest_flow(bevent->x, da_height-bevent->y)) < 0)
          return;  /* Not near a point */
      if (strcmp(args[0],"b3-down") == 0)
         nm_flow_details(msg, 3, np);
      else if (strcmp(args[0],"b2-down") == 0)
         nm_flow_details(msg, 2, np);
      }
   change_label_text(label, msg);
   }

#if 0
void show_resolving(char *msg)
{
   change_label_text(label, msg);
   }
#endif

/* Called whenever any portion of the drawing area is exposed */
void expose(Widget w, XtPointer client_data, XtPointer call_data)
{
   XmDrawingAreaCallbackStruct *cbs = 
      (XmDrawingAreaCallbackStruct *)call_data;
#ifdef W_DEBUG
   printf("expose():\n");
#endif
   if (pixmap != (Pixmap)NULL) XCopyArea(cbs->event->xexpose.display, 
      pixmap, cbs->window, gc, 0,0, da_width,da_height, 0,0);
   nm_plot_points();
   }

/* Set foreground colour for a widget */
void set_color(Widget widget, XtPointer client_data, XtPointer call_data)
{
   String color = (String)client_data;
   Display *dpy = XtDisplay(widget);
   Colormap cmap = DefaultColormapOfScreen(XtScreen(widget));
   XColor col, unused;

   if (!XAllocNamedColor(dpy, cmap, color, &col, &unused)) {
      char buf[32];
      sprintf(buf, "Can't alloc %s", color);
      XtWarning(buf);
      return;
      }
   XSetForeground (dpy, gc, col.pixel);
   }

void set_plot_color(String cp)
{
   set_color(drawing_a,cp,NULL);
   }

plot_graph(void)
{
   plot_window(drawing_a, gc);
   plot_device(pixmap);
   swindo(N_GRAPH_MARGIN,da_width-2*N_GRAPH_MARGIN,
      N_GRAPH_MARGIN,da_height-2*N_GRAPH_MARGIN);
   dwindo(pxmin,pxmax, pymin,pymax);
   settrn(P_LOG_LOG);
   draw_xaxis();  draw_yaxis();
   movabs(da_width-N_TITLE_RIGHT_MARGIN, da_height-N_TITLE_TOP_MARGIN);
   draw_title();
   plot_device(XtWindow(drawing_a));
   nifty_running = 1;
   }

