ftp.nice.ch/pub/next/graphics/3d/geomview.1.4.1.s.tar.gz#/Geomview/src/bin/geomview/gl/glui.c

This is glui.c in view mode; [Download] [Up]

/* Copyright (c) 1992 The Geometry Center; University of Minnesota
   1300 South Second Street;  Minneapolis, MN  55454, USA;
   
This file is part of geomview/OOGL. geomview/OOGL is free software;
you can redistribute it and/or modify it only under the terms given in
the file COPYING, which you should have received along with this file.
This and other related software may be obtained via anonymous ftp from
geom.umn.edu; email: software@geom.umn.edu. */

/* Authors: Stuart Levy, Tamara Munzner, Mark Phillips, Celeste Fowler */

#include "mg.h"
#include "../common/drawer.h"
#include "../common/ui.h"
#include "../common/comm.h"
#include "../common/space.h"
#include "../common/event.h"
#include "../common/motion.h"
#include "../common/worldio.h"
#include "../common/version.h"
#include "../common/lang.h"

#include "mgrib.h"
#include "stopsign.h"
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/dir.h>
#include <sys/signal.h>

#define	BLACKINDEX	0
#define	WHITEINDEX	7

#define FACEINDEX	250
#define EDGEINDEX 	251
#define NORMALINDEX 	252
#define BBOXINDEX 	253
#define BACKINDEX 	254
#define LIGHTINDEX	255


/* Panel definitions below must be in the same order! */
#define	P_MAIN		1
#define	P_TOOL		2
#define	P_APPEARANCE	3
#define	P_CAMERA	4
#define	P_LIGHTS	5
#define	P_OBSCURE	6
#define	P_COMMANDS	7
#define	P_MATERIAL	8
#define	P_CREDITS	9
#define	P_SAVE		10
#define	P_FILEBROWSER	11
#define	P_INPUT		12
#define	P_COLORPICKER	13
#define	P_MAX	14


#include "panel.c"

static struct panel {
    FL_FORM **objp;
    char *name;
    char key[2];
    char shown;
    char browse;
    int placement;
    short winid;
    long x0, y0;
} panels[] = {
    { NULL,		0,		0,0,0, 0,			0, 0,0 },
    { &MainPanel,	"geomview", 	"Pm",1,1, FL_PLACE_SIZE, 0, -1,-1 },
    { &ToolPanel,	"Tools", 	"Pt",1,1, FL_PLACE_SIZE, 0, -1,-1 },
    { &AppearancePanel,	"Appearance", 	"Pa",0,1, FL_PLACE_SIZE, 0, -1,-1 },
    { &CameraPanel,	"Cameras", 	"Pc",0,1, FL_PLACE_SIZE, 0, -1,-1 },
    { &LightingPanel,	"Lighting", 	"Pl",0,1, FL_PLACE_SIZE, 0, -1,-1 },
    { &ObscurePanel,	"Obscure", 	"Po",0,1, FL_PLACE_SIZE, 0, -1,-1 },
    { &CommandPanel,	"Commands", 	"PC",0,1, FL_PLACE_SIZE, 0, -1,-1 },
    { &MaterialsPanel,	"Materials", 	"PM",0,1, FL_PLACE_SIZE, 0, -1,-1 },
    { &CreditsPanel,	"Credits", 	"",  0,1, FL_PLACE_SIZE, 0, -1,-1 },
    { &SavePanel,	"Save",		">", 0,1,FL_PLACE_MOUSE, 0, -1,-1 },
    { &BrowserPanel,	"Files",	"Pf",0,1, FL_PLACE_SIZE, 0, -1,-1 },
    { &InputPanel,	"Load",		"<", 0,0,FL_PLACE_MOUSE, 0, -1,-1 },
    { &ColorPanel,	"Color",	"",  0,0,FL_PLACE_MOUSE, 0, -1,-1 },
};

#define	SAVE_WIO	0
#define	SAVE_RMan	1
#define	SAVE_SNAP	2
#define	SAVE_PANELS	3

static struct saveops {
    int special;
    HandleOps *ops;
    int flag;
    char *name;
} save[] = {
    { SAVE_WIO, &CommandOps,	0,	 	"Commands"		},
    { SAVE_WIO, &GeomOps,	SELF,		"Geometry alone"	},
    { SAVE_WIO, &GeomOps,	WORLDGEOM,	"Geometry [in world]"	},
    { SAVE_WIO, &GeomOps,	UNIVERSE,	"Geometry [in universe]" },
    { SAVE_RMan, NULL,		TIFF_KEYWORD,	"RMan [->tiff]"		},
    { SAVE_RMan, NULL,		FRAME_KEYWORD,	"RMan [->frame]"	},
    { SAVE_SNAP, NULL,		0,	 	"SGI snapshot"		},
    { SAVE_WIO, &CamOps,	UNIVERSE, 	"Camera"		},
    { SAVE_WIO, &TransOps,	WORLDGEOM,	"Transform [to world]"	},
    { SAVE_WIO, &TransOps,	UNIVERSE,	"Transform [to universe]"},
    { SAVE_WIO, &WindowOps,	UNIVERSE,	"Window"		},
    { SAVE_PANELS, NULL,	0,		"Panels"		},
};


int toolcolor[MAXTOOLS];
int actioncolor[MAXACTIONS];

UIState uistate;

char   *lights[MAXLIGHTS+1];

static int colindex = 249;
static Color oldcol = {0, 0, 0};
static Color curcol = {0, 0, 0};
static int curval = 0;
static int colorchanged = 0;
static FL_OBJECT *curobj = NULL;
static int *menuindex = NULL;
static int menucount = 0;
static int norefresh = 0;
static char *browsing_dir = NULL;

#define QUIETLY(cmds)	\
  do {			\
    int old;		\
    old=norefresh;	\
    norefresh=1;	\
    cmds;		\
    norefresh=old; } while (0)

static int id2menuindex(int id);
static void BuildBrowserMenu(FL_OBJECT *obj);
static void BuildBrowser(FL_OBJECT *browser, int count, char *names[]);
static void BuildEBrowser(FL_OBJECT *browser, int count, void *base, int elsize);
static void BuildEmodBrowser(FL_OBJECT *browser, int interest);
void may_colorbutton( FL_OBJECT *btn, int index, Color *c );
static void may_ap_colorbutton( FL_OBJECT *btn, int index, Appearance *ap,
				int matbit, Color *c );
static void setRGBinput(FL_OBJECT *colorwheel);
void ui_select(int id);
void ui_keyboard(int ch);
void ui_showpanel(int index, int show);
int  ui_panelshown(int index);
int  ui_name2panel(char *name);
static void ui_color(int index, Color *color);
static int gv_fl_show_form(FL_FORM *, int position, int, char *name);
static void glui_float(FL_OBJECT *, int key, int id, float min, float max);
static void glui_int(FL_OBJECT *, int key, int id, int min, int max);
static int getfloat(FL_OBJECT *, float *);
static int getint(FL_OBJECT *, int *);
static void may_set_slider( FL_OBJECT *obj, float value );
static void may_set_button( FL_OBJECT *btn, int state );
static void may_set_ap_button( FL_OBJECT *btn, Appearance *ap, int flagbit );
static void may_set_iinput( FL_OBJECT *input, int val);
static void may_set_finput( FL_OBJECT *input, float val);
static void may_set_sinput( FL_OBJECT *input, char *val);
static void may_set_browser( FL_OBJECT *browser, int line, int size);
static void adjust_browser( FL_OBJECT *brow, int is, int visible );
static void show_browsing_dir();
static void ui_save();
static void ui_load();
static void ui_filebrowser();
void ShowColor(char *name, Color *old, int index, int val, FL_OBJECT *obj);
void ui_popup_message(char *s);

#define streq(s1,s2)  (!strcmp(s1,s2))

/*
 * This wrapper for fl_show_form() resets SIGCHLD handling
 * to ensure we don't sleep forever in the 4.0 fmfont libraries.
 */
static int
gv_fl_show_form(FL_FORM *form, int posn, int bool, char *name)
{
  void *oldchld = signal(SIGCHLD, SIG_DFL);
  int win = fl_show_form(form, posn, bool, name);
  signal(SIGCHLD, oldchld);
  return win;
}

void
ui_init()
{
  cui_init();
  clight_init();
  panel_init();

  gv_event_mode( OBJROTATE );
  uistate.cursor_on = 1;
}

/* Imported from buildinfo.c, made at link time by Makefile */
extern char builddate[], buildinfo1[], buildinfo2[];

static char *credits[] = {
  "By:",
  "Stuart Levy, Tamara Munzner, Mark Phillips",
  "And:",
  "Celeste Fowler, Charlie Gunn,",
  "Nathaniel Thurston, Scott Wisdom",
  "",
  "The Geometry Center   Copyright (c) 1992",
  "anonymous ftp: geom.umn.edu",
  "email: software@geom.umn.edu",
  "",
  "geomview/OOGL is free software which may be.",
  "obtained via anonymous ftp from the above site.",
  "You can redistribute and/or modify it only",
  "under the terms given in the file COPYING,",
  "which is included in the geomview distribution.",
  "",
  "The National Science and Technology Research",
  "Center for the Computation and Visualization of",
  "Geometric Structures",
  "",
  "University of Minnesota",
  "1300 South Second Street",
  "Minneapolis, MN  55454  USA",
  "",
  "Compiled:",
  buildinfo1,
  buildinfo2
};

panel_init()
{
  register int i, x = 0;
  register struct panel *p;
  char buf[80];
  WnPosition wp;

  create_the_forms();

  if(WnGet(drawerstate.defview.win, WN_PREFPOS, &wp) > 0) {
    x = wp.xmax + 5;
    if (x > getgdesc(GD_XPMAX)) x = getgdesc(GD_XPMAX)-300;
  }
  fl_set_form_position(MainPanel, x, -33);

  for(i = 2, p = &panels[2]; i < P_MAX; i++, p++) { /* don't incl main panel */
    if(p->browse) {
	sprintf(buf, "[%.2s] %s", p->key, p->name);
	fl_add_browser_line( MoreBrowser, p->key[0] ? buf : p->name );
    }
  }

  lights[0] = "ambient";
  for (i=1; i<=MAXLIGHTS; ++i) {
    sprintf(buf, "light %1d", i);
    lights[i] = strdup(buf);
  }

  BuildBrowser( ModeBrowser, uistate.mode_count, uistate.modenames ); 
  BuildEmodBrowser( ExternalBrowser, 1 );
  BuildBrowser( ShadingBrowser, COUNT(shades), shades);
  BuildBrowser( ProjectionBrowser, COUNT(proj), proj);
  BuildBrowser( NormalizationBrowser, COUNT(norm), norm);
  BuildBrowser( ModelBrowser, COUNT(mdl), mdl);
  BuildEBrowser( SaveTypeBrowser, COUNT(save), &save[0].name, sizeof(save[0]) );
  BuildBrowser( LightingBrowser, light_count(), lights);
  BuildBrowser( CreditsBrowser, COUNT(credits), credits);
  fl_set_browser_topline(CreditsBrowser, 1);

  set_light( light_count() > 0 ? 1 : 0 );

  fl_set_object_color(FaceColorButton,FACEINDEX,FACEINDEX);
  fl_set_object_color(EdgeColorButton,EDGEINDEX,EDGEINDEX);
  fl_set_object_color(NormalColorButton,NORMALINDEX,NORMALINDEX);
  fl_set_object_color(BBoxColorButton,BBOXINDEX,BBOXINDEX);
  fl_set_object_color(BackColorButton,BACKINDEX,BACKINDEX);
  fl_set_object_color(LightColorButton,LIGHTINDEX,LIGHTINDEX);

  actioncolor[ACTION_STOP] = 1;
  actioncolor[ACTION_LOOK] = 1;
  actioncolor[ACTION_CENTER] = 1;
  actioncolor[ACTION_RESET] = 1;
  toolcolor[TOOL_ROT] = 4;
  toolcolor[TOOL_TRANS] = 4;
  toolcolor[TOOL_FLY] = 4;
  toolcolor[TOOL_ORBIT] = 4;
  toolcolor[TOOL_ZOOM] = 4;
  toolcolor[TOOL_SCALE] = 4;
  fl_set_object_color(RotateBitmap, toolcolor[TOOL_ROT], 7); 
  fl_set_object_color(TranslateBitmap, toolcolor[TOOL_TRANS], 7); 
  fl_set_object_color(ZoomBitmap, toolcolor[TOOL_ZOOM], 7); 
  fl_set_object_color(FlyBitmap, toolcolor[TOOL_FLY], 7); 
  fl_set_object_color(OrbitBitmap, toolcolor[TOOL_ORBIT], 7); 
  fl_set_object_color(ScaleBitmap, toolcolor[TOOL_SCALE], 7); 
  fl_set_object_color(StopBitmap, actioncolor[ACTION_STOP], 7); 
  fl_set_object_color(LookBitmap, actioncolor[ACTION_LOOK], 7); 
  fl_set_object_color(CenterBitmap, actioncolor[ACTION_CENTER], 7); 
  fl_set_object_color(ResetBitmap, actioncolor[ACTION_RESET], 7); 

  fl_set_bitmap(StopBitmap,bitmap_width,bitmap_height,stopsign_bits);  
  fl_set_bitmap(LookBitmap,bitmap_width,bitmap_height,look_bits);  
  fl_set_bitmap(CenterBitmap,bitmap_width,bitmap_height,center_bits);  
  fl_set_bitmap(ResetBitmap,bitmap_width,bitmap_height,reset_bits);  
  fl_set_bitmap(RotateBitmap,bitmap_width,bitmap_height, rotate_bits);
  fl_set_bitmap(ZoomBitmap,bitmap_width,bitmap_height, zoom_bits);
  fl_set_bitmap(TranslateBitmap,bitmap_width,bitmap_height, translate_bits);
  fl_set_bitmap(FlyBitmap,bitmap_width,bitmap_height, fly_bits);
  fl_set_bitmap(OrbitBitmap,bitmap_width,bitmap_height, orbit_bits);
  fl_set_bitmap(ScaleBitmap,bitmap_width,bitmap_height, scale_bits);

  fl_set_input_return(NearClippingInput,FALSE);
  fl_set_input_return(FarClippingInput,FALSE);
  fl_set_input_return(NormalScaleInput,FALSE);
  fl_set_input_return(LinewidthInput,FALSE);
  fl_set_input_return(FOVInput,FALSE);
  fl_set_input_return(LinesCloserInput,FALSE);
  fl_set_input_return(NormalScaleInput,FALSE);
  fl_set_input_return(RGBInput, FALSE);
  fl_set_object_focus(CameraPanel, CameraHiddenInput1);
  fl_set_object_focus(AppearancePanel, AppearanceHiddenInput1);
  fl_set_object_focus(ToolPanel, ToolHiddenInput1);
  fl_set_input_return(AcceptInput,FALSE);
  fl_set_input_return(CenterIdInput,FALSE);

  /*fl_set_input_return(ColorHiddenInput, TRUE);*/
  fl_set_input_return(MainHiddenInput, TRUE);
  fl_set_input_return(ObscureHiddenInput, TRUE);
  fl_set_input_return(MaterialsHiddenInput, TRUE);
  fl_set_input_return(LightingHiddenInput, TRUE);
  fl_set_input_return(CreditsHiddenInput, TRUE);
  fl_set_input_return(CameraHiddenInput1, TRUE);
  fl_set_input_return(CameraHiddenInput2, TRUE);
  fl_set_input_return(CameraHiddenInput3, TRUE);
  fl_set_input_return(AppearanceHiddenInput1, TRUE);
  fl_set_input_return(AppearanceHiddenInput2, TRUE);
  fl_set_input_return(AppearanceHiddenInput3, TRUE);
  fl_set_input_return(ToolHiddenInput1, TRUE);
  fl_set_input_return(ToolHiddenInput2, TRUE);

  fl_set_slider_bounds(IntensitySlider, 0.0, 1.0);
  fl_set_slider_bounds(ShininessSlider, 1.0, 128.0);
  fl_set_slider_bounds(KsSlider, 0.0, 1.0);
  fl_set_slider_bounds(KdSlider, 0.0, 1.0);
  fl_set_slider_bounds(KaSlider, 0.0, 1.0);
  fl_set_slider_bounds(AlphaSlider, 0.0, 1.0);
 
 sprintf(buf, "geomview %s", GEOMVIEW_VERSION);
  fl_set_object_label(MainPanelTitle, buf);
  fl_set_object_label(CreditsPanelTitle, buf);

  ui_select(uistate.targetid);
  gv_ui_center(uistate.centerid);
}

static int final_initted = 0;
/*
 * This is called after all other initialization (command-line, &c) is done.
 * It makes the main control window visible, if appropriate.
 */
void ui_final_init()
{
  int i;
  final_initted = 1;
  ui_select(CAMID(uistate.targetcam));
  ui_select(GEOMID(uistate.targetgeom));
  lights_changed_check();
  for(i=1; i<P_MAX; i++)
    if(panels[i].shown) ui_showpanel(i, 1);
  ui_objectchange();
}

/*
 * ui_update() is called once each time around the main loop.
 * Anything needing synchronous updates can go here.
 * Right now we just update the emodule browser; changes in it can
 * arise asynchronously, as processes die.
 */
ui_update()
{
  int i;
  emodule *em;
  if(uistate.emod_check) {
    for(i=0, em=VVEC(uistate.emod,emodule); i<VVCOUNT(uistate.emod); ) {
	if(VVEC(uistate.emod,emodule)[i].pid < 0 &&
	    (em->link == NULL || PoolInputFile(em->link) == NULL)) {
		emodule_reap(em);
	} else {
	    i++, em++;
	}
    }
    uistate.emod_check = 0;
  }
  if(uistate.emod_changed) {
    BuildEmodBrowser(ExternalBrowser, uistate.emod_changed);
    uistate.emod_changed = 0;
  }
}
 

int ui_panelshown(int index) { return (unsigned)index >= P_MAX ? 0 : panels[index].shown; }
     
LDEFINE(ui_panel, LVOID,
	    "(ui-panel       PANELNAME  {on|off} [ WINDOW ] )\n\
	Do or don't display the given user-interface panel.\n\
	Case is ignored in panel names.  Current PANELNAMEs are:\n\
		geomview	main panel\n\
		tools		motion controls\n\
		appearance	appearance controls\n\
		cameras		camera controls\n\
		lighting	lighting controls\n\
		obscure		obscure controls\n\
		materials	material properties controls\n\
		command		command entry box\n\
		credits		geomview credits\n\
	By default, the \"geomview\" and \"tools\" panels appear when\n\
	geomview starts.  If the optional Window is supplied, a\n\
	\"position\" clause (e.g. (ui-panel obscure on { position xmin\n\
	xmax ymin ymax }) sets the panel's default position.  (Only\n\
	xmin and ymin values are actually used.)  A present but empty\n\
	Window, e.g.  \"(ui-panel obscure on {})\" causes interactive\n\
	positioning.")
{
  char *panelname;
  int index, on;
  struct panel *p;
  WindowStruct *ws=NULL;
  WnPosition wp;
  
  LDECLARE(("ui-panel", LBEGIN,
	    LSTRING, &panelname,
	    LKEYWORD, &on,
	    LOPTIONAL,
	    LWINDOW, &ws,
	    LEND));
  
  if((index = ui_name2panel(panelname)) == 0) {
    fprintf(stderr, "ui-panel: expected name of a panel, got \"%s\"\n",
	    panelname);
    return Lnil;
  }
  if (ws) {
    p = &panels[index];
    if(p->objp == NULL) return Lnil;
    p->placement = FL_PLACE_SIZE;
    if(WnGet(ws->wn, WN_PREFPOS, &wp) > 0) {
      p->placement = FL_PLACE_POSITION;
      if(*p->objp)
	fl_set_form_position(*p->objp, p->x0=wp.xmin, p->y0=wp.ymin);
    }
  }
  ui_showpanel( index, boolval("ui-panel", on) );
  return Lt;
}

void ui_showpanel(int index, int show)
{
  struct panel *p = &panels[index];
  int wasshown;
  if((unsigned) index >= P_MAX || p->objp == NULL || *p->objp == NULL) return;
  wasshown = p->shown;
  if(show < 0) show = !wasshown;
  p->shown = show;
  if(!final_initted)
    return;
  if(show) {
    int owin = winget();

    if (index == P_FILEBROWSER) ui_filebrowser();
    else if (index == P_INPUT) ui_load();
    else if (index == P_SAVE) ui_save();

    winset(p->winid = gv_fl_show_form(*p->objp, p->placement,
			    TRUE, p->name));
    if(index == P_COLORPICKER && wasshown)
	wintitle(p->name);
    winpop();
    if(owin>0) winset(owin); /* prevent NOSUCHWINDOW error if this is a popup */
  } else if(p->winid > 0) {
    p->x0 = (*p->objp)->x;	/* Record position */
    p->y0 = (*p->objp)->y;
    fl_hide_form(*p->objp);
    p->winid = 0;
    if (p->placement == FL_PLACE_SIZE) p->placement = FL_PLACE_POSITION;
  }
}
  

int ui_name2panel(char *name)
{
  register int i;
  if(strcasecmp(name, "main") == 0) return P_MAIN;
  for(i=P_MAX; --i > 0 && strcasecmp(name, panels[i].name) != 0; ) ;
  if(i==0)
    for(i=P_MAX; --i > 0 && strncasecmp(name, panels[i].name, 4) != 0; ) ;
  return i;
}

int ui_winid2panel(int winid)
{
  register int i;
  for(i = P_MAX; --i > 0 && panels[i].winid != winid; )   ;
  return i;
}

/*-----------------------------------------------------------------------
 * Function:	panel_check
 * Description:	check for and process events in the UI panel
 * Returns:	nothing
 * Author:	mbp
 * Date:	Sat Jan 18 14:51:17 1992
 */
void panel_check()
{
  fl_check_forms();
}

/*		(Used to be in gv_target.)
 * 		The bbox of the picked object becomes uistate.pickedbboxcolor,
 * 		bbox of unpicked object reset to bboxcolor.
 */

ui_highlight(int newid)
{
  int newtype = TYPEOF(newid);
  DGeom *dg;
  DView *v;
  static Color black = {0.0, 0.0, 0.0};
  Color c;
  
  if ( newtype == T_GEOM &&
      (dg = (DGeom *) drawer_get_object(newid)) && dg->bboxap ) {
    c = uistate.pickedbboxcolor;
    gv_bbox_color( GEOMID(uistate.targetgeom), black.r, black.g, black.b ); 
    gv_bbox_color( newid, c.r, c.g, c.b );
  } else if (newtype == T_CAM) { /* pop cam window in case it's obscured */
    v = (DView *) drawer_get_object(newid);
    if (v) {
      mggl_ctxselect(v->mgctx); /* winset happens in here */
      winpop();
    }
  }
}

/* ui_action is separate from ui_event_mode code because they're 
   keeping different old values around 
 */

void ui_action(int val)
{  
  static int oldval = 0;
  static FL_OBJECT *oldbitmap = NULL;
  static FL_OBJECT *newbitmap = NULL;
  int state;

  switch (val) {
  case ACTION_STOP: newbitmap = StopBitmap; break;
  case ACTION_CENTER: newbitmap = CenterBitmap; break;
  case ACTION_RESET: newbitmap = ResetBitmap; break;    
  case ACTION_LOOK: newbitmap = LookBitmap; break;
  }
  if (oldbitmap) fl_set_object_color(oldbitmap, actioncolor[oldval], 7);
  if (newbitmap) {
    fl_set_object_color(newbitmap, 7, actioncolor[val]); 
    fl_set_object_color(newbitmap, actioncolor[val], 7); 
    oldval = val;
    oldbitmap = newbitmap;
  }
  newbitmap = NULL;
}

/*-----------------------------------------------------------------------
 * Function:	ui_event_mode
 * Description:	Handle UI aspects of event mode change.
 * Args:	*mode: new motion mode string
 * Returns:	
 * Author:	munzner,slevy
 * Date:	Fri Jan 31 20:56:46 1992
 */

void ui_event_mode(char *mode)
{
  int index = ui_mode_index(mode);
  static int oldval = 0;
  static FL_OBJECT *oldbitmap = NULL;
  static FL_OBJECT *newbitmap = NULL;

  uistate.mode_current = index;
  adjust_browser( ModeBrowser, index+1, 1);
  fl_select_browser_line( ModeBrowser, index+1 );
  switch (index) {
  case TOOL_ROT: newbitmap = RotateBitmap; break;
  case TOOL_TRANS: newbitmap = TranslateBitmap; break;
  case TOOL_ZOOM: newbitmap = ZoomBitmap; break;
  case TOOL_FLY: newbitmap = FlyBitmap; break;
  case TOOL_ORBIT: newbitmap = OrbitBitmap; break;
  case TOOL_SCALE: newbitmap = ScaleBitmap; break;
  }
  if (oldbitmap != newbitmap) {
    if (oldbitmap)
      fl_set_object_color(oldbitmap, toolcolor[oldval], 7);
    fl_set_object_color(newbitmap, 7, toolcolor[index]); 
    oldval = index;
    oldbitmap = newbitmap;
  }

}

LDEFINE(ui_target, LVOID,
       "(ui-target      ID [yes|no])\n\
	Set the target of user actions (the selected line of the\n\
	target object browser) to ID.  The second argument specifies\n\
	whether to make ID the current object regardless of its type.\n\
	If \"no\", then ID becomes the current object of its type\n\
	(geom or camera).  The default is \"yes\".  This command may\n\
	result in a change of motion modes based on target choice.")
{
  DObject *obj;
  int id, newtype;
  int immediate=YES_KEYWORD;

  LDECLARE(("ui-target", LBEGIN,
	    LID, &id,
	    LOPTIONAL,
	    LKEYWORD, &immediate,
	    LEND));
  immediate = boolval("ui-target", immediate);
  newtype = TYPEOF(id);

  if (id == uistate.targetid ) return Lt;
  if ( !(obj = drawer_get_object(id))
      || (id == TARGETID) || (id == CENTERID)
      || (id == SELF) || (id == UNIVERSE) || (id == PRIMITIVE)) {
    may_set_sinput(TargetIdInput, drawer_id2name(id));
    return Lt;
  }
  if (immediate) {
    /* ui_highlight must be called before set_ui_target_id */
    /* freeze the browser so old target name doesn't flash
       during highlight. */
    fl_freeze_object(TargetIdInput);
    ui_highlight(id); 
    set_ui_target_id( id ); 
    adjust_browser(PickBrowser, id2menuindex(id)+1, 6);
    may_set_sinput(TargetIdInput, drawer_id2name(id));
    fl_unfreeze_object(TargetIdInput);
    ui_select( id );
  } else {
    /* immediate == NOIMMEDIATE: for cases like deleting geometry
     * with the command language when current target is a camera.
     * update targettype but don't change browser.
     */
    switch (newtype) {
    case T_GEOM: uistate.targetgeom = INDEXOF(id); break;
    case T_CAM: uistate.targetcam = INDEXOF(id); break;
    }
  }
  return Lt;
}

LDEFINE(ui_center, LVOID,
       "(ui-center      ID)\n\
	Set the center for user interface (i.e. mouse) controlled\n\
	motions to object ID.")
{
  DObject *obj;
  int id;
  LDECLARE(("ui-center", LBEGIN,
	    LID, &id,
	    LEND));

  if ( (id == NOID) || (id == CENTERID) ) {
    id = uistate.centerid;
  } else {
    set_ui_center(id);
  }
  may_set_sinput(CenterIdInput, drawer_id2name(id));
  return Lt;
}

void ui_mousefocus(int index)
{
  uistate.mousefocus = index;
  if (uistate.targetcam == INDEXOF(FOCUSID)) {
    ui_select(CAMID(uistate.mousefocus));
  }
}

/* GL version does not need to know about either of these. */
void ui_windowWillOpen(DView *dv) { }
void ui_windowDidOpen(DView *dv)  { }

LDEFINE(snapshot, LINT,
#ifdef sgi
       "(snapshot       CAM-ID     FILENAME)\n\
	Save a snapshot of CAM-ID in IRIS rgb image format in file FILENAME\n\
	(a string).   The window is popped above all other windows and\n\
	redrawn before taking the snapshot."
#else
       "(snapshot       CAM-ID     FILENAME)"
#endif
       )
{
  char cmd[512];
  DView *dv;
  WnWindow *wn = NULL;
  WnPosition wp;
  int opts;
  int id;
  char *fname;
  LDECLARE(("snapshot", LBEGIN,
	    LID, &id,
	    LSTRING, &fname,
	    LEND));
  
  if(!ISCAM(id) || (dv = (DView *)drawer_get_object(id)) == NULL || dv->mgctx == NULL) {
    OOGLError(0, "snapshot: id %d: no such camera", id);
    return LCopy(L1);
  }
  mgctxselect(dv->mgctx);
  mgctxget(MG_WINDOW, &wn);
  if(WnGet(wn, WN_CURPOS, &wp) <= 0) {
    OOGLError(0, "snapshot: can't find window for camera %s", dv->name[1]);
    return LCopy(L1);
  }
  winpop();		/* Force window to top */
  mgctxget(MG_SETOPTIONS, &opts);
  drawer_int(id, DRAWER_DOUBLEBUFFER, 0);
  gv_redraw(id);	/* Force it to be redrawn now */
  sginap(10);	/* Will this give window mgr a chance to actually pop the window? */
  gv_draw(id);
  mgctxset(MG_SETOPTIONS, opts, MG_END);
  sprintf(cmd, "scrsave '%s' %d %d %d %d", fname,
	  wp.xmin, wp.xmax, wp.ymin, wp.ymax);
  return LCopy(system(cmd) ? L1 : L0);
}

int
ui_savepanels(char *fname)
{
    FILE *f;
    struct panel *pan;
    int i;

    f = strcmp(fname, "-") ? fopen(fname,"w") : stdout;
    if(f == NULL) {
	fprintf(stderr, "geomview: %s: cannot open: %s\n", fname, sperror());
	return 1;
    }
    fprintf(f, "(progn\n");
    for(i=1, pan = &panels[1]; i < COUNT(panels); i++, pan++) {
	if(!pan->browse) continue;
	if(pan->winid) {
	    winset(pan->winid);
	    getorigin(&pan->x0, &pan->y0);
	}
	fprintf(f, "(ui-panel \"%s\" %s", pan->name, pan->shown ? "on":"off");
	if(pan->x0 >= 0 || pan->y0 >= 0)
	    fprintf(f, " { position %d %d %d %d }",
		pan->x0,pan->x0,pan->y0,pan->y0);
	fprintf(f, ")\n");
    }
    fprintf(f, ")\n");
    if(f != stdout) fclose(f);
    return 0;
}


void ui_objectchange()
{
  if(PickBrowser == NULL) return;	/* Initialized? */
  BuildBrowserMenu(PickBrowser);
  adjust_browser( PickBrowser, id2menuindex(uistate.targetid)+1, 6 );
}

LDEFINE(ui_emodule_define, LVOID,
"(emodule-define  NAME  SHELL-COMMAND ...)\n\
	Define an external module called NAME, which then appears in the\n\
	external-module browser.  The SHELL-COMMAND string\n\
	is a UNIX shell command which invokes the module.\n\
	See emodule-run for discussion of external modules.")
{
  emodule *em;
  int current = fl_get_browser( ExternalBrowser );
  char *title, *cmd;
  LDECLARE(("emodule-define", LBEGIN,
	    LSTRING, &title,
	    LSTRING, &cmd,
	    LEND));

  em = ui_emodule_install(0, strdup(title), (PFI)emodule_run);
  em->text = strdup(cmd);
  BuildEmodBrowser(ExternalBrowser, 1);
  return Lt;
}

/*-----------------------------------------------------------------------
 * Function:	ename_fix
 * Description:	fix up an emodule name for sorting
 * Args:	c: write fixed name here
 *		e: orig name
 *		n: length
 * Returns:	nothing
 * Author:	mbp
 * Date:	Thu Jan 28 11:06:44 1993
 * Notes:	converts names of the form  [D]XXX to XX0[D]
 *		where D is any string and XXX is any string and
 *		XX0 is that string with its last char replaced by '0'.
 *
 *		When sorting modules in the module  browser the
 *		"fixed" names are compared.  The fixed names are not
 *		stored anywhere or used otherwise.
 */
static void ename_fix(c,e,n)
char *c,*e;
int n;
{
  char num[80];
  int ni=0, ei=0;
  if (e[0] != '[') goto copy;
  ei = 1;
  num[0] = '\0';
  while (ei<n && e[ei++] != ']') num[ni++] = e[ei-1];
  if (ei == n) goto copy;
  num[ni] = '\0';
  strncpy(c,e+ei,n);
  c[strlen(c)-1] = '0';
  strcat(c,"[");
  strcat(c,num);
  strcat(c,"]");
  return;
 copy:
  strncpy(c,e,n);
  return;
}

static emod_compare(e1, e2)
emodule *e1, *e2;
{
  char c1[80], c2[80];

  ename_fix(c1,e1->name,80);
  ename_fix(c2,e2->name,80);

  return strcmp(c1,c2);
}

LDEFINE(emodule_sort, LVOID,
"(emodule-sort)\n\
	Sorts the modules in the application browser alphabetically.")
{
  emodule *em;
  char *title, *cmd;
  size_t nel, size;
  LDECLARE(("emodule-sort", LBEGIN,
	    LEND));

  em = VVEC(uistate.emod, emodule);
  nel = VVCOUNT(uistate.emod);
  size = sizeof(emodule);

  qsort(em, nel, size, emod_compare);
  BuildEmodBrowser(ExternalBrowser, 1);
  return Lt;
}


LDEFINE(ui_emodule_start, LVOID,
"(emodule-start  NAME)\n\
	Starts the external module NAME, defined by emodule-define.\n\
	Equivalent to clicking on the corresponding module-browser entry.")
{
  emodule *em;
  int i;
  char *name;
  LDECLARE(("emodule-start", LBEGIN,
	    LSTRING, &name,
	    LEND));
  i = ui_emodule_index(name, &em);
  if(i < 0) return Lnil;
  (*em->func)(em);
  may_set_browser( ExternalBrowser, i+1, 2 );
  return Lt;
}

void ui_replace_mode(char *name, PFI proc, int type, int index)
{
  int val = fl_get_browser(ModeBrowser);
  int id = (uistate.targetid == FOCUSID) ? CAMID(uistate.targetcam) : 
    uistate.targetid;

  ui_reinstall_mode(name, proc, type, index);
  fl_freeze_form(MainPanel);
  BuildBrowser( ModeBrowser,uistate.mode_count,uistate.modenames);
  adjust_browser(ModeBrowser, val, 1);
  fl_unfreeze_form(MainPanel);
}

void ui_add_mode(char *name, PFI proc, int type)
{
  int val = fl_get_browser(ModeBrowser);

  ui_install_mode(name, proc, type);
  BuildBrowser( ModeBrowser,uistate.mode_count,uistate.modenames);
  adjust_browser(ModeBrowser, val, 1);
}

void ui_remove_mode(char *name)
{
  int val = fl_get_browser(ModeBrowser);
  int current;
  
  current = (val == ui_mode_index(name)+1); 
  ui_uninstall_mode(name);
  BuildBrowser( ModeBrowser,uistate.mode_count,uistate.modenames);
  if (current) {
    gv_event_mode( uistate.modenames[0] );
  } else {
    adjust_browser(ModeBrowser, val, 1);
  }
}

void ui_maybe_refresh(int id)
{
   if (norefresh) return;
   if ( id==WORLDGEOM || id==GEOMID(uistate.targetgeom) || id==ALLGEOMS 
       || id==TARGETGEOMID) {
    ui_select(GEOMID(uistate.targetgeom));
  } else if( id==ALLCAMS || id==FOCUSID || id==TARGETCAMID
	|| id==CAMID(uistate.targetcam) || id==CAMID(uistate.mousefocus) ) {
    ui_select(CAMID(uistate.targetcam));
  } else {
    ui_select(NOID);
  }
}

void ui_select(int id)
{
  DObject *obj;

  if(!AppearancePanel) return;	/* Initialized? */
  if(ISGEOM(id)) {
    register DGeom *dg;
    register Appearance *ap;
    int revert = 0;

    if (dg=(DGeom*)drawer_get_object(id)) {
      ap = drawer_get_ap(id);
      revert = ap->override;

      fl_freeze_form( MainPanel );
      may_set_ap_button( FaceDrawButton, ap, APF_FACEDRAW);
      may_set_ap_button( EdgeDrawButton, ap, APF_EDGEDRAW);
      may_set_ap_button( VectDrawButton, ap, APF_VECTDRAW);
      fl_unfreeze_form( MainPanel );

      fl_freeze_form( AppearancePanel );
      may_set_ap_button( NormalDrawButton, ap, APF_NORMALDRAW);
      may_set_ap_button( EvertButton, ap, APF_EVERT);
      may_set_button( BBoxDrawButton, dg->bboxdraw);
      may_set_browser( ShadingBrowser, ap->shading - CONSTANTSHADE +1, 3);
      may_set_iinput( BezDiceInput, dg->bezdice);
      may_set_finput( NormalScaleInput, ap->nscale);
      may_set_iinput( LinewidthInput, ap->linewidth);
      if (ap->mat) {
	revert |= ap->mat->override;
	may_ap_colorbutton( FaceColorButton, FACEINDEX, ap, MTF_DIFFUSE,
		&ap->mat->diffuse);
	may_ap_colorbutton( EdgeColorButton, EDGEINDEX, ap, MTF_EDGECOLOR,
		&ap->mat->edgecolor);
	may_ap_colorbutton( NormalColorButton, NORMALINDEX, ap, MTF_NORMALCOLOR,
		&ap->mat->normalcolor);
	if(dg->bboxap && dg->bboxap->mat)
	  may_ap_colorbutton( BBoxColorButton, BBOXINDEX, dg->bboxap,
			  MTF_EDGECOLOR, &dg->bboxap->mat->edgecolor);
      }
      may_set_button( RevertAppearanceButton, !revert );
      may_set_button( OverrideAppearanceButton, uistate.apoverride );
      fl_unfreeze_form( AppearancePanel );

      fl_freeze_form( MaterialsPanel );
      if (ap->mat) {
	if(ap->mat->valid & MTF_Ka)
	  may_set_slider( KaSlider, ap->mat->ka);
	if(ap->mat->valid & MTF_Kd)
	  may_set_slider( KdSlider, ap->mat->kd);
	if(ap->mat->valid & MTF_Ks)
	  may_set_slider( KsSlider, ap->mat->ks);
	if(ap->mat->valid & MTF_SHININESS)
	  may_set_slider( ShininessSlider, ap->mat->shininess);
	if(ap->mat->valid & MTF_ALPHA)
	  may_set_slider( AlphaSlider, ap->mat->alpha);
      }
      if (ap->valid & APF_TRANSP)
	may_set_button( TransparentButton, ap->flag & APF_TRANSP );
      fl_unfreeze_form( MaterialsPanel );
      
      fl_freeze_form( ObscurePanel );
      may_set_browser( NormalizationBrowser, dg->normalization+1, 3 );
      may_set_button( InertiaButton, uistate.inertia );
      may_set_button( OwnMotionButton, uistate.ownmotion );
      may_set_button( ConstrainedMotionButton, uistate.constrained );
      fl_unfreeze_form( ObscurePanel );

      ApDelete(ap);
    }
    
  } else if(ISCAM(id)) {
    register DView *dv;
    int i;
    float f;
    
    if(dv = (DView *) drawer_get_object(id)) {
      fl_freeze_form( CameraPanel );
      may_colorbutton( BackColorButton, BACKINDEX, &(dv->backcolor));
      CamGet(dv->cam, CAM_NEAR, &f);
      may_set_finput( NearClippingInput, f);
      CamGet(dv->cam, CAM_FAR, &f);
      may_set_finput( FarClippingInput, f);
      CamGet(dv->cam, CAM_FOCUS, &f);
      may_set_finput( FocalInput, f);
      CamGet(dv->cam, CAM_PERSPECTIVE, &i);
      may_set_browser( ProjectionBrowser, i+1, 2);
      CamGet(dv->cam, CAM_FOV, &f);	/* perspective -> degrees, */
      may_set_finput( FOVInput, f );	/*  orthographic -> abs size */
      may_set_iinput( LinesCloserInput, (int)dv->lineznudge );
      may_set_button( DrawCameraButton, dv->cameradraw );
      may_set_button( SoftShadingButton, dv->shader != NULL );
#if 0
      /* old */
      if (   (dv->Item == drawerstate.universe)
	  && (spaceof(WORLDGEOM) == TM_HYPERBOLIC) ) {
	fl_show_object(DrawSphereButton);
	fl_show_object(ModelBrowser);
	may_set_button( DrawSphereButton, dv->hsphere!=NULL );
	may_set_browser( ModelBrowser, dv->hmodel+1, 1 );
      } else {
	fl_hide_object(DrawSphereButton);
	fl_hide_object(ModelBrowser);
      }
#else
      /* new */
      may_set_button( DrawSphereButton, dv->hsphere!=NULL );
      may_set_browser( ModelBrowser, dv->hmodel+1, 1 );
#endif
      fl_unfreeze_form( CameraPanel );
    }
  }
  /* neither camera nor geometry specific */
  if (obj = drawer_get_object(uistate.targetid)) {
    may_set_sinput(TargetIdInput, drawer_id2name(obj->id));
  }

  glui_set_space();

}

glui_set_space()
{
  fl_freeze_form( MainPanel );
  switch (uispace(spaceof(WORLDGEOM))) {
  default:
  case EUCLIDEAN:
    may_set_button( EuclideanButton,  1 );
    may_set_button( HyperbolicButton, 0 );
    may_set_button( SphericalButton,  0 );
    break;
  case HYPERBOLIC:
    may_set_button( EuclideanButton,  0 );
    may_set_button( HyperbolicButton, 1 );
    may_set_button( SphericalButton,  0 );
    break;
  case SPHERICAL:
    may_set_button( EuclideanButton,  0 );
    may_set_button( HyperbolicButton, 0 );
    may_set_button( SphericalButton,  1 );
    break;
  }
  fl_unfreeze_form( MainPanel );
}


void set_light_display(int lightno)
{
  extern Color *light_color();
  extern float light_intensity();
  Color *color;

  adjust_browser( LightingBrowser, lightno+1, 5);
  color = light_color();
  fl_mapcolor(LIGHTINDEX,(int) (color->r*255.0),(int) (color->g*255.0) ,
	      (int) (color->b*255.0));
  fl_redraw_object( LightColorButton );

  fl_set_slider_value( IntensitySlider, light_intensity() );
}

void ui_light_button()
{
  may_set_button( LightEditButton, uistate.lights_shown);
}

/*
 * Show what's happening on the keyboard.
 * Commands are enclosed in [brackets].  In case of error, a '?' appears
 * following the close-bracket.
 */
void
ui_keyboard(int ch)
{
  static char kybd[9];
  static int nextc = 0;
  if(ch <= 0) {
    ui_keyboard(']');
    if(ch < 0) ui_keyboard('?');
    nextc = 0;
  } else {
    if(nextc >= sizeof(kybd)-1) {
	bcopy(kybd+1, kybd, sizeof(kybd)-2);
	nextc = sizeof(kybd)-2;
    } else if(nextc == 0) {
	kybd[nextc++] = '[';
    }
    kybd[nextc++] = ch;
    kybd[nextc] = '\0';
    fl_set_object_label(KeyboardText, kybd);
  }
}

void ui_lights_changed()
{
  if (LightingBrowser) {
    fl_freeze_form(LightingPanel);
    BuildBrowser( LightingBrowser, light_count(), lights);
    set_light( uistate.current_light );
    fl_unfreeze_form(LightingPanel);
  }
}

/*
 * Invoke color popup
 */
void ui_pickcolor(int val)
{
  Color old;
  char *name;
  int index;
  FL_OBJECT *obj;
  DView *dv;

  switch (val) {
  case DRAWER_BACKCOLOR:
	if (dv = (DView*)drawer_get_object( CAMID(uistate.targetcam) )) 
	  ShowColor("Background", &(dv->backcolor), BACKINDEX,
		    val, BackColorButton);
	return;
  case DRAWER_DIFFUSE:
	name = "Faces"; index = FACEINDEX; obj = FaceColorButton; break;
  case DRAWER_EDGECOLOR:
	name = "Edges"; index = EDGEINDEX; obj = EdgeColorButton; break;
  case DRAWER_NORMALCOLOR:
	name = "Normals"; index = NORMALINDEX; obj = NormalColorButton; break;
  case DRAWER_BBOXCOLOR:
	name = "BBoxes"; index = BBOXINDEX; obj = BBoxColorButton;
	break;
  }
  ui_color(index, &old);
  ShowColor(name, &old, index, val, obj);
}

/*
 * Toggle cursor
 */
void ui_curson(int on)
{
  if( (uistate.cursor_on = (on<0) ? !uistate.cursor_on : on) )
    curson();
  else cursoff();
}



/* 
 * Callback Procedures
 */

void CloseThisPanel(FL_OBJECT *obj, long val)
{
  int i;
  for(i=0; i<COUNT(panels); i++)
    if(panels[i].objp && obj->form == *panels[i].objp)
	ui_showpanel(i, 0);
}

void ShowPanel(FL_OBJECT *obj, long val)
{
  ui_showpanel(val, 1);
}

static void ui_color(int index, Color *color)
{
  short r, g, b;

  fl_getmcolor(index, &r, &g, &b);
  color->r = ((float)r)/255.0;
  color->g = ((float)g)/255.0;
  color->b = ((float)b)/255.0;
}

void ColorwheelProc(FL_OBJECT *obj, long val)
{
  if (obj == IntensitySlide)
    fl_set_colorwheel_intensity(Colorwheel, 
				fl_get_slider_value(IntensitySlide));
  fl_get_colorwheel(Colorwheel, &curcol.r);
  setRGBinput(Colorwheel);

  if (curval == DRAWER_BACKCOLOR) {
    gv_backcolor( CAMID(uistate.targetcam), curcol.r, curcol.g, curcol.b );
  } else if (curval == DRAWER_LIGHTCOLOR) {
    set_light_color(&curcol);
  } else if (curval == DRAWER_BBOXCOLOR) {
    gv_bbox_color( GEOMID(uistate.targetgeom), curcol.r, curcol.g, curcol.b );
  } else {
    drawer_color(GEOMID(uistate.targetgeom), curval, &curcol);
  }
  colorchanged = 1;
}

void OKColorProc(FL_OBJECT *obj, long val)
{
  if(!colorchanged) ColorwheelProc(obj, val);
  ui_showpanel(P_COLORPICKER, 0);
}

void RGBInputProc(FL_OBJECT *input, long val)
{
  float rgb[3];
  char *str = fl_get_input(input);
  if(sscanf(str, "%f%f%f", &rgb[0], &rgb[1], &rgb[2]) == 3) {
    fl_set_colorwheel(Colorwheel, rgb);
    fl_set_slider_value(IntensitySlide, fl_get_colorwheel_intensity(Colorwheel));
    ColorwheelProc(Colorwheel, 0);
  } else {
    setRGBinput(Colorwheel);
  }
}

static void setRGBinput(FL_OBJECT *colorwheel)
{
  float rgb[3];
  char str[64];
  fl_get_colorwheel(colorwheel, rgb);
  sprintf(str, "%.3f %.3f %.3f", rgb[0],rgb[1],rgb[2]);
  fl_set_input(RGBInput, str);
}
  
void CancelColorProc(FL_OBJECT *obj, long val)
{
  fl_set_colorwheel(Colorwheel, &oldcol.r);
  fl_set_slider_value(IntensitySlide, 
		      fl_get_colorwheel_intensity(Colorwheel));
  ui_showpanel(P_COLORPICKER, 0);
/*  fl_mapcolor(colindex,(int) (oldcol.r*255.0),(int) (oldcol.g*255.0) ,
		(int) (oldcol.b*255.0));
  fl_redraw_object(curobj); */

  if(!colorchanged)
    return;
  if (curval == DRAWER_BACKCOLOR) {
    gv_backcolor( CAMID(uistate.targetcam), oldcol.r, oldcol.g, oldcol.b );
  } else if (curval == DRAWER_LIGHTCOLOR) {
    set_light_color(&oldcol);
  } else {
    drawer_color(GEOMID(uistate.targetgeom), curval, &oldcol);
  }

}
void HiddenReturnProc(FL_OBJECT *obj, long val)
{
  if (obj->form == ObscurePanel)
    fl_set_object_focus(obj->form, ObscureHiddenInput);
  /* else if (obj->form == ColorPanel)
    fl_set_object_focus(obj->form, ColorHiddenInput); */
  else if (obj->form == LightingPanel)
    fl_set_object_focus(obj->form, LightingHiddenInput);
  else if (obj->form == CreditsPanel)
    fl_set_object_focus(obj->form, CreditsHiddenInput);
  else if (obj->form == MaterialsPanel)
    fl_set_object_focus(obj->form, MaterialsHiddenInput);
  else /*  if (obj->form == MainPanel) */
    fl_set_object_focus(obj->form, MainHiddenInput);
}

void HiddenInputProc(FL_OBJECT *obj, long val)
{
  Event event;
  char *str = fl_get_input(obj);
  char c = str[strlen(str)-1];

  if (c) {
    event.dev = c; 
    event.val = 1;
    fl_set_input(obj, "");
    gv_rawevent(event.dev, event.val, event.x, event.y, event.t);
  }
}



/*
 * Input Panel
 */

void OKInputProc(FL_OBJECT *obj, long val)
{
  char *str = fl_get_input(PopupInput);
  if (str && *str) {
    gv_load(str, NULL);
    ui_showpanel(P_INPUT, 0);
  }
}

void FileBrowserButtonProc(FL_OBJECT *obj, long val)
{
  ui_showpanel(P_INPUT, 0);
  ui_showpanel(P_FILEBROWSER, 1);
}

/*
 * Save panel
 */

static int suitable_id(int savetype, char **namep)
{
   struct saveops *sp = &save[savetype];
   int id = drawer_idbyname(*namep);
   DObject *dobj;

   if(savetype >= 0 && savetype < COUNT(save)) {
	if(sp->ops == &CamOps || sp->ops == &WindowOps
			    || sp->special == SAVE_RMan
			    || sp->special == SAVE_SNAP) {
	    if(!ISCAM(id)) id = FOCUSID;
	} else if(sp->ops == &GeomOps && !ISGEOM(id))
	    id = GEOMID(uistate.targetgeom);
   }
   *namep = drawer_id2name(id);
   return (drawer_get_object(id) == NULL) ? NOID : id;
}

void SaveTypeProc(FL_OBJECT *obj, long val)
{
   char *object = fl_get_input(SaveObjInput);
   if(suitable_id(fl_get_browser(obj)-1, &object) != NOID)
       fl_set_input(SaveObjInput, object);
}

void OKSaveProc(FL_OBJECT *obj, long val)
{
  char *fname = fl_get_input(SaveFileInput);
  char *object = fl_get_input(SaveObjInput);
  int savetype = fl_get_browser(SaveTypeBrowser)-1;
  int id, err;
  Pool *p;
  struct saveops *sp;

  if(fname == NULL || object == NULL || *fname == '\0' || *object == '\0'
		   || savetype < 0)
    return;
  if((id = suitable_id(savetype, &object)) == NOID) {
    fl_set_input(SaveObjInput, "?");
    return;
  }
  sp = &save[savetype];
  switch(sp->special) {
  case SAVE_WIO:
    p = PoolStreamTemp(fname, streq(fname,"-")?stdout:NULL, 1, sp->ops);
    if(p == NULL) {
	fprintf(stderr, "Can't open output: %s: %s\n", fname, sperror());
	return;
    }
    worldio(sp->ops, p, sp->flag, id);
    if(PoolOutputFile(p) != stdout) PoolClose(p);
    PoolDelete(p);
    break;
  case SAVE_RMan:
    gv_rib_display(sp->flag, fname);
    gv_rib_snapshot(id, fname);
    break;
  case SAVE_SNAP:
    err = gv_snapshot(id, fname);
    fprintf(stderr, "\7");	/* Ring bell when done */
    if(err)
	return;
    break;
  case SAVE_PANELS:
    if(ui_savepanels(fname))
	return;
  }
  ui_showpanel(P_SAVE, 0);
  uistate.savewhat = NOID;
}


/*
 * Main Panel
 */

void
QuitProc(FL_OBJECT *obj, long val)
{
  gv_exit();
}

void
HelpProc(FL_OBJECT *obj, long val)
{
  print_help();
}

void
DeleteProc(FL_OBJECT *obj, long val)
{
  gv_delete( uistate.targetid );
}
	       
void
PickBrowserProc(FL_OBJECT *obj, long type)
{
  gv_ui_target( menuindex[fl_get_browser(obj)-1], IMMEDIATE );
}

void MoreBrowserProc(FL_OBJECT *obj, long val)
{
  ui_showpanel(fl_get_browser(obj)-1 + (P_MAIN+1), 1);
}

void
ModeBrowserProc(FL_OBJECT *obj, long val)
{
  int index = fl_get_browser( obj ) - 1;
  if (index < 0) return;
  gv_event_mode(uistate.modenames[index]);
}

void ExternalBrowserProc(FL_OBJECT *obj, long val)
{
  int i = abs(fl_get_browser(obj))-1;
  gv_ui_emodule_start( VVINDEX(uistate.emod, emodule, i)->name );
}

static void BuildEmodBrowser(FL_OBJECT *browser, int interest)
{
  int i;
  emodule *em;

  fl_freeze_object(browser);
  fl_clear_browser(browser);
  for(i=0, em=VVEC(uistate.emod,emodule); i<VVCOUNT(uistate.emod); i++, em++) {
    char line[64];
    sprintf(line, em->pid > 0 ? "@C1@b%.58s" : "%.63s", em->name);
    fl_add_browser_line(browser, line);
  }
  fl_set_browser_topline(browser, interest < 2 ? 0 : interest-1);
  fl_unfreeze_object(browser);
}


/*
 * Tools Panel
 */

void ToolProc(FL_OBJECT *obj, long val)
{
  gv_event_mode(uistate.modenames[val]); 
}

void ActionProc(FL_OBJECT *obj, long val)
{
  int id = uistate.targetid;
  int state;

  switch(val) {
  case ACTION_STOP:
    drawer_stop(NOID);   
    ui_action(ACTION_STOP);
    break;
  case ACTION_LOOK:
    gv_look(GEOMID(uistate.targetgeom), CAMID(uistate.targetcam));
    ui_action(ACTION_LOOK);
    break;
  case ACTION_CENTER:
    drawer_center(id); 
    ui_action(ACTION_CENTER);
    break;
  case ACTION_RESET:
    drawer_center(NOID); 
    ui_action(ACTION_RESET);
    break;
  }
}

void IdInputProc(FL_OBJECT *obj, long val)
{
  switch(val) {
  case UI_CENTER:
    gv_ui_center(drawer_name2metaid(fl_get_input(obj))); break;
  case UI_TARGET:
    gv_ui_target(drawer_name2metaid(fl_get_input(obj)), IMMEDIATE); break;
  }
}


/* 
 * Appearance Panel
 */

void RevertAppearanceProc(FL_OBJECT *obj, long val)
{
  drawer_set_ap( GEOMID(uistate.targetgeom), NULL, NULL );
}

void ToggleButtonProc(FL_OBJECT *obj, long val)
{
  int onoff = fl_get_button( obj );
  drawer_int(GEOMID(uistate.targetgeom), val, onoff);
}

void
ShadingBrowserProc(FL_OBJECT *obj, long val)
{
  drawer_int(GEOMID(uistate.targetgeom), DRAWER_SHADING,
	     fl_get_browser( obj ) - 1 + CONSTANTSHADE);
}


void ColorChange(FL_OBJECT *obj, long val)
{
  ui_pickcolor(val);
}
void SliderProc(FL_OBJECT *obj, long val)
{
  float f = fl_get_slider_value(obj);
  QUIETLY( drawer_float(GEOMID(uistate.targetgeom), val, f) );
}

void LinesCloserProc(FL_OBJECT *obj, long val)
{ 
  glui_float(obj, val, CAMID(uistate.targetcam), -10000., 10000.); 
  HiddenReturnProc(obj,val);
}

void NormalScaleProc(FL_OBJECT *obj, long val)
{ 
  glui_float(obj, val, GEOMID(uistate.targetgeom), 0., 999.); 
}

void LinewidthProc(FL_OBJECT *obj, long val)
{ 
  glui_int(obj, val, GEOMID(uistate.targetgeom), 1, 256); 

}

/* 
 * Camera Panel
 */

void ClippingProc(FL_OBJECT *obj, long val)
{ 
   /* Negative clipping planes can be useful, especially in
      hyperbolic/orthogonal mode --njt  */
   glui_float(obj, val, CAMID(uistate.targetcam), -1e20, 1e20); 
}

void FOVProc(FL_OBJECT *obj, long val)
{ 
  glui_float(obj, val, CAMID(uistate.targetcam), 1e-6, 179.999); 
}

void FocalProc(FL_OBJECT *obj, long val)
{ glui_float(obj, val, TARGETCAMID, 1e-20, 1e20);
}

void BezDiceProc(FL_OBJECT *obj, long val)
{ 
  glui_int(obj, val, GEOMID(uistate.targetgeom), 0, 999); 
}

void SoftShadingProc(FL_OBJECT *obj, long val)
{ gv_soft_shader(TARGETCAMID, fl_get_button(obj) ? ON_KEYWORD : OFF_KEYWORD);
}

/* 
 * Obscure Panel
 */

void
ObscureBrowserProc(FL_OBJECT *obj, long val)
{
  int i = fl_get_browser( obj ) - 1;
  int id;
  if (i >= 0) {
    switch (val) {
    case DRAWER_SAVE: id = NOID; break;
    case DRAWER_PROJECTION: id = CAMID( uistate.targetcam ); break;
    case DRAWER_NORMALIZATION: id = GEOMID( uistate.targetgeom ); break;
    default:
      OOGLError(1,"invalid val (%d) in ObscureBrowserProc", val);
      return;
    }
    drawer_int( id, val, i );
  }
}


void
SpaceButtonProc(FL_OBJECT *obj, long val)
{
  int i;
  switch (val) {
  default:
  case EUCLIDEAN:
    i = EUCLIDEAN_KEYWORD;
    break;
  case HYPERBOLIC:
    i = HYPERBOLIC_KEYWORD;
    break;
  case SPHERICAL:
    i = SPHERICAL_KEYWORD;
    break;
  }
  gv_space(i);
  glui_set_space();
}


void
ModelBrowserProc(FL_OBJECT *obj, long val)
{
  int i = fl_get_browser( obj ) - 1;
  gv_hmodel(CAMID(uistate.targetcam), hmodelkeyword("ModelBrowserProc", i));
}

void
DrawCameraProc(FL_OBJECT *obj, long val)
{
  int button = fl_get_button(obj);
  drawer_int(CAMID(uistate.targetcam), DRAWER_CAMERADRAW, button );
}

void
DrawSphereProc(FL_OBJECT *obj, long val)
{
  int button = fl_get_button(obj);
  drawer_int(CAMID(uistate.targetcam), DRAWER_HSPHERE, button );
}

/*
 * Lighting Panel
 */

void LightingBrowserProc(FL_OBJECT *obj, long val)
{
  int lightno = fl_get_browser(obj) - 1;
  set_light( lightno );
}

void LightColorButtonProc(FL_OBJECT *obj, long val)
{
  extern Color *light_color(); /* in clights.c */

  ShowColor("Light Color", light_color(), LIGHTINDEX, val, obj);
}

void AddLightButtonProc(FL_OBJECT *obj, long val)
{
  if (light_count() > 8) {
    OOGLError(0, "Can't have more than 8 lights in GL\n");
    return;
  }
  add_light();
}

void DelLightButtonProc(FL_OBJECT *obj, long val)
{
  /* don't delete ambient light ! */
  if (uistate.current_light == 0) return;
  delete_light();
}


void LightEditButtonProc(FL_OBJECT *obj, long val)
{
  int editing = fl_get_button(obj);

  light_edit_mode(editing);
}

/* 
 * Browser Panel
 */

static void show_browsing_dir()
{
    char hint[512];
    if(browsing_dir) {
	sprintf(hint, "%s/", browsing_dir);
	fl_set_input(AcceptInput, hint);
    }
}

static int maybe_rebuild_file_browser(char *str, char *path)
{
  char *array[512];
  DIR *dirp;
  struct direct *dp;
  register int i, j;
  char fullpath[512];
  char *s = str;

  if (path && str[0] != '/') {
    sprintf(fullpath, "%s/%s", path, str);
    s = fullpath;
  }
  dirp = opendir(s);
  if (dirp != NULL) {
    if(browsing_dir) free(browsing_dir);
    browsing_dir = strdup(s);
    show_browsing_dir();
    for(i = 0; (dp = readdir(dirp)) != NULL; ) {
#if 0
      if (dp->d_name[0] == '.' && dp->d_name[1] == '\0')
	continue;
#endif
      if (dp->d_name[0] == '.' && dp->d_name[1] != '.')
	continue;
	/* Straight insert sort.  Could qsort(), but let's save space. */
      for(j = i; --j >= 0 && strcmp(dp->d_name, array[j]) < 0; )
	array[j+1] = array[j];
      array[j+1] = strdup(dp->d_name);
      if(i == COUNT(array)-2) {
	array[i++] = strdup("<more entries omitted>");
	break;
      }
      i++;
    }
    closedir(dirp);
    BuildBrowser(FileBrowser, i, array);
    while(--i >= 0) free(array[i]);
    return 1;
  }
  return 0;
}

void DirectoryBrowserProc(FL_OBJECT *obj, long val)
{
  if (maybe_rebuild_file_browser(fl_get_browser_line(obj,fl_get_browser(obj)), 
				 NULL))
    show_browsing_dir();
}

void FileBrowserProc(FL_OBJECT *obj, long val)
{
  int i = fl_get_browser( obj ) - 1;
  char *str;

  if ( (i >= 0) && (str = fl_get_browser_line(obj, i+1)) ) {
    maybe_rebuild_file_browser(str,browsing_dir);
  }
}

void AcceptProc(FL_OBJECT *obj, long val)
{
  char *str = fl_get_input(obj);
  if (str && *str) {
    fl_deselect_browser_line(DirectoryBrowser, 
			     fl_get_browser(DirectoryBrowser));
    fl_deselect_browser_line(FileBrowser, fl_get_browser(FileBrowser));
    if (!maybe_rebuild_file_browser(str, NULL)) {
      gv_load(str, NULL);
    }
  }
}

void
LoadProc(FL_OBJECT *obj, long val)
{
  char *str,fullpath[256];
  int line;

  if (val == UI_ADDCAMERA) {
    CameraStruct cs;
    cs.h = NULL;
    cs.cam = NULL;
    gv_new_camera(NULL, &cs);
  } else if (((line = fl_get_browser(FileBrowser)) > 0) &&
	     (str = fl_get_browser_line(FileBrowser,line))) {
    /* val == UI_ADD or UI_REPLACE */

    if(browsing_dir && str[0] != '/') {
      sprintf(fullpath, "%s/%s", browsing_dir, str);
      str = fullpath;
    }
    if (val == UI_REPLACE) gv_delete(TARGETGEOMID);
    gv_load(str, NULL);
  }
}

/*
 * Command Panel
 */

/* hack: this is actually the callback of a hidden return button
 * "CommandReturnButton" instead of the CommandInput so that
 * when you hit return you re-execute the previous command
 * (Forms will not trigger an input's callback if nothing has changed.)
 */

void CommandInputProc(FL_OBJECT *obj, long val)
{
  char *str = fl_get_input(CommandInput);
  if (str && *str)
    comm_object(str, &CommandOps, NULL, NULL, COMM_NOW);
}

/*
 * Private procedures: make these static soon? 
 */

static int id2menuindex(int id)
{
  int i;

  if (drawer_cam_count() == 1 &&
      (id == TARGETCAMID || id == ALLCAMS || id == FOCUSID))
    id = real_id(id);
  for (i=0; i<menucount; ++i)
    if (menuindex[i] == id) return i;
  return -1;
}


static void may_ap_colorbutton( FL_OBJECT *btn, int index, Appearance *ap,
					int matbit, Color *c )
{
  if(ap->mat && (ap->mat->valid & matbit)) {
    int boxtype = ap->mat->override & matbit ? FL_DOWN_BOX : FL_FRAME_BOX;
    may_colorbutton( btn, index, c );
    if(btn->boxtype != boxtype)
	fl_set_object_boxtype( btn, boxtype );
  }
}

void may_colorbutton( FL_OBJECT *btn, int index, Color *c )
{
  short cwas[3], cis[3];
  cis[0] = 255*c->r;  cis[1] = 255*c->g; cis[2] = 255*c->b;
  fl_getmcolor( index, &cwas[0],&cwas[1],&cwas[2] );
  if(memcmp(cwas, cis, sizeof(cwas))) {
      fl_mapcolor( index, cis[0],cis[1],cis[2] );
      fl_set_object_color( btn, index, index );
  }
}

static void may_set_ap_button( FL_OBJECT *btn, Appearance *ap, int flagbit )
{
  int brightened = (ap->flag & flagbit) ? 1 : 0;
  int box = (ap->override & flagbit) != 0 ? FL_DOWN_BOX : FL_FRAME_BOX;

  if(fl_get_button(btn) != brightened)
    fl_set_button(btn, brightened);
  if(btn->boxtype != box)
    fl_set_object_boxtype(btn, box);
}

static void may_set_button( FL_OBJECT *btn, int state )
{
  state = (state != 0);
  if(fl_get_button(btn) != state)
    fl_set_button(btn, state);
}

/* note inputs deal with strings, not numbers */
static void may_set_iinput( FL_OBJECT *input, int val)
{
  char buf[10];
  int i;
  if (!getint(input, &i) || i != val) {
    sprintf(buf,"%3d",val);
    fl_set_input(input, buf);
  }
}

/* note inputs deal with strings */
static void may_set_sinput( FL_OBJECT *input, char *val)
{
  char *str = fl_get_input(input);
  if ((strcmp(str,val))) { /* if str != val */
    fl_set_input(input, val);
  }
}

/* note inputs deal with strings, not numbers */
static void may_set_finput( FL_OBJECT *input, float val)
{
  char buf[80];
  float f;
  if (!getfloat(input, &f) || f != val) {
    sprintf(buf,"%5g",val);
    fl_set_input(input, buf);
  }
}

static void may_set_browser( FL_OBJECT *browser, int line, int size)
{
  if(fl_get_browser(browser) != line) 
    adjust_browser(browser, line, size);
}


/*
 * Adjust a browser to make its new choice visible.
 * If possible, leave the old choice visible too.
 */
static void adjust_browser( FL_OBJECT *brow, int is, int visible )
{
  int was, max;

  was = fl_get_browser(brow);
  if(was == is)
    return;
  max = fl_get_browser_maxline( brow );

  /*
   * If we can't place both old & new values in window, don't try.
   */
  if(was - is <= -visible || was - is >= visible)
    was = is;

  fl_select_browser_line( brow, is );

  if(was <= visible && is <= visible) {
    fl_set_browser_topline( brow, 1 );	/* First preference: hop to top.  */
  } else if(was > max - visible && is > max - visible) {
    fl_set_browser_topline( brow, max - visible + 1 ); /* Second: hop to bottom */
  } else if(was == max - visible && is == max - visible) {
    fl_set_browser_topline( brow, max - visible ); /* Third: special case */
  } else {
    /* Last: center both in window */
    fl_set_browser_topline( brow, (was+is-visible) / 2 + 1);
  }
}

static void
BuildBrowser(FL_OBJECT *browser, int count, char *names[])
{
  int i;

  fl_freeze_object( browser );
  fl_clear_browser( browser );
  for (i=0; i<count; ++i)
    fl_add_browser_line(browser, names[i]);
  fl_unfreeze_object( browser );
}

static void
BuildEBrowser(FL_OBJECT *browser, int count, void *base, int elsize)
{
  int i;
  fl_freeze_object( browser );
  fl_clear_browser( browser );
  for(i=0; i<count; i++)
    fl_add_browser_line(browser, *(char **)((char *)base + i*elsize));
  fl_unfreeze_object( browser );
}

BuildObjectMenuList( char ***menulist, int **menuindex, int *count )
{
  register int i;
  int n, max, camcount;
  register DObject *obj, **objs;
  char buf[64];

  if ((camcount = drawer_cam_count()) > 1) {
    /* + 1 for "Current Camera" entry */
    *count = camcount + drawer_geom_count() + 1;     

    /* an obscure place to put code for the obscure panel... */
    fl_show_object(DrawCameraButton);
  } else {
    /* don't "Current Camera", just c0 */
    fl_hide_object(DrawCameraButton);
    *count = camcount + drawer_geom_count();
  }

  *menulist = OOGLNewNE(char *, *count, "no space for menulist array");
  *menuindex = OOGLNewNE(int, *count, "no space for menuindex array");

  n = 0;
  objs = (DObject**)dgeom;
  for (i=0; i<dgeom_max; ++i) {
    if ( (obj=objs[i]) != NULL && ((DGeom*)obj)->citizenship != ALIEN) {
      if (obj->name[1] != NULL) 
	sprintf(buf, "[%s] %s", obj->name[0], obj->name[1]);
      else
	strcpy(buf, obj->name[0]);
      (*menuindex)[n] = obj->id;
      (*menulist)[n] = strdup(buf);
      ++n;
    }
  }
  if (camcount > 1) {
    (*menuindex)[n] = FOCUSID;
    (*menulist)[n] = strdup("[c] Current Camera");
    ++n;
  }
  objs = (DObject **)dview;
  for (i=0; i<dview_max; ++i) {
    if ( (obj=objs[i]) != NULL ) {
      if (obj->name[1] == NULL) obj->name[1] = "Default Camera";
      sprintf(buf, "[%s] %s", obj->name[0], obj->name[1]);
      (*menuindex)[n] = obj->id;
      (*menulist)[n] = strdup(buf);
      ++n;
    }
  }
}

DestroyObjectMenuList( char **menulist, int count )
{
  while ( --count >= 0 )
    OOGLFree(menulist[count]);
  OOGLFree(menulist);
}

static void
BuildBrowserMenu(FL_OBJECT *obj)
{
  register int i;
  char **menulist;

  fl_freeze_object( obj );
  fl_clear_browser( obj );
  if (menuindex) OOGLFree(menuindex);
  BuildObjectMenuList(&menulist, &menuindex, &menucount);
  for (i=0; i<menucount; ++i)
    fl_add_browser_line(obj, menulist[i]);
  fl_unfreeze_object( obj );
  DestroyObjectMenuList(menulist, menucount);
}



static void
may_set_slider( FL_OBJECT *obj, float value )
{
  if (fl_get_slider_value(obj) != value)
    fl_set_slider_value(obj, value);
}

/*
 * Utility routines for interpreting input values
 */

static void
glui_int(FL_OBJECT *obj, int key, int id, int min, int max)
{
    int i;
    if(getint(obj, &i)) {
	if(i < min) i = min;
	else if(i > max) i = max;
	drawer_int(id, key, i);
    } else {
	ui_select(id);
    }
}

static void
glui_float(FL_OBJECT *obj, int key, int id, float min, float max)
{
    float f;
    if(getfloat(obj, &f)) {
	if(f < min) f = min;
	else if(f > max) f = max;
	drawer_float(id, key, f);
    } else {
	ui_select(id);
    }
}
static int
getfloat(FL_OBJECT *input, float *fp)
{
   char *after, *str = fl_get_input(input);
   *fp = strtod(str, &after);
   return str != after;
}

static int
getint(FL_OBJECT *input, int *ip)
{
   char *after, *str = fl_get_input(input);
   *ip = strtol(str, &after, 0);
   return str != after;
}


void
ShowColor(char *name, Color *old, int index, int val, FL_OBJECT *obj)
{
  int winid, key = 0;

  curval = val;
  colindex = index;
  CoCopy(old,&oldcol);	    
  CoCopy(old,&curcol);	    
  curobj = obj;
  fl_set_colorwheel(Colorwheel, &oldcol.r);
  fl_set_slider_value(IntensitySlide, 
		      fl_get_colorwheel_intensity(Colorwheel));
  setRGBinput(Colorwheel);
  panels[P_COLORPICKER].name = name;
  ui_showpanel(P_COLORPICKER, 1);
  /* turn on drawing of relevant thing automatically if you change the color */
  switch(val) {
  case DRAWER_DIFFUSE:	key = DRAWER_FACEDRAW; break;
  case DRAWER_EDGECOLOR: key = DRAWER_EDGEDRAW; break;
  case DRAWER_NORMALCOLOR: key = DRAWER_NORMALDRAW; break;
  case DRAWER_BBOXCOLOR: key = DRAWER_BBOXDRAW; break;
  default: return;
  }
  drawer_int(GEOMID(uistate.targetgeom), key, 1);
}


static void ui_filebrowser()
{
  char **dirp = getfiledirs();
  char *dirpath, path[1024], *getwd(), *str, *line, *thisdir;
  int i, changed, selected;
  static char *array[64];
  int prefer = -1;

  selected = fl_get_browser(DirectoryBrowser)-1;
  changed = 0;
  for (i=0; dirp[i] != NULL; i++) {
    thisdir = strcmp(dirp[i],".") ? dirp[i] : getwd(path);
    if (!(array[i]) || (strcmp(array[i],thisdir))) { 
	if (array[i]) OOGLFree(array[i]);
	array[i] = strdup(thisdir);
	changed = 1;
    }
    if((prefer < 0 || i == selected) && our_space_dir(thisdir))
	prefer = i;
  }
  if(prefer < 0) prefer = selected<0 ? 0 : selected;
  if (changed || prefer != selected) {
    BuildBrowser(DirectoryBrowser, i, array);
    may_set_browser( DirectoryBrowser, prefer+1, 3 );
    DirectoryBrowserProc(DirectoryBrowser, 0);
  }
  show_browsing_dir();
}

static void ui_save()
{
  static char input[128];
  static int num = 1;
  static char *oldname = "";
  static char *newname = "";
  static char *oldstem = "";
  
  int id = uistate.savewhat;
  DObject *dobj = drawer_get_object(id);
  int savetype;
  
  if(dobj == NULL || dobj->name[0] == NULL)
    id = WORLDGEOM, dobj = (DObject*)dgeom[0];
  fl_set_object_focus(SavePanel, SaveFileInput);
  fl_set_input(SaveObjInput, dobj->name[0]);
  if (!fl_get_browser(SaveTypeBrowser)) 
    fl_select_browser_line(SaveTypeBrowser, 1);
  SaveTypeProc(SaveTypeBrowser, 1);
}

static void ui_load()
{
  fl_set_object_focus(InputPanel, PopupInput);
  fl_set_input(PopupInput, "");
}

static void PopupMessageOKProc(FL_OBJECT *obj, long data)
{
  fl_hide_form((FL_FORM*)data);
  fl_free_form((FL_FORM*)data);
}

static FL_FORM *make_popup(s)
char *s;
{
  FL_OBJECT *obj;
  FL_FORM *form;
  char *ss = strdup(s), *t;
  form = fl_bgn_form(FL_NO_BOX,400.0,230.0);
  obj = fl_add_box(FL_UP_BOX,0.0,0.0,400.0,230.0,"");
  obj = fl_add_button(FL_NORMAL_BUTTON,170.0,10.0,60.0,40.0,"OK");
    fl_set_object_lsize(obj,FL_LARGE_FONT);
    fl_set_object_lstyle(obj,FL_BOLD_STYLE);
    fl_set_call_back(obj,PopupMessageOKProc,(long)form);
  obj = fl_add_browser(FL_NORMAL_BROWSER,10.0,60.0,380.0,160.0,"");
    fl_set_object_boxtype(obj,FL_NO_BOX);
  t = strtok(ss, "\n");
  do {
    fl_add_browser_line( obj, t );
    t = strtok(NULL, "\n");
  } while (t != NULL);
  free(ss);
  fl_end_form();
  return form;
}

/*-----------------------------------------------------------------------
 * Function:	ui_popup_message
 * Description:	pop up a message in a dialog box
 * Args:	s: the message; may contain newlines
 * Returns:	
 * Author:	mbp
 * Date:	Mon Feb  1 12:09:12 1993
 * Notes:	dialog box stays around till the user presses its
 *		  "OK" button
 */
void ui_popup_message(char *s)
{
  FL_FORM *popup = make_popup(s);
  fl_show_form(popup, FL_PLACE_CENTER, TRUE, "Geomview Message");
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.