/* app.c
 * geg, a GTK+ Equation Grapher
 * David Bryant 1998
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif /* M_PI */
#ifndef M_E
#define M_E 2.7182818284590452354
#endif /* M_E */

/* minimum distance between pressing and releasing mouse button for */
/* selection to be acknowledged */
#define MINSEL 5

// #include <pixmaps/new.xpm>
// #include <pixmaps/erase.xpm>
#include <pixmaps/restart.xpm>
#include <pixmaps/xy.xpm>
#include <pixmaps/zoom0.xpm>
#include <pixmaps/in.xpm>
#include <pixmaps/out.xpm>
#include <pixmaps/edit.xpm>
#include <pixmaps/print.xpm>
#include <pixmaps/prefs.xpm>
#include <pixmaps/pi.xpm>
#include <pixmaps/grid.xpm>
#include <pixmaps/sequence.xpm>
#include <pixmaps/sine.xpm>
#include <pixmaps/exit.xpm>
#include <pixmaps/zoom.xbm>
#include <pixmaps/zoom_m.xbm>
#include <pixmaps/aint.xbm>
#include <pixmaps/aint_m.xbm>
#include <pixmaps/fint.xbm>
#include <pixmaps/fint_m.xbm>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "app.h"
#include "about.h"
#include "help.h"
#include "prefs.h"
#include "formulas.h"
#include "log.h"
#include "misc.h"
#include "colors.h"
#include "tokeniser.h"
#include "parser.h"
#include "i18n.h"

/* global data */

GdkColor black, white;
GdkColormap *colormap = NULL;
gdouble var[ENDVARIABLES];                      /* internal variables */
struct_ranges ranges;                           /* range limits */
gdouble fx = 1.0, fy = 1.0, ratio = 1.0;
gint mute = 0;
gchar *template = NULL;
gchar *geg_file = NULL, *eps_file = NULL, *svg_file = NULL, *pdf_file = NULL;
gchar *data_file = NULL;
parse_tree *fun[26];
struct_integ integr[26];

#if 0
gchar *varname[] = {
  "der_step",
  "tgt_left",
  "tgt_right",
  "mult",
  "incr",
  "tgtl",
  "tgtr",
  "actv",
  "gap",
  "lw",
  "color",
  "ptrad",
  "amin",
  "amax",
  "tmin",
  "tmax"
};
#endif

gchar *ps_header = "\
%% macro definitions\n\
/m { ratio mul moveto } def\n\
/l { ratio mul lineto } def\n\
/d { ratio mul point_radius fx div 0 360 arc fill } def\n\
/s { stroke } def\n\
/sl { do_formulas { show } { pop } ifelse } def\n\
/sr { do_formulas { dup stringwidth pop neg 0 rmoveto show }\n\
                  { pop } ifelse } def\n\
/setcolor { do_color { 3 mul dup\n\
            colors exch get exch 1 add dup\n\
	    colors exch get exch 1 add\n\
	    colors exch get setrgbcolor }\n\
            { 5 ge { colors 15 get setgray }\n\
                   { colors 12 get setgray } ifelse } ifelse } def\n\
/setbg { do_color { 0 setcolor } { 1 setgray } ifelse } def\n\
/xt { do_xtick { do_grid { 3 setcolor dup ymin m ymax l s }\n\
      { 2 setcolor dup 1 fy div m -1 fy div l s } ifelse } if } def\n\
/yt { do_ytick { do_grid { 3 setcolor dup xmin exch m xmax exch l s }\n\
      { 2 setcolor dup 1 fx div exch m -1 fx div exch l s } ifelse } if } def\n\
/symbol { /Symbol findfont number_size fx div 2.5 div scalefont setfont } def\n\
/helv { /Helvetica findfont formula_size fx div 2.5 div scalefont setfont } def\n\
/xp { do_xval { exch number_size -2.5 div fy div m dup stringwidth pop\n\
                do_grid { neg ( ) stringwidth pop 0.8 mul sub }\n\
		{ -0.5 mul } ifelse 0 rmoveto 1 setcolor show }\n\
              { pop pop } ifelse } def\n\
/xz { do_xval { exch number_size -2.5 div fy div m dup stringwidth pop neg\n\
                ( ) stringwidth pop 0.8 mul sub 0 rmoveto 1 setcolor show }\n\
              { pop pop } ifelse } def\n\
/yp { do_yval { exch do_grid { -1.4 } { -1.8 } ifelse fx div exch m dup\n\
                stringwidth pop neg number_size do_grid { -2.6 } { -10 } ifelse\n\
		div fx div rmoveto 1 setcolor show } { pop pop } ifelse } def\n\
/slw { fx div setlinewidth} def\n\
/do_xval do_xval do_Ox and def\n\
/do_yval do_yval do_Oy and def\n\
/do_xtick do_xval do_grid or def\n\
/do_ytick do_yval do_grid or def\n\
1 setlinejoin\n\n\
%% clipbox\n\
xmin dup ymin m ymax l xmax dup ymax l ymin l closepath\n\
gsave setbg fill grestore\n\
clip do_box { box_lw 2 mul slw stroke } { newpath } ifelse\n\n\
";

static FILE *fd = NULL;
static GtkWidget *xmin_en, *xmax_en, *ymin_en, *ymax_en, *tmin_en, *tmax_en;
static GtkWidget *wi, *ed=NULL, *tb, *da, *eq_co, *func_la, *graph_fr;
static GdkPixmap *pixmap = NULL;
static GtkTextBuffer *te_buf;
static GtkTextIter te_iter;
static GdkGC *back_gc = NULL, *func_gc = NULL;
static GdkGC *numb_gc = NULL, *axes_gc = NULL, *grid_gc, *sel_gc = NULL;
static gint x_down, y_down;
static GdkCursor *cursors[4];

/* locally global data */
static void release_event(GtkWidget *widget, GdkEventButton *event,
			  gpointer data);
static void selection_event(GtkWidget *widget, GdkEventMotion *event, 
			    gpointer data);
static gint solve_fint(gchar **formulas, gint nformulas, 
		       gdouble x1, gdouble x2, 
		       gdouble y1, gdouble y2);
static gint solve_aint(gchar **formulas, gint nformulas,
		       gdouble x1, gdouble x2,
		       gdouble y1, gdouble y2);
static void edit_formulas_event(GtkWidget *widget, GdkEventButton *event,
			gpointer data);
static void edit_geg_event(GtkWidget *widget, GdkEventButton *event,
			gpointer data);
static void edit_eps_event(GtkWidget *widget, GdkEventButton *event,
			gpointer data);
static void print_event(GtkWidget *widget, GdkEventButton *event,
			gpointer data);
static void press_event(GtkWidget *widget, GdkEventButton *event,
			gpointer data);
static void motion_event(GtkWidget *widget, GdkEventMotion *event,
			 gpointer data);
static void leave_event(GtkWidget *widget, GdkEvent *event, gpointer data);
static gint configure_event(GtkWidget *widget, GdkEventConfigure *event,
			    gpointer data);
static gint expose_event(GtkWidget *widget, GdkEventExpose *event,
			 gpointer data);
static void redraw_event(GtkWidget *widget, gpointer data);
static void drawing_init(GtkWidget *widget, gpointer data);
static gint process_formula(gchar *formula, gint number);
static void clear_pixmap(void);
static void draw_axes(void);
static gint range_ok(gint dofix);
static gint pixmap_x(gdouble x);
static gint pixmap_y(gdouble y);
static gdouble real_x(gint x);
static gdouble real_y(gint y);
static void reset_size_toplevel();
static void process_output(gint mode);
static void file_save(GtkWidget *widget, gpointer data);
static void geg_file_save(gchar *file_selected);
static void geg_file_open(GtkWidget *widget, gpointer data);
static void refresh_graph(GtkWidget *widget, gpointer data);
static void update_functions(GtkWidget *widget, gpointer data);
static void zoom_event(GtkWidget *widget, gpointer data);
static void entry_changed_event(GtkWidget *widget, gpointer data);
static void go_event(GtkWidget *widget, gpointer data);
static void export_parameters(GtkWidget *widget, gpointer data);
static void reset_preferences(GtkWidget *widget, gpointer data);
static void set_graphic_frame_title();
static void restart_event(GtkWidget *widget, gpointer data);
static void update_ranges(void);
static void entry_signals(gchar *flag);
static void parse_rest_of_file(FILE *fp);
static void parse_data_file(void);
static void exit_geg(void);
static void draw_grid(GtkWidget *widget, gpointer data);
static void set_coordinates(GtkWidget *widget, gpointer data);
static void show_mode(gint i);
static void rotate_mode(GtkWidget *widget, gpointer data);
static void sequence_mode(GtkWidget *widget, gpointer data);
static void compound_entry(char *title, GtkWidget **entry, GtkWidget *vbox);
static GtkWidget *create_pixmap(GtkWidget *widget, gchar **data);

/* parse_rcfile, parses ~/.gegrc
 */
void
parse_input_file(void)
{
  strncpy(rc_file, getenv("HOME"), 248);
  strcat(rc_file, "/.gegrc");
  /* load preferences */
  prefs_rc_parse(rc_file, DEF_RC_INIT);
}

static void
parse_rest_of_file(FILE *fp) {
  gchar *ptr;  
  gint found = 0;
  while(fgets(template, 256, fp)) {
    ptr = strchr(template, '\n');
    if (ptr) *ptr = '\0';
    ptr = template;
    while (isspace(*ptr)) ++ptr;
    if (*ptr == '\0') {
      /* empty line is viewed as comment "##" */
      if (found) (void)formula_add(NULL, "##");
      continue;
    }
    if (*ptr == '#') {
      if (!found) { found = 1; continue; }
    } else
      found = 1;
    if (formula_find(ptr))
      goto parse_end;
      /* draw the formula and add it to the list if it parses ok */
    if(!formula_add(NULL, ptr))
      goto parse_end;	
  }
parse_end:
  fclose(fp);
}

static void
parse_data_file(void)
{
  FILE *fp;
  if (data_file) {
    sprintf(template, "%s %s", _("Reading"), data_file);
    addto_log("▪ ");
    write_log(template);    
    fp = prefs_rc_parse(data_file, DEF_RC_FILE);
    realize_preferences(NULL, NULL);
    ranges = prefs.ranges;
    if (!fp) return;
    parse_rest_of_file(fp);
  }
}

/* exit_geg, called when window manager tries to delete window
 */
static void
exit_geg()
{
  if (geg_file) unlink(geg_file);  
  if (pdf_file) unlink(pdf_file);
  if (eps_file) unlink(eps_file);
  if (svg_file) unlink(svg_file);
  gtk_main_quit();
}

/* delete_event, called when window manager tries to delete window
 */
static gint
delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
  static gint done = 0;
  GtkWidget *dialog;

  if (done) return(FALSE);
  dialog = gtk_message_dialog_new (GTK_WINDOW(wi),
			           GTK_DIALOG_DESTROY_WITH_PARENT,
                                   GTK_MESSAGE_QUESTION,
                                   GTK_BUTTONS_YES_NO,
                                   _("Really quit ?"));
  gtk_window_set_title(GTK_WINDOW(dialog), "geg: question");
    
  if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_NO) {
    gtk_widget_destroy (dialog);      
    return(TRUE);
  } else {
    done = 1;
    gtk_widget_destroy (dialog);
    exit_geg();
  }
  return(FALSE);
}

void
reset_variables()
{
  gint i;

  if (func_gc)
    gdk_gc_set_line_attributes(func_gc, 1, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND);	  
  for (i=0; i<26; i++) {
    var[i] = NAN;        /* parameters set to NAN by default */
    if (fun[i]) { free_tree(fun[i]); fun[i] = NULL; }
  }
  var[4] = M_E;          /* e = exp(1) by default */
  var[INTSTEP] = 0.005;  /* default relative step for integration */    
  var[RINCR] = 0.001;    /* default relative step for derivative */  
  var[RTGTR] = 100;      /* relative size of right tangent segments */
  var[RTGTL] = 100;      /* relative size of left tangent segments */
  var[COLOR] = -1;       /* RGB color 0..255|0..255|0..255 concatenated */
  var[GAP] = 1.0;        /* default gap */
  var[LINEWIDTH] = -1;   /* linewidth (< 0 : inactive) */
  var[POINTRADIUS] = -1; /* point_radius (< 0 : inactive) */  
}

/* app, creates the main interface, called once by main
 */
int
app(void)
{
  GtkWidget *main_vb, *work_hb, *hs, *entry_hb;
  GtkWidget *mb, *file_me, *edit_me, *param_me, *help_me;
  GtkWidget *temp_mi;
  GtkWidget *info_vb, *range_vb;
  GtkWidget *status_la;
  GtkWidget *temp_bu;
  GtkWidget *range_fr, *log_fr;
  gint i;

  for (i = 0; i < 26; i++) {
    fun[i] = NULL;
    integr[i].ptr = NULL;
  }
  reset_variables();
  
  /* Create temporary files */
  template = malloc(1024);
  geg_file = malloc(256);    
  eps_file = malloc(256);
  pdf_file = malloc(256);
  svg_file = malloc(256);    
  strcpy(template, "/tmp/gegXXXXXX");
  if ((i=mkstemp(template))) {
    close(i);
    unlink(template);      
  }
  sprintf(geg_file, "%s.geg", template);    
  sprintf(eps_file, "%s.eps", template);
  sprintf(pdf_file, "%s.pdf", template);
  sprintf(svg_file, "%s.svg", template);
  
  /* create the main window */
  wi = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  
  gtk_window_set_wmclass(GTK_WINDOW(wi), "geg", "Geg");
  gtk_window_set_title(GTK_WINDOW(wi), "geg");
  gtk_window_set_policy(GTK_WINDOW(wi), TRUE, TRUE, TRUE);
  gtk_signal_connect(GTK_OBJECT(wi), "delete_event",
                     GTK_SIGNAL_FUNC(delete_event), NULL);
  gtk_signal_connect(GTK_OBJECT(wi), "destroy",
                     GTK_SIGNAL_FUNC(delete_event), NULL);

  main_vb = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(wi), main_vb);
  gtk_widget_show(main_vb);

  mb = gtk_menu_bar_new();
  gtk_box_pack_start(GTK_BOX(main_vb), mb, FALSE, TRUE, 0);
  gtk_widget_show(mb);

/*  tb = gtk_toolbar_new(GTK_ORIENTATION_HORIZONTAL, GTK_TOOLBAR_BOTH); */
  tb = gtk_toolbar_new();
  gtk_box_pack_start(GTK_BOX(main_vb), tb, FALSE, TRUE, 1);

  work_hb = gtk_hbox_new(FALSE, 0);
  gtk_box_pack_start(GTK_BOX(main_vb), work_hb, TRUE, TRUE, 0);
  gtk_widget_show(work_hb);

  hs = gtk_hseparator_new();
  gtk_box_pack_start(GTK_BOX(main_vb), hs, FALSE, TRUE, 0);
  gtk_widget_show(hs);

  entry_hb = gtk_hbox_new(FALSE, 0);
  gtk_box_pack_start(GTK_BOX(main_vb), entry_hb, FALSE, FALSE, 0);
  gtk_widget_show(entry_hb);

  /* file menu */
  file_me = gtk_menu_new();

  temp_mi = gtk_menu_item_new_with_label(_("Open *.geg"));
  gtk_menu_append(GTK_MENU(file_me), temp_mi);
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
                     GTK_SIGNAL_FUNC(geg_file_open), NULL);
  gtk_widget_show(temp_mi);

  temp_mi = gtk_menu_item_new_with_label(_("Save as *.geg file ..."));
  gtk_menu_append(GTK_MENU(file_me), temp_mi);
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
                     GTK_SIGNAL_FUNC(file_save), "geg");
  gtk_widget_show(temp_mi);

  temp_mi = gtk_menu_item_new_with_label(_("Save as *.eps file ..."));
  gtk_menu_append(GTK_MENU(file_me), temp_mi);
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
                     GTK_SIGNAL_FUNC(file_save), "eps");
  gtk_widget_show(temp_mi);

  temp_mi = gtk_menu_item_new_with_label(_("Save as *.pdf file ..."));
  gtk_menu_append(GTK_MENU(file_me), temp_mi);
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
                     GTK_SIGNAL_FUNC(file_save), "pdf");
  gtk_widget_show(temp_mi);

  temp_mi = gtk_menu_item_new_with_label(_("Save as *.svg file ..."));
  gtk_menu_append(GTK_MENU(file_me), temp_mi);
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
                     GTK_SIGNAL_FUNC(file_save), "svg");
  gtk_widget_show(temp_mi);  
  
  temp_mi = gtk_menu_item_new_with_label(_("Exit"));
  gtk_menu_append(GTK_MENU(file_me), temp_mi);
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
                     GTK_SIGNAL_FUNC(delete_event), NULL);
  gtk_widget_show(temp_mi);

  temp_mi = gtk_menu_item_new_with_label(_("File"));
  gtk_menu_item_set_submenu(GTK_MENU_ITEM(temp_mi), file_me);
  gtk_menu_bar_append(GTK_MENU_BAR(mb), temp_mi);
  gtk_widget_show(temp_mi);
  
  /* edit menu */
  edit_me = gtk_menu_new();

  temp_mi = gtk_menu_item_new_with_label(_("Erase some Formulas..."));
  gtk_menu_append(GTK_MENU(edit_me), temp_mi);
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
                     GTK_SIGNAL_FUNC(erase_event), 
		     GTK_SIGNAL_FUNC(refresh_graph));
  gtk_widget_show(temp_mi);

  temp_mi = gtk_menu_item_new_with_label(_("Edit formulas ..."));
  gtk_menu_append(GTK_MENU(edit_me), temp_mi);
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
			  GTK_SIGNAL_FUNC(edit_formulas_event),
                          NULL);
  gtk_widget_show(temp_mi);

  temp_mi = gtk_menu_item_new_with_label(_("Edit geg file ..."));
  gtk_menu_append(GTK_MENU(edit_me), temp_mi);
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
			  GTK_SIGNAL_FUNC(edit_geg_event),
                          NULL);
  gtk_widget_show(temp_mi);
  
  temp_mi = gtk_menu_item_new_with_label(_("Edit postscript file ..."));
  gtk_menu_append(GTK_MENU(edit_me), temp_mi);
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
			  GTK_SIGNAL_FUNC(edit_eps_event),
                          NULL);
  gtk_widget_show(temp_mi);

  temp_mi = gtk_menu_item_new_with_label(_("Clear Log"));
  gtk_menu_append(GTK_MENU(edit_me), temp_mi);
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
                     GTK_SIGNAL_FUNC(clear_log), NULL);
  gtk_widget_show(temp_mi);
    
  temp_mi = gtk_menu_item_new_with_label(_("Edition"));
  gtk_menu_item_set_submenu(GTK_MENU_ITEM(temp_mi), edit_me);
  gtk_menu_bar_append(GTK_MENU_BAR(mb), temp_mi);
  gtk_widget_show(temp_mi);
  
  /* parameters menu */
  param_me = gtk_menu_new();
  
  temp_mi = gtk_menu_item_new_with_label(_("Export parameters to Prefs"));
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
		     GTK_SIGNAL_FUNC(export_parameters), NULL);
  gtk_menu_append(GTK_MENU(param_me), temp_mi);
  gtk_widget_show(temp_mi);

  temp_mi = gtk_menu_item_new_with_label(_("Builtin parameters"));
  gtk_menu_append(GTK_MENU(param_me), temp_mi);
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
		     GTK_SIGNAL_FUNC(reset_preferences), NULL);
  gtk_widget_show(temp_mi);
  
  temp_mi = gtk_menu_item_new_with_label(_("Preferences..."));
  gtk_menu_append(GTK_MENU(param_me), temp_mi);
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
                     GTK_SIGNAL_FUNC(prefs_event),
		     GTK_SIGNAL_FUNC(realize_preferences));
  gtk_widget_show(temp_mi);
    
  temp_mi = gtk_menu_item_new_with_label(_("Parameters"));
  gtk_menu_item_set_submenu(GTK_MENU_ITEM(temp_mi), param_me);
  gtk_menu_bar_append(GTK_MENU_BAR(mb), temp_mi);
  gtk_widget_show(temp_mi);
  
  /* help menu */
  help_me = gtk_menu_new();

  temp_mi = gtk_menu_item_new_with_label(_("Help..."));
  gtk_menu_append(GTK_MENU(help_me), temp_mi);
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
                     GTK_SIGNAL_FUNC(help_event), NULL);
  gtk_widget_show(temp_mi);

  temp_mi = gtk_menu_item_new_with_label(_("Examples..."));
  gtk_menu_append(GTK_MENU(help_me), temp_mi);
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
		     GTK_SIGNAL_FUNC(geg_file_open), (gpointer)2);
  gtk_widget_show(temp_mi);

  temp_mi = gtk_menu_item_new_with_label(_("About..."));
  gtk_menu_append(GTK_MENU(help_me), temp_mi);
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
                                GTK_SIGNAL_FUNC(about_event), NULL);
  gtk_widget_show(temp_mi);

  temp_mi = gtk_menu_item_new_with_label(_("Help"));
  gtk_menu_item_set_submenu(GTK_MENU_ITEM(temp_mi), help_me);
  gtk_menu_bar_append(GTK_MENU_BAR(mb), temp_mi);
  gtk_menu_item_right_justify(GTK_MENU_ITEM(temp_mi));
  gtk_widget_show(temp_mi);

  /* work area */
  info_vb = gtk_vbox_new(FALSE, 0);
  gtk_box_pack_start(GTK_BOX(work_hb), info_vb, FALSE, FALSE, 0);

  /* xmin, xmax, ymin, ymax */
  range_fr = gtk_frame_new(_("Ranges"));
  gtk_box_pack_start(GTK_BOX(info_vb), range_fr, FALSE, FALSE, 0);

  range_vb = gtk_vbox_new(TRUE, 0);
  gtk_container_add(GTK_CONTAINER(range_fr), range_vb);

  compound_entry(_("Xmin:"), &xmin_en, range_vb);
  compound_entry(_("Xmax:"), &xmax_en, range_vb);
  compound_entry(_("Ymin:"), &ymin_en, range_vb);
  compound_entry(_("Ymax:"), &ymax_en, range_vb);
  compound_entry(_("Tmin:"), &tmin_en, range_vb);
  compound_entry(_("Tmax:"), &tmax_en, range_vb);  

  ranges = prefs.ranges;
  entry_signals("connect");

  update_ranges();

  gtk_signal_connect(GTK_OBJECT(xmin_en), "activate", 
                     GTK_SIGNAL_FUNC(refresh_graph), NULL);
  gtk_signal_connect(GTK_OBJECT(xmax_en), "activate", 
                     GTK_SIGNAL_FUNC(refresh_graph), NULL);
  gtk_signal_connect(GTK_OBJECT(ymin_en), "activate", 
                     GTK_SIGNAL_FUNC(refresh_graph), NULL);
  gtk_signal_connect(GTK_OBJECT(ymax_en), "activate",
                     GTK_SIGNAL_FUNC(refresh_graph), NULL);
  gtk_signal_connect(GTK_OBJECT(tmin_en), "activate", 
                     GTK_SIGNAL_FUNC(refresh_graph), NULL);
  gtk_signal_connect(GTK_OBJECT(tmax_en), "activate", 
                     GTK_SIGNAL_FUNC(refresh_graph), NULL);  

  gtk_widget_show(range_vb);
  gtk_widget_show(range_fr);

  /* log */
  log_fr = gtk_frame_new(_("Log"));
  gtk_box_pack_start(GTK_BOX(info_vb), log_fr, TRUE, TRUE, 0);
  gtk_widget_show(log_fr);

  create_log(log_fr);

  /* status */
  log_fr = gtk_frame_new(_("Status"));
  gtk_box_pack_start(GTK_BOX(info_vb), log_fr, FALSE, FALSE, 0);
  gtk_widget_show(log_fr);

  status_la = gtk_label_new("");
  gtk_container_add(GTK_CONTAINER(log_fr), status_la);
  gtk_widget_show(status_la);

  gtk_widget_show(info_vb);

  parse_data_file();  
  
  /* graph */
  graph_fr = gtk_frame_new("(x,y)");
  gtk_box_pack_start(GTK_BOX(work_hb), graph_fr, TRUE, TRUE, 0);
  gtk_widget_show(graph_fr);

  func_la = gtk_label_new("f(x)");
  gtk_box_pack_start(GTK_BOX(entry_hb), func_la, FALSE, FALSE, 5);
  gtk_widget_show(func_la);

  eq_co = gtk_combo_new();
  gtk_combo_disable_activate(GTK_COMBO(eq_co));
  gtk_signal_connect(GTK_OBJECT(GTK_COMBO(eq_co)->entry), "activate",
		     GTK_SIGNAL_FUNC(go_event), eq_co);
  gtk_box_pack_start(GTK_BOX(entry_hb), eq_co, TRUE, TRUE, 0);
  gtk_widget_show(eq_co);

  temp_bu = gtk_button_new_with_label(_("GO!"));
  gtk_signal_connect(GTK_OBJECT(temp_bu), "clicked",
                     GTK_SIGNAL_FUNC(go_event), eq_co);
  gtk_box_pack_start(GTK_BOX(entry_hb), temp_bu, FALSE, TRUE, 0);
  gtk_widget_show(temp_bu);
  
  da = gtk_drawing_area_new();
  gtk_drawing_area_size(GTK_DRAWING_AREA(da), prefs.ranges.width, prefs.ranges.height);
  gtk_widget_set_events(da, GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | 
                            GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK |
			    GDK_BUTTON_RELEASE_MASK );
  gtk_signal_connect(GTK_OBJECT(da), "motion_notify_event", 
                     GTK_SIGNAL_FUNC(motion_event), status_la);
  gtk_signal_connect(GTK_OBJECT(da), "leave_notify_event", 
                     GTK_SIGNAL_FUNC(leave_event), status_la);
  gtk_signal_connect(GTK_OBJECT(da), "button_press_event",
                     GTK_SIGNAL_FUNC(press_event), NULL);
  gtk_signal_connect(GTK_OBJECT(da), "configure_event",
                     GTK_SIGNAL_FUNC(drawing_init), NULL);
  gtk_signal_connect(GTK_OBJECT(da), "configure_event",
                     GTK_SIGNAL_FUNC(configure_event), NULL);
  gtk_signal_connect(GTK_OBJECT(da), "configure_event",
                     GTK_SIGNAL_FUNC(update_functions), NULL);
  gtk_signal_connect(GTK_OBJECT(da), "expose_event", 
                     GTK_SIGNAL_FUNC(expose_event), NULL);
  gtk_signal_connect_after(GTK_OBJECT(da), "button_release_event",
                     GTK_SIGNAL_FUNC(refresh_graph), NULL);
  gtk_container_add(GTK_CONTAINER(graph_fr), da);
  gtk_widget_show(da);
  
  /* we need to realize the window so we can create the pixmaps */
  gtk_widget_realize(wi);
  colormap = gdk_window_get_colormap(wi->window);
  g_assert(colormap != NULL);

  /* toolbar buttons */

  gtk_toolbar_append_item(GTK_TOOLBAR(tb), _("Restart"), 
                          _("Erase all formulas and reset zoom"), NULL,
			  create_pixmap(wi, restart_xpm), 
			  GTK_SIGNAL_FUNC(restart_event), NULL);

  gtk_toolbar_append_item(GTK_TOOLBAR(tb), _("Edit"), _("Edit formulas"),
                          NULL, create_pixmap(wi, edit_xpm),
			  GTK_SIGNAL_FUNC(edit_formulas_event),
                          NULL);
  
#if 0  
  gtk_toolbar_append_item(GTK_TOOLBAR(tb), _("Erase"),
                          _("Select formulas to erase"), NULL,
			  create_pixmap(wi, erase_xpm),
			  GTK_SIGNAL_FUNC(erase_event),
			  GTK_SIGNAL_FUNC(refresh_graph));
#endif
  
  gtk_toolbar_append_space(GTK_TOOLBAR(tb));

  gtk_toolbar_append_item(GTK_TOOLBAR(tb), _("↕x,y↕"),
			  _("Zoom along Ox, Oy, or both"), NULL, 
                          create_pixmap(wi, xy_xpm),
			  GTK_SIGNAL_FUNC(zoom_event), "xy");
  
  gtk_toolbar_append_item(GTK_TOOLBAR(tb), _("Reset"), _("Reset zoom to default"),
                          NULL, create_pixmap(wi, zoom0_xpm),
			  GTK_SIGNAL_FUNC(zoom_event), "0");
  
  gtk_toolbar_append_item(GTK_TOOLBAR(tb), _("In"), _("Zoom in"), NULL, 
                          create_pixmap(wi, in_xpm),
			  GTK_SIGNAL_FUNC(zoom_event), "in");
  
  gtk_toolbar_append_item(GTK_TOOLBAR(tb), _("Out"), _("Zoom out"), NULL, 
                          create_pixmap(wi, out_xpm),
			  GTK_SIGNAL_FUNC(zoom_event), "out");
  
  gtk_toolbar_append_space(GTK_TOOLBAR(tb));

  gtk_toolbar_append_item(GTK_TOOLBAR(tb), _("t→(x,y) ?"),
			  _("Use cartesian / polar / parametric mode"),
			  NULL, create_pixmap(wi, sine_xpm),
			  GTK_SIGNAL_FUNC(rotate_mode), NULL);

  gtk_toolbar_append_item(GTK_TOOLBAR(tb), _("u(n) ?"),
			  _("Draw graphic representation of sequence"),
			  NULL, create_pixmap(wi, sequence_xpm),
			  GTK_SIGNAL_FUNC(sequence_mode), NULL);
  
  gtk_toolbar_append_item(GTK_TOOLBAR(tb), _("1 : π"), 
			  _("Use decimal / radian  x coordinates"), NULL,
			  create_pixmap(wi, pi_xpm),
			  GTK_SIGNAL_FUNC(set_coordinates), NULL);

  gtk_toolbar_append_item(GTK_TOOLBAR(tb), _("grid"), 
			  _("Draw coordinate grid"), NULL,
			  create_pixmap(wi, grid_xpm),
			  GTK_SIGNAL_FUNC(draw_grid), NULL);
  
  gtk_toolbar_append_space(GTK_TOOLBAR(tb));

  gtk_toolbar_append_item(GTK_TOOLBAR(tb), _("Print"), _("Print graph window"),
                          NULL, create_pixmap(wi, print_xpm), 
			  GTK_SIGNAL_FUNC(print_event),
                          (gpointer)&prefs.print);
  
  gtk_toolbar_append_item(GTK_TOOLBAR(tb), _("Prefs"), _("Edit preferences"),
                          NULL, create_pixmap(wi, prefs_xpm), 
			  GTK_SIGNAL_FUNC(prefs_event),
                          GTK_SIGNAL_FUNC(realize_preferences));
  
  gtk_toolbar_append_item(GTK_TOOLBAR(tb), _("Exit"), _("Exit geg"),
			  NULL, create_pixmap(wi, exit_xpm),
			  GTK_SIGNAL_FUNC(delete_event), NULL);


  /* time to realize the preferences */
  if (!data_file) realize_preferences(wi, "firstcall");

  gtk_widget_show(tb);

  gtk_widget_show(wi);

  gtk_widget_grab_focus(GTK_COMBO(eq_co)->entry);

  configure_event(da, NULL, NULL);
  set_graphic_frame_title();
  
  if (data_file) {
    refresh_graph(NULL, NULL);
    g_free(data_file);
    data_file = NULL;
  }

  return(0);
}

/* create_pixmap, convenience function to create a pixmap widget, from data
 */
static GtkWidget *
create_pixmap(GtkWidget *widget, gchar **data)
{
  GtkStyle *style;
  GdkBitmap *mask;
  GdkPixmap *gdk_pixmap;
  GtkWidget *gtk_pixmap;
  
  style = gtk_widget_get_style(widget);
  g_assert(style != NULL);

  gdk_pixmap = gdk_pixmap_create_from_xpm_d(widget->window, 
                                            &mask, &style->bg[GTK_STATE_NORMAL],
					    data);
  g_assert(gdk_pixmap != NULL);
  gtk_pixmap = gtk_pixmap_new(gdk_pixmap, mask);
  g_assert(gtk_pixmap != NULL);
  gtk_widget_show(gtk_pixmap);

  return(gtk_pixmap);
}

/* realize preferences, make the non-startup preferences take effect       
 * the startup preferences are handled elsewhere
 */
void
realize_preferences(GtkWidget *widget, gpointer data)
{
  guint i;

  /* toolbar */
  switch(prefs.tb) {
  case(GEG_RC_PICTURES_AND_TEXT):
    gtk_toolbar_set_style(GTK_TOOLBAR(tb), GTK_TOOLBAR_BOTH);
    break;
  case(GEG_RC_PICTURES_ONLY):
    gtk_toolbar_set_style(GTK_TOOLBAR(tb), GTK_TOOLBAR_ICONS);
    break;
  case(GEG_RC_TEXT_ONLY):
    gtk_toolbar_set_style(GTK_TOOLBAR(tb), GTK_TOOLBAR_TEXT);
    break;
  default:
    break;
  }

  /* tooltips */
  if(prefs.tt == GEG_RC_ON)
    gtk_toolbar_set_tooltips(GTK_TOOLBAR(tb), TRUE);
  else if(prefs.tt == GEG_RC_OFF)
    gtk_toolbar_set_tooltips(GTK_TOOLBAR(tb), FALSE);
  else
    g_assert_not_reached();

  if(ranges.mode == DEF_CARTESIAN) {
    gtk_label_set_text(GTK_LABEL(func_la), "f(x)");    
  } else
  if(ranges.mode == DEF_POLAR) {
    gtk_label_set_text(GTK_LABEL(func_la), "r(t)");  
  } else
  if(ranges.mode == DEF_PARAMETRIC) {
    gtk_label_set_text(GTK_LABEL(func_la), "x= ;y= ");    
  } else
  if(ranges.mode == DEF_SEQUENCE) {
    gtk_label_set_text(GTK_LABEL(func_la), "u(n)");    
  }

  if (data_file) {
    gtk_entry_set_text(GTK_ENTRY(xmin_en), ftoa(ranges.xmin));
    gtk_entry_set_text(GTK_ENTRY(xmax_en), ftoa(ranges.xmax));  
    gtk_entry_set_text(GTK_ENTRY(ymin_en), ftoa(ranges.ymin));
    gtk_entry_set_text(GTK_ENTRY(ymax_en), ftoa(ranges.ymax));
    if (ranges.mode == DEF_POLAR) {
      gtk_entry_set_text(GTK_ENTRY(tmin_en), ftoa(ranges.amin));
      gtk_entry_set_text(GTK_ENTRY(tmax_en), ftoa(ranges.amax));      
    }
    if (ranges.mode == DEF_PARAMETRIC) {
      gtk_entry_set_text(GTK_ENTRY(tmin_en), ftoa(ranges.tmin));
      gtk_entry_set_text(GTK_ENTRY(tmax_en), ftoa(ranges.tmax));      
    }    
  }

  if (!widget) return;
  
  /* colors */
  
  for (i=0; i<prefs.n_colors; i++)
    alloc_color(&prefs.gdk_color[i], &prefs.rgb_value[3*i], colormap);

  reset_size_toplevel();
  set_graphic_frame_title();
  if(!data)	/* check for firstcall ? */
    refresh_graph(NULL, NULL);
}

/* compound_entry, convenience function that creates a label and an entry
 * in a hbox, used by app() to create the range labels/entries    
 */
static void
compound_entry(gchar *title, GtkWidget **entry, GtkWidget *vbox)
{
  GtkWidget *temp_hb, *temp_la;

  temp_hb = gtk_hbox_new(FALSE, 0);

  *entry = gtk_entry_new();
  /* just make the width greater than the default */
  gtk_widget_set_usize(*entry, 96, -1);
  gtk_box_pack_end(GTK_BOX(temp_hb), *entry, FALSE, FALSE, 0);
  gtk_widget_show(*entry);

  temp_la = gtk_label_new(title);
  gtk_box_pack_end(GTK_BOX(temp_hb), temp_la, FALSE, FALSE, 30);
  gtk_widget_show(temp_la);

  gtk_widget_show(temp_hb);

  gtk_box_pack_start(GTK_BOX(vbox), temp_hb, TRUE, TRUE, 0);
}

/* update_ranges, change this from using ftoa
 */
static void
update_ranges(void)
{
  gdouble width  = ranges.xmax - ranges.xmin;
  gdouble height = ranges.ymax - ranges.ymin;
  GString *buf_1 = g_string_new(NULL);
  GString *buf_2 = g_string_new(NULL);

  entry_signals("disconnect");
  
  g_string_sprintf(buf_1, "%%0.%df", 
		   CLAMP((gint)ceil(-log10(width / 100)), 0, 6));

  g_string_sprintf(buf_2, buf_1->str, ranges.xmin);
  gtk_entry_set_text(GTK_ENTRY(xmin_en), buf_2->str);
  /* gtk_signal_emit_stop_by_name(GTK_OBJECT(xmin_en), "changed"); */

  g_string_sprintf(buf_2, buf_1->str, ranges.xmax);
  gtk_entry_set_text(GTK_ENTRY(xmax_en), buf_2->str);
  /* gtk_signal_emit_stop_by_name(GTK_OBJECT(xmax_en), "changed"); */

  g_string_sprintf(buf_1, "%%0.%df",
		   CLAMP((gint)ceil(-log10(height / 100)), 0, 6));

  g_string_sprintf(buf_2, buf_1->str, ranges.ymin);
  gtk_entry_set_text(GTK_ENTRY(ymin_en), buf_2->str);
  /* gtk_signal_emit_stop_by_name(GTK_OBJECT(ymin_en), "changed"); */

  g_string_sprintf(buf_2, buf_1->str, ranges.ymax);
  gtk_entry_set_text(GTK_ENTRY(ymax_en), buf_2->str);
  /* gtk_signal_emit_stop_by_name(GTK_OBJECT(ymax_en), "changed"); */

  g_string_free(buf_1, TRUE);
  g_string_free(buf_2, TRUE);

  entry_signals("connect");
}

/* zoom_event, called when either of zoom_in or zoom_out are clicked from
 * the toolbar. "in" and "out" are passed as data
 */
static void
zoom_event(GtkWidget *widget, gpointer data)
{
  gdouble x = 0, y = 0;
  gdouble width, height;
  gchar buf[64];
  
  if(!range_ok(FALSE))
   return;

  if(!strcmp((char *)data, "0")) {
    ranges.xmin = prefs.ranges.xmin;
    ranges.ymin = prefs.ranges.ymin;
    ranges.xmax = prefs.ranges.xmax;
    ranges.ymax = prefs.ranges.ymax;
    refresh_graph(NULL, NULL);
    addto_log("▪ ");    
    write_log(_("Zoom reset"));
    return;
  }
  
  if(!strcmp((char *)data, "xy")) {
    prefs.zoom_xy = (prefs.zoom_xy % 4) + 1;
    if (prefs.zoom_xy == 1) strcpy(buf, _("Zoom along Ox"));
    if (prefs.zoom_xy == 2) strcpy(buf, _("Zoom along Oy"));
    if (prefs.zoom_xy == 3) strcpy(buf, _("Zoom along Ox and Oy"));
    if (prefs.zoom_xy == 4) strcpy(buf, _("Zoom orthonormal"));
    addto_log("▪ ");    
    write_log(buf);
    set_graphic_frame_title();
    return;
  }

  width = ranges.xmax - ranges.xmin;
  height = ranges.ymax - ranges.ymin;

  if(!strcmp((char *)data, "in")) {
    if((width < prefs.minres) ||
       (height < prefs.minres)) {
      write_log(_("Minimum Resolution"));
      return;
    }
    if (prefs.zoom_xy & 1)
      x = - prefs.zoom / 2 * (width / (1 + prefs.zoom));
    if (prefs.zoom_xy & 2)    
      y = - prefs.zoom / 2 * (height / (1 + prefs.zoom));
    if (prefs.zoom_xy == 4) {
      x = - prefs.zoom / 2 * (width / (1 + prefs.zoom));
    }
    addto_log("▪ ");    
    write_log(_("Zoom In"));
  }
  else if(!strcmp((char *)data, "out")) {
    if((width > prefs.maxres) ||
       (height > prefs.maxres)) {
      write_log(_("Maximum Resolution"));
      return;
    }
    if (prefs.zoom_xy & 1)    
      x = prefs.zoom / 2 * width;
    if (prefs.zoom_xy & 2)    
      y = prefs.zoom / 2 * height;
    if (prefs.zoom_xy == 4) {
      x = prefs.zoom / 2 * width;
    }
    addto_log("▪ ");    
    write_log(_("Zoom Out"));
  }
  else
    g_assert_not_reached();

  ranges.xmin -= x;
  ranges.xmax += x;
  if (prefs.zoom_xy == 4) {
    ranges.ymin = (ranges.ymin + ranges.ymax)/2.0;
    y = 0.5 * (ranges.xmax - ranges.xmin) * ((gdouble)ranges.height / (gdouble)ranges.width);
    ranges.ymax = ranges.ymin + y; 
    ranges.ymin = ranges.ymin - y;
  } else {
    ranges.ymin -= y;
    ranges.ymax += y;
  }

  mute = 1;
  update_ranges();
  clear_pixmap();
  draw_axes();
  reset_variables();
  formula_foreach((FormulaFunc)process_formula);
  redraw_event(da, NULL);
  mute = 0;
}

/* go_event, called when the go button is clicked, draw the formula
 */
static void
go_event(GtkWidget *widget, gpointer data)
{
  GtkWidget *combo;
  gchar *formula;

  combo = (GtkWidget *)data;
  formula = (gchar *)gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(combo)->entry));

  if (formula_find(formula)) return;
  
  /* draw the formula and add it to the list if it parses ok */
  if(!formula_add((FormulaFunc)process_formula, formula))
    return;

  redraw_event(da, NULL);
}

/* resize size of toplevel window */

static void
reset_size_toplevel()
{
  gint u, v;
  GdkWindow *window;
  window = gtk_widget_get_window(wi);
  u = gdk_window_get_width(window) + prefs.ranges.width - ranges.width;
  v = gdk_window_get_height(window) + prefs.ranges.height - ranges.height;
  gdk_window_resize(window, u, v);
  ranges = prefs.ranges;
}

/* open a geg file
 */
static gchar *
file_select(gchar *mode)
{
  gint num_of_files = 0, result;	
  GtkWidget *dialog;
  GSList *filenames;
  gchar *file_selected = NULL;

  if (mode == NULL)
    strcpy(template, _("Open *.geg file ..."));
  else
  if (!strcmp(mode, "exa")) {
    file_selected = mode;
    mode = NULL;
  }
  else
  if (!strcmp(mode, "geg"))    
    sprintf(template, "%s *.%s ...", _("Save as"), mode);
  
  dialog = gtk_file_chooser_dialog_new (template, NULL,
					(mode)? GTK_FILE_CHOOSER_ACTION_SAVE:
					GTK_FILE_CHOOSER_ACTION_OPEN,
				        GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
					GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
					NULL);

  if (file_selected) {
    gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog),
					 "/usr/share/geg/examples");
    file_selected = NULL;
  }
  
  /* Allow the user to choose more than one file at a time. */

  gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dialog), FALSE);

  result = gtk_dialog_run (GTK_DIALOG (dialog));
		
  if (result == GTK_RESPONSE_ACCEPT) {
    filenames = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER (dialog));
	
    while (filenames != NULL) {
      num_of_files++;
      if (file_selected) g_free(file_selected);
      file_selected = g_strdup(filenames->data);
      filenames = filenames->next;
    }
  }
  gtk_widget_destroy (dialog);
  return file_selected;
}

static void
geg_file_open(GtkWidget *widget, gpointer data)
{
  FILE *fp;
  gchar *file_selected = "";

  if (!data) file_selected = file_select(NULL);
  else
  if (data == (gpointer)1) file_selected = geg_file;
  else
  if (data == (gpointer)2) file_selected = file_select("exa");
  
  fp = prefs_rc_parse(file_selected, DEF_RC_FILE);
  if (!fp) return;
  restart_event(widget, NULL);
  sprintf(template, "%s %s", _("Loading"), file_selected);
  addto_log("▪ ");
  write_log(template);
  parse_rest_of_file(fp);
  alloc_func_colors(colormap);  
  if (!data) g_free(file_selected);
  reset_size_toplevel();
  configure_event(da, NULL, NULL);    
  refresh_graph(NULL, NULL);
}

static void
geg_file_save(gchar *file_selected)
{
  FILE *fp;
  gchar **formulas = formula_list();
  struct_ranges save_ranges;
  guint i;
  save_ranges = prefs.ranges;
  prefs.ranges = ranges;
  fp = prefs_rc_write(file_selected, DEF_RC_FILE);
  prefs.ranges = save_ranges;
  if (!fp) return;
  fprintf(fp, "\n# Formulas\n");
  for (i = 0; i < formula_count(); i++) {
    if (strcmp(formulas[i], "##"))
      fprintf(fp, "%s\n", formulas[i]);
    else
      fprintf(fp, "\n");      
  }
  fclose(fp);
  sprintf(template, "%s %s", file_selected, _("saved"));  
  addto_log("▪ ");
  write_log(template);
}

/* save a geg file
 */
static void
file_save(GtkWidget *widget, gpointer data)
{
  GtkWidget *dialog;
  struct stat buf;
  gchar *file_selected, *name = NULL;
  gint mode;

  file_selected = file_select((gchar *)data);
  if (!file_selected) return;

  mode = stat(file_selected, &buf);
  if ((mode == 0) && S_ISREG(buf.st_mode)) {
    dialog = gtk_message_dialog_new (GTK_WINDOW(wi),
				     GTK_DIALOG_DESTROY_WITH_PARENT,
                                     GTK_MESSAGE_QUESTION,
                                     GTK_BUTTONS_YES_NO,
                                     _("Overwrite file '%s' ?"),
                                     file_selected);
    gtk_window_set_title(GTK_WINDOW(dialog), "geg: question");
    
    if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_YES) {
      gtk_widget_destroy (dialog);      
      unlink(file_selected);      
    } else {
      gtk_widget_destroy (dialog);
      return;    
    }
  }
  
  if (!strcmp((gchar *)data, "geg")) mode = -1;
  else
  if (!strcmp((gchar *)data, "eps")) { mode = GEG_RC_EPS; name = eps_file; }
  else
  if (!strcmp((gchar *)data, "pdf")) { mode = GEG_RC_PDF; name = pdf_file; }
  else
  if (!strcmp((gchar *)data, "svg")) { mode = GEG_RC_SVG; name = svg_file; }
  
  if (mode < 0) {
    geg_file_save(file_selected);
    return;
  }
  process_output(mode);
  if (name) {
    sprintf(template, "mv %s \"%s\"", name, file_selected);
    if (system(template));
  }
  /* restart_event(widget, NULL); */
  addto_log("▪ ");
  addto_log(_("Writing"));
  addto_log(" ");  
  write_log(file_selected);
  g_free(file_selected);
}

/* refresh_graph
 */
static void
refresh_graph(GtkWidget *widget, gpointer data)
{
  if(!range_ok(FALSE))
    return;
  mute = 1;
  update_ranges();
  clear_pixmap();
  draw_axes();
  reset_variables();
  formula_foreach((FormulaFunc)process_formula);
  redraw_event(da, NULL);	/* expose entire area */
  mute = 0;
}

/* update_functions,
 */
static void
update_functions(GtkWidget *widget, gpointer data)
{
  reset_variables();
  formula_foreach((FormulaFunc)process_formula);
}

static void
entry_changed_event(GtkWidget *widget, gpointer data)
{
  if(!strcmp("xmin", data))
    ranges.xmin = atof(gtk_entry_get_text(GTK_ENTRY(xmin_en)));
  else if(!strcmp("xmax", data))
    ranges.xmax = atof(gtk_entry_get_text(GTK_ENTRY(xmax_en)));
  else if(!strcmp("ymin", data))
    ranges.ymin = atof(gtk_entry_get_text(GTK_ENTRY(ymin_en)));
  else if(!strcmp("ymax", data))
    ranges.ymax = atof(gtk_entry_get_text(GTK_ENTRY(ymax_en)));
  else if(!strcmp("tmin", data)) {
    if (ranges.mode == DEF_POLAR)
      ranges.amin = atof(gtk_entry_get_text(GTK_ENTRY(tmin_en)));
    else
    if (ranges.mode == DEF_PARAMETRIC)
      ranges.tmin = atof(gtk_entry_get_text(GTK_ENTRY(tmin_en)));    
  } else if(!strcmp("tmax", data)) {
    if (ranges.mode == DEF_POLAR)
      ranges.amax = atof(gtk_entry_get_text(GTK_ENTRY(tmax_en)));
    else
    if (ranges.mode == DEF_PARAMETRIC)    
      ranges.tmax = atof(gtk_entry_get_text(GTK_ENTRY(tmax_en)));
  }
}

/* restart_event, resets zoom, erases all functions
 */
static void
restart_event(GtkWidget *widget, gpointer data)
{
  GtkWidget *entry; 
  reset_variables();
  ranges = prefs.ranges;
  prefs.zoom_xy = 3;
  set_graphic_frame_title();
  remove_all_event(NULL, NULL);
  refresh_graph(NULL, NULL);
  clear_log(NULL, NULL);
  addto_log("▪ ");
  write_log(_("Restart"));
  entry = GTK_COMBO(eq_co)->entry;
  gtk_entry_set_text(GTK_ENTRY(entry), "");	/* do we want this ? */
  gtk_widget_grab_focus(entry);
}

/* reset_preferences
 */
static void
export_parameters(GtkWidget *widget, gpointer data)
{
  addto_log("▪ ");
  write_log(_("Exporting main window"));
  write_log(_("  parameters to preferences"));
  prefs.ranges = ranges;
  if (get_prefs_widget()) prefs_to_widgets();
}

/* reset_preferences
 */

static void
reset_preferences(GtkWidget *widget, gpointer data)
{
  gint i;
  free_prefs_allocs();
  initialize_prefs();
  for (i=0; i<prefs.n_colors; i++)
    alloc_color(&prefs.gdk_color[i], &prefs.rgb_value[3*i], colormap);  
  prefs.zoom_xy = 3;
  set_graphic_frame_title();
  realize_preferences(widget, data);  
  gtk_entry_set_text(GTK_ENTRY(tmin_en), "");
  gtk_entry_set_text(GTK_ENTRY(tmax_en), "");
  addto_log("▪ ");
  write_log(_("Builtin parameters reset"));
}

/* entry_signals, function that turns off and on the changed callbacks for
 * the range entry areas
 */
static void
entry_signals(gchar *flag)
{
  static guint xmin_handler, xmax_handler, ymin_handler, ymax_handler;
  static guint tmin_handler, tmax_handler;

  if(!strcmp(flag, "connect")) {
    xmin_handler = gtk_signal_connect(GTK_OBJECT(xmin_en), "changed",
                                      GTK_SIGNAL_FUNC(entry_changed_event),
				      "xmin");
    xmax_handler = gtk_signal_connect(GTK_OBJECT(xmax_en), "changed",
                                      GTK_SIGNAL_FUNC(entry_changed_event), 
				      "xmax");
    ymin_handler = gtk_signal_connect(GTK_OBJECT(ymin_en), "changed",
                                      GTK_SIGNAL_FUNC(entry_changed_event),
				      "ymin");
    ymax_handler = gtk_signal_connect(GTK_OBJECT(ymax_en), "changed", 
                                      GTK_SIGNAL_FUNC(entry_changed_event),
				      "ymax");
    tmin_handler = gtk_signal_connect(GTK_OBJECT(tmin_en), "changed", 
                                      GTK_SIGNAL_FUNC(entry_changed_event),
				      "tmin");
    tmax_handler = gtk_signal_connect(GTK_OBJECT(tmax_en), "changed", 
                                      GTK_SIGNAL_FUNC(entry_changed_event),
				      "tmax");
  }
  else if(!strcmp(flag, "disconnect")) {
    gtk_signal_disconnect(GTK_OBJECT(xmin_en),
                          xmin_handler);
    gtk_signal_disconnect(GTK_OBJECT(xmax_en),
                          xmax_handler);
    gtk_signal_disconnect(GTK_OBJECT(ymin_en),
                          ymin_handler);
    gtk_signal_disconnect(GTK_OBJECT(ymax_en),
                          ymax_handler);
    gtk_signal_disconnect(GTK_OBJECT(tmin_en),
                          tmin_handler);
    gtk_signal_disconnect(GTK_OBJECT(tmax_en),
                          tmax_handler);   
  }
  else
    g_assert_not_reached();
}

/* set_coordinates
 */
static void
set_coordinates(GtkWidget *widget, gpointer data)
{
  ranges.radian = 1 - ranges.radian;
  addto_log("▪ ");  
  sprintf(template, _("Showing x as %s"),
	  (ranges.radian)?_("radian"):_("decimal"));
  write_log(template);
  set_graphic_frame_title();
  refresh_graph(NULL, NULL);
}

static void
draw_grid(GtkWidget *widget, gpointer data)
{
  addto_log("▪ ");
  prefs.do_grid = 1 - prefs.do_grid;
  sprintf(template, "%s %s", _("Drawing grid:"), (prefs.do_grid)? _("yes"):_("no"));
  write_log(template);
  refresh_graph(NULL, NULL);
}

void
set_graphic_frame_title()
{
  gchar *head="", *xyzoom="", *picoord="";
  
  if (ranges.mode == DEF_CARTESIAN) head = _("Cartesian");
  if (ranges.mode == DEF_POLAR) head = _("Polar");
  if (ranges.mode == DEF_PARAMETRIC) head = _("Parametric");
  if (ranges.mode == DEF_SEQUENCE) head = _("Sequence");  
  if (prefs.zoom_xy == 1) xyzoom = "↕x,y";
  if (prefs.zoom_xy == 2) xyzoom = "x,y↕";
  if (prefs.zoom_xy == 3) xyzoom = "↕x,y↕";
  if (prefs.zoom_xy == 4) xyzoom = "x ⊾ y";  /* □⊥ */
  if (ranges.radian) picoord = "   π"; else picoord = "";
  sprintf(template, "%s   %s%s", head, xyzoom, picoord);
  gtk_frame_set_label(GTK_FRAME(graph_fr), template);  
}

/* set_minmax_value
 */
void
set_minmax_value(gint i)
{
  if (i == AMIN) {
    ranges.amin = var[AMIN];
    if (ranges.mode == DEF_POLAR)
      gtk_entry_set_text(GTK_ENTRY(tmin_en), ftoa(ranges.amin));
  }
  if (i == AMAX) {
    ranges.amax = var[AMAX];
    if (ranges.mode == DEF_POLAR)
      gtk_entry_set_text(GTK_ENTRY(tmax_en), ftoa(ranges.amax));
  }    
  if (i == TMIN) {
    ranges.tmin = var[TMIN];
    gtk_entry_set_text(GTK_ENTRY(tmin_en), ftoa(ranges.tmin));
  } 
  if (i == TMAX) {
    ranges.tmax = var[TMAX];
    gtk_entry_set_text(GTK_ENTRY(tmax_en), ftoa(ranges.tmax));
  }
}

/* set_mode
 */
void
set_mode(gint i)
{
  if (i <= 3) ranges.mode = i;
  show_mode(0);
}

static void
show_mode(gint i)
{
  gchar buf[32];  
  if (ranges.mode == DEF_CARTESIAN) {
    if (i) {
      addto_log("▪ ");    
      write_log(_("Cartesian mode"));
    }
    gtk_entry_set_text(GTK_ENTRY(tmin_en), "");
    gtk_entry_set_text(GTK_ENTRY(tmax_en), "");
    strcpy(buf, "f(x)");    
  } else
  if (ranges.mode == DEF_POLAR) {
    if (i) {
      addto_log("▪ ");    
      write_log(_("Polar mode"));
    }
    sprintf(buf, "%g", ranges.amin);
    gtk_entry_set_text(GTK_ENTRY(tmin_en), buf);
    sprintf(buf, "%g", ranges.amax);
    gtk_entry_set_text(GTK_ENTRY(tmax_en), buf);
    strcpy(buf, "r(t)");    
  } else
  if (ranges.mode == DEF_PARAMETRIC) {
    if (i) {
      addto_log("▪ ");    
      write_log(_("Parametric mode"));
    }
    sprintf(buf, "%g", ranges.tmin);
    gtk_entry_set_text(GTK_ENTRY(tmin_en), buf);
    sprintf(buf, "%g", ranges.tmax);
    gtk_entry_set_text(GTK_ENTRY(tmax_en), buf);
    strcpy(buf, "x= ;y=");    
  } else
  if (ranges.mode == DEF_SEQUENCE) {
    if (i) {
      addto_log("▪ ");    
      write_log(_("Sequence mode"));
    }
    gtk_entry_set_text(GTK_ENTRY(tmin_en), "0");
    gtk_entry_set_text(GTK_ENTRY(tmax_en), "");
    strcpy(buf, "u(n)");    
  }    
  set_graphic_frame_title();
  gtk_label_set_text(GTK_LABEL(func_la), buf);  
}

/* rotate_mode
 */
static void
rotate_mode(GtkWidget *widget, gpointer data)
{
  if (ranges.mode == DEF_POLAR) {
      ranges.amin = atof(gtk_entry_get_text(GTK_ENTRY(tmin_en)));
      ranges.amax = atof(gtk_entry_get_text(GTK_ENTRY(tmax_en)));
  }
  else
    if (ranges.mode == DEF_PARAMETRIC) {
      ranges.tmin = atof(gtk_entry_get_text(GTK_ENTRY(tmin_en)));
      ranges.tmax = atof(gtk_entry_get_text(GTK_ENTRY(tmax_en)));
  }
  if (ranges.mode == 3)
    ranges.mode = 0;
  else
    ranges.mode = (ranges.mode + 1) % 3;
  show_mode(1);
  if (widget) refresh_graph(NULL, NULL);
}

static void
sequence_mode(GtkWidget *widget, gpointer data)
{
  ranges.mode = 3;
  show_mode(1);
  if (widget) refresh_graph(NULL, NULL);
}

/* parse_command_line, parse the command line options
 */
void
parse_command_line(int argc, char *argv[])
{
  int i;

  for(i = 1; i < argc; i++)
    if(!strcmp("-h", argv[i]) || !strcmp("--help", argv[i])) {
      print_help();
      exit(0);      
    } else
    if(!strcmp("-v", argv[i]) || !strcmp("--version", argv[i])) {
      g_print("geg Version "
#ifdef VERSION
	      VERSION
#else
	      "?.?.?"
#endif
	      " by David Bryant\n");
      exit(0);
    } else {
      if (data_file) g_free(data_file);
      data_file = g_strdup(argv[i]);
    }
}

/* pixmap_x, converts the real x-coordinate to its x-coordinate in the pixmap
 */
gint pixmap_x(gdouble x)
{
  return(rint((x - ranges.xmin) / (ranges.xmax - ranges.xmin) * (gdouble)ranges.width));
}

/* pixmap_y, converts the real y-coordinate to its y-coordinate in the pixmap
 */
gint pixmap_y(gdouble y)
{
  gdouble temp;

  temp = rint((gdouble)ranges.height -
         ((y - ranges.ymin) / (ranges.ymax - ranges.ymin) * (gdouble)ranges.height));

  /* if the value is out of bounds, prevent overflowing */
  if(temp > (ranges.height / 2 + ranges.height))
    return(ranges.height / 2 + ranges.height);

  if(temp < (ranges.height / 2  - ranges.height))
    return(ranges.height / 2 - ranges.height);

  return((gint)temp);
}

/* real_x, converts the pixmap x-coordinate to its real x-coordinate
 */
gdouble real_x(gint x)
{
  return((((gdouble)x / (gdouble)ranges.width) * (ranges.xmax - ranges.xmin)) + ranges.xmin);
}

/* real_y, converts the pixmap y-coordinate to its real y-coordinate
 */
gdouble real_y(gint y)
{
  return((((ranges.height - y) / (gdouble)ranges.height) * 
	  (ranges.ymax - ranges.ymin)) + ranges.ymin);
}

/* draw_axes_event, draws the horizontal and vertical axes onto the pixmap   
 * this function NEEDS a complete REWRITE!!!
 */
void
draw_axes(void)
{
  static GdkFont *numb_font = NULL;
  gdouble x_range, y_range, x_spacing, y_spacing, x, y;
  gint pi_inc, sub_pi;
  gint x_radix, y_radix, x_count;
  gint x_coef, y_coef, y_count;
  gint i, j, d;
  GString *buf_1 = g_string_new(NULL);
  GString *buf_2 = g_string_new(NULL);

  if(!numb_font)
    (void)((numb_font = gdk_font_load(prefs.numb_font)) ||
           (numb_font = gdk_font_load("fixed")));

  g_assert(numb_font != NULL);
  
  if (prefs.do_color) {
    gdk_gc_set_foreground(numb_gc, &prefs.gdk_color[DEF_NUMB_COLOR]);
    gdk_gc_set_foreground(axes_gc, &prefs.gdk_color[DEF_AXES_COLOR]);
    gdk_gc_set_foreground(grid_gc, &prefs.gdk_color[DEF_GRID_COLOR]);
  } else {
    gdk_gc_set_foreground(numb_gc, &prefs.gdk_color[DEF_GRAY_COLOR]);
    gdk_gc_set_foreground(axes_gc, &prefs.gdk_color[DEF_GRAY_COLOR]);
    gdk_gc_set_foreground(grid_gc, &prefs.gdk_color[DEF_GRAY_COLOR]);    
  }

  range_ok(TRUE);

  /* the tricky bit... set up for notches and numbers */

  x_range = (ranges.xmax - ranges.xmin);
  y_range = (ranges.ymax - ranges.ymin);
  
  /* starting radices which we work up from, so wind them back by 2 */
  x_radix = (gint)log10(x_range) - 2;
  y_radix = (gint)log10(y_range) - 2;

  /* decimal values */
  /* the next bit works out whether to use 1,2,5 * 10^x_radix spacing */
  for(;;) {
    if(((1 * pow(10, x_radix)) * ranges.width / x_range) > prefs.space) {
      x_coef = 1;
      break;
    }
    if(((2 * pow(10, x_radix)) * ranges.width / x_range) > prefs.space) {
      x_coef = 2;
      break;
    }
    if(((5 * pow(10, x_radix)) * ranges.width / x_range) > prefs.space) {
      x_coef = 5;
      break;
    }
    x_radix++;
  }

  /* the next bit works out whether to use 1,2,5 * 10^y_radix spacing */
  for(;;) {
    if(((1 * pow(10, y_radix)) * ranges.height / y_range) > prefs.space) {
      y_coef = 1;
      break;
    }
    if(((2 * pow(10, y_radix)) * ranges.height / y_range) > prefs.space) {
      y_coef = 2;
      break;
    }
    if(((5 * pow(10, y_radix)) * ranges.height / y_range) > prefs.space) {
      y_coef = 5;
      break;
    }
    y_radix++;
  }

  if(ranges.radian) {
    pi_inc = (gint)ceil((gdouble)prefs.space * x_range / ranges.width / M_PI);
    x_spacing = pi_inc * M_PI;
    sub_pi = (gint)MIN(pow(2, floor(log(x_spacing * ranges.width /
			      (ranges.xmax - ranges.xmin) / prefs.space) / log(2))), 128);
    x_spacing = pi_inc * M_PI / sub_pi;    
  } else {
    x_spacing = x_coef * pow(10, x_radix);
  }

  x_count = (gint)ceil(x_range / x_spacing);
  
  y_spacing = y_coef * pow(10, y_radix);
  y_count = (gint)ceil(y_range / y_spacing);

  /* draw grid */
  if (prefs.do_grid) {
    x = (floor(ranges.xmin / x_spacing)) * x_spacing;
    for(i = 0; i <= x_count; i++, x += x_spacing) {
      gdk_draw_line(pixmap, grid_gc,
                    pixmap_x(x), pixmap_y(ranges.ymin),
                    pixmap_x(x), pixmap_y(ranges.ymax));
    }
    y = ((gint)((-1 + ranges.ymin / y_spacing))) * y_spacing;
    for(i = 0; i <= y_count; i++, y += y_spacing) {    
      gdk_draw_line(pixmap, grid_gc,
                    pixmap_x(ranges.xmin), pixmap_y(y),
		    pixmap_x(ranges.xmax), pixmap_y(y));
    }
  }
  
  /* draw the horizontal axis */
  if (prefs.do_Ox)
  gdk_draw_line(pixmap, axes_gc,
  		0               , pixmap_y(0),
  		ranges.width - 1, pixmap_y(0));
  
  /* draw the vertical axis */
  if (prefs.do_Oy)
  gdk_draw_line(pixmap, axes_gc,
                pixmap_x(0), 0,
		pixmap_x(0), ranges.height - 1);

  if (fd) {
    fprintf(fd, "%s", "%% axes / grid\n");
    fprintf(fd, "2 setcolor axes_lw slw\n");
    fprintf(fd, "do_Ox { xmin 0 m xmax 0 l s } if\n");
    fprintf(fd, "do_Oy { 0 ymin m 0 ymax l s } if\n");
    fprintf(fd, "grid_lw slw\n");    
    fprintf(fd, "symbol\n");
  }

  /* draw notch and label */
  x = (floor(ranges.xmin / x_spacing)) * x_spacing;
  
  for(i = 0; i <= x_count; i++, x += x_spacing) {
    if (prefs.do_Ox && prefs.do_xval && !prefs.do_grid)
      gdk_draw_line(pixmap, axes_gc,
                    pixmap_x(x), pixmap_y(0) - 2,
                    pixmap_x(x), pixmap_y(0) + 2);
    if (fd)
      fprintf(fd, "%f xt\n", x);

    /* test for origin */
    if(fabs(x) < x_spacing / 2) {
      g_string_sprintf(buf_1, "0");
      d = (prefs.do_grid)? 2 : 5;
      j = 0;
    } else {
      if(ranges.radian)
        g_string_sprintf(buf_2, "%%gp");
      else
        g_string_sprintf(buf_2, "%%0.%df", 
		         MAX((gint)ceil(-log10(x_spacing)), 0));
      if(ranges.radian) {
	if (fabs( x / M_PI - 1.0) < 0.05)
          g_string_sprintf(buf_1, "%s", "p");
	else
        if (fabs( x / M_PI + 1.0) < 0.05)
          g_string_sprintf(buf_1, "%s", "-p");
	else
          g_string_sprintf(buf_1, buf_2->str, x / M_PI);
      } else
        g_string_sprintf(buf_1, buf_2->str, x);      
      d = (prefs.do_grid)? 2 : 0;
      j = 1;
    }

    if (prefs.do_Ox && prefs.do_xval)
      gdk_draw_text(pixmap, numb_font, numb_gc,
                    pixmap_x(x) - d - (prefs.do_grid? 9 : 4) *
		      gdk_string_width(numb_font, buf_1->str) / 8,
		    pixmap_y(0) + gdk_string_height(numb_font, buf_1->str) + 5,
		    buf_1->str, buf_1->len);
    if (fd)
      fprintf(fd, "%f (%s) x%c\n", x, buf_1->str, j?'p':'z');
  }

  y = ((gint)((-1 + ranges.ymin / y_spacing))) * y_spacing;

  for(i = 0; i <= y_count; i++, y += y_spacing) {
    /* test for origin */
    if(fabs(y) < y_spacing / 2) continue;

    if (prefs.do_Oy && prefs.do_yval && !prefs.do_grid)
      gdk_draw_line(pixmap, axes_gc,
                    pixmap_x(0) - 2, pixmap_y(y),
		    pixmap_x(0) + 2, pixmap_y(y));
    if (fd)
      fprintf(fd, "%f yt\n", y);

    g_string_sprintf(buf_2, "%%0.%df", MAX((gint)ceil(-log10(y_spacing)), 0));
    g_string_sprintf(buf_1, buf_2->str, y);

    if (prefs.do_Oy && prefs.do_yval)
      gdk_draw_text(pixmap, numb_font, numb_gc,
                    pixmap_x(0) - 3 - gdk_string_width(numb_font, buf_1->str),
		    pixmap_y(y) + (prefs.do_grid?  7 : 2) *
		      gdk_string_height(numb_font, buf_1->str) / 4,
		    buf_1->str, buf_1->len);
    if (fd)
      fprintf(fd, "%f (%s) yp\n", y, buf_1->str);
  }

  if (fd) {
    fprintf(fd, "%s", "\n%% draw curves\n");
    fprintf(fd, "curve_lw slw\n");
  }

  g_string_free(buf_1, TRUE);
  g_string_free(buf_2, TRUE);
}

/* check
 */
gint
check(gchar *prog)
{
  struct stat buf;
  char *ptr;
  gint i = FALSE;
  if (prog) {
    prog = g_strdup(prog);
    ptr = strchr(prog, ' ');
    if (ptr) *ptr = '\0';
    if (*prog == '/') {
      if (stat(prog, &buf)) {
        sprintf(template, "/usr/bin/%s %s", prog, _("not found"));
        i = TRUE;
      }
    } else {
      sprintf(template, "/usr/bin/%s", prog);
      if (stat(template, &buf)) {
        sprintf(template, "%s %s", prog, _("not found"));
        i = TRUE;
      }
    }
    g_free(prog);
    if (i) write_log(template);
  }
  return(i);
}

static gchar *
getbool(gint i)
{
  return (i)? "true" : "false";
}

/* process_output
 */
static void
process_output(gint mode)
{
  gint i;
  struct stat buf;
  
  i = 0;
  i |= ((prefs.print == GEG_RC_PDF) && check(prefs.eps_to_pdf));
  i |= ((prefs.print == GEG_RC_SVG) && check(prefs.eps_to_svg));
  if (i) write_log(_("Check prefs / utilities"));

  i = stat(eps_file, &buf);
  if (i == 0) {
    if (S_ISREG(buf.st_mode))
      unlink(eps_file);
    else
      return;
  }

  fd = fopen(eps_file, "w");
  
  fprintf(fd, "%s", "%!PS-Adobe-2.0 EPSF-2.0\n");
  fprintf(fd, "%s", "%%Pages: 1\n");
  ratio = (gdouble) ranges.height / (gdouble) ranges.width;
  if (ratio * 520 > 768.0) {
    fy = 768.0;
    fx = fy / ratio;
  } else {
    fx = 520.0;
    fy = fx * ratio;    
  }
  fx = fx / (ranges.xmax - ranges.xmin);
  fy = fy / (ranges.ymax - ranges.ymin);
  ratio = fy / fx;
  fprintf(fd, "%s %d %d %d %d\n", "%%BoundingBox:", 37, 37, 559, 
          37 + (522 * ranges.height) / ranges.width);
  fprintf(fd, "%s", "%%EndComments\n%%Page: 1 1\n");
  fprintf(fd, "gsave\n/fx %f def /fy %f def\n", fx, fy);
  fprintf(fd, "fx dup scale\n%f %f translate\n",
	  -ranges.xmin+38/fx, ratio*(-ranges.ymin+38/fy));
  fprintf(fd, "/xmin %f def /xmax %f def\n/ymin %f def /ymax %f def\n",
	  ranges.xmin, ranges.xmax, ranges.ymin, ranges.ymax);
  fprintf(fd, "/ratio %f def\n", ratio);  
  fprintf(fd, "/zoom %f def\n", prefs.zoom);
  
  fprintf(fd, "%s", "\n%% colors\n");
  fprintf(fd, "%s", "%% 0=backgr, 1=numbers, 2=axes, 3=select, 4=gray, 5=def, 6+ curves\n");
  fprintf(fd, "/colors [\n");  
  for (i=0; i<prefs.n_colors; i++) color_print(fd, i);
  fprintf(fd, "] def\n\n");
  fprintf(fd, "%%%% adjust these values\n");
  fprintf(fd, "/do_color %s def\n", getbool(prefs.do_color));
  fprintf(fd, "/do_formulas %s def\n", getbool(prefs.text_position >= 0));
  fprintf(fd, "/do_box %s def\n", getbool(prefs.do_box));
  fprintf(fd, "/do_grid %s def\n", getbool(prefs.do_grid));  
  fprintf(fd, "/do_Ox %s def /do_Oy %s def\n", 
              getbool(prefs.do_Ox), getbool(prefs.do_Oy));
  fprintf(fd, "/do_xval %s def /do_yval %s def\n", 
              getbool(prefs.do_xval), getbool(prefs.do_yval));
  fprintf(fd, "/number_size %g def\n", prefs.number_size);
  fprintf(fd, "/formula_size %g def\n", prefs.formula_size);
  fprintf(fd, "/axes_lw %g def\n", prefs.axes_linewidth);
  fprintf(fd, "/box_lw %g def\n", prefs.box_linewidth);
  fprintf(fd, "/grid_lw %g def\n", prefs.grid_linewidth);  
  fprintf(fd, "/curve_lw %g def\n", prefs.curves_linewidth);
  fprintf(fd, "/point_radius %g def\n\n", prefs.point_radius);  
  fprintf(fd, "%s", ps_header);
  refresh_graph(NULL, NULL);
  fprintf(fd, "%s", "showpage\ngrestore\n%%Trailer\n%%EOF\n");  
  fclose(fd);
  fflush(fd);
  fd = NULL;

  i = stat(eps_file, &buf);
  if (i != 0 || !S_ISREG(buf.st_mode)) goto failure;

  if (mode == GEG_RC_EPS) return;  
  
  if (mode == GEG_RC_PDF) {    
    sprintf(template, "cd /tmp ; %s %s ; rm -f %s",
	    prefs.eps_to_pdf, eps_file, eps_file);
    if (system(template));
    i = stat(pdf_file, &buf);
    if (i != 0 || !S_ISREG(buf.st_mode)) goto failure;
    return;
  } else
  if (mode == GEG_RC_SVG) {
    sprintf(template, "cd /tmp ; %s %s %s ; rm -f %s",
	    prefs.eps_to_svg, eps_file, svg_file, eps_file);
    if (system(template));
    i = stat(svg_file, &buf);
    if (i != 0 || !S_ISREG(buf.st_mode)) goto failure;
    return;
  }
  
failure:
  addto_log("▪ ");    
  write_log(_("Output failed !"));  
}

/* edit_formulas_event
 */
static void
delete_edit_event(GtkWidget *widget, gpointer data)
{
  gtk_widget_destroy(ed);
  ed = NULL;
}

static void
ok_edit_event(GtkWidget *widget, gpointer data)
{
  FILE *fp;
  GtkTextIter start, end;
  gchar *text, *ptr, *form, *lform = NULL;
  gchar c;
  remove_all_event(NULL, NULL);
  gtk_text_buffer_get_start_iter(te_buf, &start);  
  gtk_text_buffer_get_end_iter(te_buf, &end);
  text = gtk_text_buffer_get_text(te_buf, &start, &end, FALSE);
  if (!text) {
    delete_edit_event(widget, NULL);
    return;
  }
  if (!strcmp(data, "eps")) {
    fp = fopen(eps_file, "w");
    if (!fp) return;
    fprintf(fp, "%s", text);
    fclose(fp);
    delete_edit_event(widget, NULL);
    return;
  } else
  if (!strcmp(data, "geg")) {
    fp = fopen(geg_file, "w");
    if (!fp) goto sequel;
    fprintf(fp, "%s", text);
    fclose(fp);
    geg_file_open(widget, (gpointer)1);
    goto sequel;
  }
  ptr = text;
  form = text;
  while (*ptr) {
    while ((c = *ptr) && *ptr != '\n') ++ptr;
    *ptr = '\0';
    while (isspace(*form)) ++form;
    if (*form == '\0') formula_add(NULL, "##");
    else
    if (!formula_find(form)) formula_add(NULL, form);
    if (c) {
      ++ptr;
      form = ptr;
      if (*form && *form != '\n') lform = form;
    }
  }
  if (lform) {
    while (isspace(*lform)) ++lform;    
    gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(eq_co)->entry), lform);
  }
 sequel:
  g_free(text);
  set_graphic_frame_title();
  refresh_graph(NULL, NULL);
  /* clear_log(NULL, NULL); */
  delete_edit_event(widget, NULL);
}

void
raise_editor(gchar *data)
{
  static GdkFont *font = NULL;
  GtkWidget *bu, *vb, *te, *sw;
  PangoFontDescription *font_desc;

  addto_log("▪ ");
  if (data && !strcmp(data, "formulas")) {
    write_log(_("Edition of formulas"));
    sprintf(template, "%s: %s", "Geg", _("Edit formulas"));  
  } else
  if (data && !strcmp(data, "geg")) {  
    write_log(_("Edition of geg file"));
    sprintf(template, "%s: %s", "Geg", _("Edit *.geg file"));      
  }
  if (data && !strcmp(data, "eps")) {  
    write_log(_("Edition of eps file"));
    sprintf(template, "%s: %s", "Geg", _("Edit *.eps file"));      
  }  

  if(ed && ed->window) {
    gtk_widget_map(ed);
    gdk_window_raise(ed->window);
    return;
  }

  if(!font)
    font = gdk_font_load("fixed");

  g_assert(font != NULL);

  ed = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_modal(GTK_WINDOW(ed), TRUE);
  gtk_widget_set_usize(ed, 960, 720);

  gtk_signal_connect(GTK_OBJECT(ed), "delete_event",
                     GTK_SIGNAL_FUNC(delete_edit_event), NULL);

  gtk_window_set_wmclass(GTK_WINDOW(ed), "edit", "Geg");
  gtk_window_set_title(GTK_WINDOW(ed), template);
  gtk_window_set_policy(GTK_WINDOW(ed), TRUE, TRUE, TRUE);
  gtk_window_position(GTK_WINDOW(ed), GTK_WIN_POS_NONE);

  vb = gtk_vbox_new(FALSE, 0);
  te = gtk_text_view_new();

  font_desc = pango_font_description_from_string ("Dejavu Sans Mono 12");
  gtk_widget_modify_font (te, font_desc);

  gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(te), GTK_WRAP_WORD);

  sw = gtk_scrolled_window_new(GTK_TEXT_VIEW(te)->vadjustment,
			       GTK_TEXT_VIEW(te)->hadjustment);
  gtk_container_add(GTK_CONTAINER(ed), vb);
  gtk_container_add(GTK_CONTAINER(vb), sw);
  gtk_container_add(GTK_CONTAINER(sw), te);

  bu = gtk_button_new_with_label(_("Ok"));
  gtk_widget_set_usize(bu, 50, 30);
  GTK_WIDGET_SET_FLAGS(bu, GTK_CAN_DEFAULT);
  gtk_box_pack_start(GTK_BOX(vb), sw, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(vb), bu, FALSE, FALSE, 0);
  gtk_signal_connect(GTK_OBJECT(bu), "clicked",
                     GTK_SIGNAL_FUNC(ok_edit_event), data);

  te_buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(te));
  gtk_text_buffer_get_end_iter(te_buf, &te_iter);

  gtk_widget_show(ed);
  gtk_widget_show(vb);
  gtk_widget_show(sw);
  gtk_widget_show(bu);
  gtk_widget_show(te);
  gtk_widget_show((GtkWidget *)te_buf);
}

void
edit_formulas_event(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
  gint i;
  gchar **formulas = formula_list();
  
  raise_editor("formulas");
  for (i = 0; i < formula_count(); i++) {
    if (strcmp(formulas[i], "##"))
      sprintf(template, "%s\n", formulas[i]);
    else
      sprintf(template, "\n");      
    gtk_text_buffer_insert(te_buf, &te_iter, template, strlen(template));
  }  
}

/* edit_script_event
 */
void
edit_geg_event(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
  FILE *fp;

  geg_file_save(geg_file);
  raise_editor("geg");
  fp = fopen(geg_file, "r");
  if (fp) {
    while(fgets(template, 256, fp)) {
      gtk_text_buffer_insert(te_buf, &te_iter, template, strlen(template));
    }
    fclose(fp);
  }
}

/* edit_eps_event
 */
void
edit_eps_event(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
  FILE *fp;
  addto_log("▪ ");  
  write_log(_("Edition of EPS file"));
  process_output(GEG_RC_EPS);
  *template = '\0';
  if (prefs.editor) *template = prefs.editor[0];
  if (*template) sprintf(template, "%s %s", prefs.editor, eps_file);
  if (!*template || system(template) == 0x7f00) {
    raise_editor("eps");
    fp = fopen(eps_file, "r");
    if (fp) {
      while(fgets(template, 256, fp)) {
        gtk_text_buffer_insert(te_buf, &te_iter, template, strlen(template));
      }
      fclose(fp);
    }    
  }
}

/* print_event
 */
void
print_event(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
  gint mode = *(gint *)data;
  gint i;
  gchar *msg;
  
  addto_log("▪ ");
  if (mode == GEG_RC_EPS) msg = _("EPS file to be created");
  else if (mode == GEG_RC_PDF) msg = _("PDF file to be created");
  else if (mode == GEG_RC_SVG) msg = _("SVG file to be created");
  else msg = "???";
  write_log(msg);
  process_output(mode);
  
  i = 0;
  i |= ((prefs.print == GEG_RC_EPS) && check(prefs.eps_viewer));
  i |= ((prefs.print == GEG_RC_PDF) && check(prefs.pdf_viewer));
  i |= ((prefs.print == GEG_RC_SVG) && check(prefs.svg_viewer));    
  if (i) {
    write_log(_("Check prefs / utilities"));
    return;
  }

  if (mode == GEG_RC_EPS) {
      sprintf(template, "( cd /tmp ; %s %s ) &",
	      prefs.eps_viewer, eps_file);
      if (system(template));
  } else
  if (mode == GEG_RC_PDF) {
      sprintf(template, "( cd /tmp ; %s %s ) &",
	      prefs.pdf_viewer, pdf_file);
      if (system(template));
  } else
  if (mode == GEG_RC_SVG) {
      sprintf(template, "( cd /tmp ; %s %s ) &",
	      prefs.svg_viewer, svg_file);
      if (system(template));
  }
}

/* press_event, event that occurs when the user click on the drawing area 
 * left button == zoom
 * middle button == function intercepts
 * right button == axes intercepts
 */
void
press_event(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
  if(event->button > 3)	/* mouse with lotsa buttons */
    return;
  
  /* store the press coordinates */
  x_down = (gint)event->x;
  y_down = (gint)event->y;
  
  /* connect the motion signal so we see a rectangle */
  gtk_signal_connect(GTK_OBJECT(widget), "motion_notify_event",
                     GTK_SIGNAL_FUNC(selection_event), NULL);
  
  gdk_window_set_cursor(widget->window, cursors[event->button]);

  gtk_signal_connect(GTK_OBJECT(widget), "button_release_event",
                       GTK_SIGNAL_FUNC(release_event), NULL);
}

/* release_event, event that occurs when the user releases button on the
 * drawing area
 */
static void
release_event(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
  gint b, x_up, y_up;
  gdouble x1, x2;
  gdouble y1, y2;
  
  /* remove the selection callback, and this callback */
  gtk_signal_disconnect_by_func(GTK_OBJECT(widget),
                                GTK_SIGNAL_FUNC(release_event), NULL);
  gtk_signal_disconnect_by_func(GTK_OBJECT(widget), 
                                GTK_SIGNAL_FUNC(selection_event), NULL);
  
  /* put the normal cursor back */
  gdk_window_set_cursor(widget->window, cursors[0]);

  x_up = (gint)event->x;
  y_up = (gint)event->y;
  
  /* if the user pressed and released the button nearby, we'll let 'em off */
  if((abs(x_up - x_down) < MINSEL) && (abs(y_up - y_down) < MINSEL))
    return;

  /* check for zero width or zero height */
  if((x_up == x_down) || (y_up == y_down))
    return;
  
  x1 = (x_down < x_up) ? real_x(x_down) : real_x(x_up);
  x2 = (x_down > x_up) ? real_x(x_down) : real_x(x_up);
  y1 = (y_down > y_up) ? real_y(y_down) : real_y(y_up);
  y2 = (y_down < y_up) ? real_y(y_down) : real_y(y_up);

  b = event->button;
  if (ranges.mode && b <= 3) b = 1;
  
  switch(b) {
  case 1:	/* zoom to the selection */
    /* check for minimum resolution */
    if((x2 - x1 < prefs.minres) ||
       (y2 - y1 < prefs.minres)) {
      write_log(_("Minimum Resolution"));
      ranges.xmin = x1 - MAX((prefs.minres - (x2 - x1)) / 2, 0);
      ranges.xmax = x2 + MAX((prefs.minres - (x2 - x1)) / 2, 0);
      ranges.ymin = y1 - MAX((prefs.minres - (y2 - y1)) / 2, 0);
      ranges.ymax = y2 + MAX((prefs.minres - (y2 - y1)) / 2, 0);
    }
    else {
      ranges.xmin = x1;
      ranges.xmax = x2;
      ranges.ymin = y1;
      ranges.ymax = y2;
    }
    addto_log("▪ ");
    write_log(_("Zoom Selection"));
    break;
  case 2:	/* solve function intercepts */
    addto_log("▪ ");
    write_log(_("Function Intercepts:"));
    reset_variables();
    mute = 1;
    formula_forall(solve_fint, x1, x2, y1, y2);
    mute = 0;
    break;
  case 3:	/* solve axes intercepts */
    addto_log("▪ ");    
    write_log(_("Axis Intercepts:"));
    reset_variables();
    mute = 1;
    formula_forall(solve_aint, x1, x2, y1, y2);
    mute = 0;
    break;
  default:
    g_assert_not_reached();
    break;
  }
}

/* motion_event, function sets the status label to tell what the coordinates
 * of the mouse pointer are, callback from the drawing area widget    
 */
void
motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data)
{
  GtkWidget *label;
  GString *buf_1 = g_string_new(NULL);
  GString *buf_2 = g_string_new(NULL);
  gdouble width  = ranges.xmax - ranges.xmin;
  gdouble height = ranges.ymax - ranges.ymin;

  label = (GtkWidget *)data;

  g_string_sprintf(buf_1, "x : %%0.%df, y : %%0.%df",
                   CLAMP((gint)ceil(-log10(width / ranges.width)), 0, 6),
                   CLAMP((gint)ceil(-log10(height / ranges.height)), 0, 6));
  g_string_sprintf(buf_2, buf_1->str, real_x(event->x), real_y(event->y));
  gtk_label_set(GTK_LABEL(label), buf_2->str);

  g_string_free(buf_1, TRUE);
  g_string_free(buf_2, TRUE);
}

/* selection_event, this callback is registered after a mouse button has been
 * pressed in the drawing area, it erases the ?last? rectangle and draws 
 * another to represent the area the user has selected. the callback is
 * unregistered when the user releases the mouse button
 */
void
selection_event(GtkWidget *widget, GdkEventMotion *event, gpointer data)
{
  static gint x_left = 0, y_top = 0;
  static gint x_right = 0, y_bottom = 0;
  gint x, y;
  /* vanish the last selection */
  /* top horizontal line of rectangle */
  gdk_draw_pixmap(widget->window,
                  widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
		  pixmap, 
		  x_left, y_top,
		  x_left, y_top,
		  x_right - x_left, 1);
  /* left vertical line of rectangle */ 
  gdk_draw_pixmap(widget->window,
                  widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
		  pixmap,
		  x_left, y_top,
		  x_left, y_top,
		  1, y_bottom - y_top);
  /* bottom horizontal line of rectangle */
  gdk_draw_pixmap(widget->window,
                  widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
		  pixmap,
		  x_left, y_bottom,
		  x_left, y_bottom,
		  x_right - x_left + 1, 1);
  /* right vertical line of rectangle */
  gdk_draw_pixmap(widget->window,
                  widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
		  pixmap,
		  x_right, y_top,
		  x_right, y_top,
		  1, y_bottom - y_top);

  gdk_gc_set_foreground(sel_gc, &prefs.gdk_color[DEF_SEL_COLOR]);
  gdk_gc_set_line_attributes(sel_gc, 1, GDK_LINE_ON_OFF_DASH,
                             GDK_CAP_NOT_LAST, GDK_JOIN_MITER);

  x = (gint) event->x;
  y = (gint) event->y;

  x_left   = (x_down < x) ? x_down : x;
  x_right  = (x_down > x) ? x_down : x;
  y_top    = (y_down < y) ? y_down : y;
  y_bottom = (y_down > y) ? y_down : y;

  gdk_draw_rectangle(widget->window,
  		     sel_gc,
		     FALSE,
		     x_left, y_top,
		     x_right - x_left, y_bottom - y_top);
}

/* leave_event, function clears the status label when the mouse leaves the
 * drawing area, callback from the drawing area widget 
 */
void
leave_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
  GtkWidget *label;

  label = (GtkWidget *)data;

  gtk_label_set(GTK_LABEL(label), "");
}

/* configure_event, this is called whenever the drawing area widget changes
 * its size, the old pixmap is deleted and replaced with one of size equal 
 * to the new size of the drawing area
 */
gint
configure_event(GtkWidget *widget, GdkEventConfigure *event, gpointer data)
{
  gdouble y;
  /* delete the old pixmap if it exists */
  if(pixmap)
    gdk_pixmap_unref(pixmap);

  /* set the ranges.width/height to the dimensions of the drawing area */
  ranges.width = widget->allocation.width;
  ranges.height = widget->allocation.height;

  /* create a new pixmap of a size equal to the drawing area */
  pixmap = gdk_pixmap_new(widget->window, ranges.width, ranges.height, -1);
  /* clear the pixmap to the user's background color */
  clear_pixmap();
  if (prefs.zoom_xy == 4) {
    ranges.ymin = (ranges.ymin + ranges.ymax)/2.0;
    y = 0.5 * (ranges.xmax - ranges.xmin) * ((gdouble)ranges.height / (gdouble)ranges.width);
    ranges.ymax = ranges.ymin + y; 
    ranges.ymin = ranges.ymin - y;
  }  
  draw_axes();
  return(FALSE);
}

/* clear_pixmap, this function clears the pixmap to the user's background
 * color. It should be called immediately after creating the pixmap
 * and also when we wish to wipe the pixmap eg. when the user changes the
 * zoom.
 */
void
clear_pixmap(void)
{
  gdk_gc_set_foreground(back_gc, &prefs.gdk_color[DEF_BACK_COLOR]);
  gdk_draw_rectangle(pixmap, back_gc, TRUE, 0, 0,
                     ranges.width,
		     ranges.height);
}

/* drawing_init, this function initializes stuff  
 */
void
drawing_init(GtkWidget *widget, gpointer data)
{
  GdkPixmap *source, *mask;

  colormap = gdk_window_get_colormap(widget->window);
  // style = gtk_widget_get_style(widget);
  gdk_color_black(colormap, &black);
  gdk_color_white(colormap, &white);

  alloc_func_colors(colormap);

  /* do the GCs */
  back_gc = gdk_gc_new(widget->window);
  func_gc = gdk_gc_new(widget->window);
  numb_gc = gdk_gc_new(widget->window);
  axes_gc = gdk_gc_new(widget->window);
  grid_gc = gdk_gc_new(widget->window);  
  sel_gc  = gdk_gc_new(widget->window);
  
  /* do the cursors */
  cursors[0] = gdk_cursor_new(GDK_CROSSHAIR);

  source = gdk_pixmap_create_from_data(widget->window, zoom_bits,
				       zoom_width, zoom_height, 1,
				       &black, &white);
  mask = gdk_pixmap_create_from_data(widget->window, zoom_m_bits,
				     zoom_m_width, zoom_m_height, 1,
				     &black, &white);
  cursors[1] = gdk_cursor_new_from_pixmap(source, mask, 
					  &black, 
					  &white, 
					  0, 0);

  source = gdk_pixmap_create_from_data(widget->window, fint_bits,
				       fint_width, fint_height, 1,
				       &black, &white);
  mask = gdk_pixmap_create_from_data(widget->window, fint_m_bits,
				       fint_m_width, fint_m_height, 1,
				       &black, &white);
  cursors[2] = gdk_cursor_new_from_pixmap(source, mask, 
					  &black, 
					  &white, 
					  0, 0);

  source = gdk_pixmap_create_from_data(widget->window, aint_bits,
				       aint_width, aint_height, 1,
				       &black, &white);
  mask = gdk_pixmap_create_from_data(widget->window, aint_m_bits,
				       aint_m_width, aint_m_height, 1,
				       &black, &white);
  cursors[3] = gdk_cursor_new_from_pixmap(source, mask, 
					  &black, 
					  &white, 
					  0, 0);
  
  /* set the drawing area window's cursor to the default cursor */
  gdk_window_set_cursor(widget->window, cursors[0]);

  /* unregister this function from the drawing area widget so it doesn't */
  /* get called again */
  gtk_signal_disconnect_by_func(GTK_OBJECT(widget),
                                GTK_SIGNAL_FUNC(drawing_init), NULL);
}

/* expose_event, your run of the mill expose event, copies the exposed
 * rectangular region of the pixmap to the drawing area widget's window
 */
gint
expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
{
  gdk_draw_pixmap(widget->window,
                  widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
		  pixmap,
		  event->area.x, event->area.y,	/* source coordinates */
		  event->area.x, event->area.y, /* destination coordinates */
		  event->area.width, event->area.height);

  return(FALSE);
}

/* redraw_event, this is like a forced expose event that exposes the entire
 * drawing area widget. it is neccessary such that the colors get updated
 * when the user changes his/her color preferences                      
 */
void
redraw_event(GtkWidget *widget, gpointer data)
{
  gdk_draw_pixmap(widget->window,
                  widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
		  pixmap,
                  0, 0, 0, 0,
		  widget->allocation.width, widget->allocation.height);
}

/* solve_fint
 */
static gint
solve_fint(gchar **formulas, gint nformulas, gdouble x1, gdouble x2,
	   gdouble y1, gdouble y2)
{
  token_list **list;
  parse_tree **tree;
  gdouble x_left, x_middle, x_right, y, yp;
  gint intercept = FALSE;
  gint i, j, bl, br, bm;
  GString *buf_1, *buf_2;

  buf_1 = g_string_new(NULL);
  buf_2 = g_string_new(NULL);

  g_assert(x1 < x2);
  g_assert(y1 < y2);

  list = g_new(token_list *, nformulas);
  tree = g_new(parse_tree *, nformulas);

  for(i = 0; i < nformulas; i++) {
    list[i] = make_token_list(formulas[i]);
    tree[i] = make_parse_tree(list[i]);
  }
  
  for(i = 0; i < nformulas - 1; i++) {
    if (tree[i]->type >= COM) continue;
    if (tree[i]->type == DEF) {
      if (tree[i]->left) (void) eval_tree(tree[i]->right);
      continue;	
    }
    for(j = i + 1; j < nformulas; j++) {
      if (tree[j]->type >= COM) continue;
      if (tree[j]->type == DEF) {
	if (tree[j]->left) (void) eval_tree(tree[j]->right);
        continue;	
      }
      x_left = x1;
      x_right = x2;
      var[23] = x_left; /* 23 is 'x' */
      y = eval_tree(tree[i]) - eval_tree(tree[j]);
      if (!isfinite(y)) continue;
      bl = (y > 0);
      var[23] = x_right;
      y = eval_tree(tree[i]) - eval_tree(tree[j]);
      if (!isfinite(y)) continue;      
      br = (y > 0);
      if(bl ^ br) {
	bm = 0;
	while((x_right - x_left) > prefs.minres / 10) {
	  x_middle = (x_left + x_right) / 2;
	  var[23] = x_middle;
          y = eval_tree(tree[i]) - eval_tree(tree[j]);
	  if (!isfinite(y)) { bm = -1; break; }
          bm = (y > 0);
	  if(bl ^ bm) {
	    x_right = x_middle;
	    br = bm;
	  } else {
	    x_left = x_middle;
	    bl = bm;
	  }
	}
	if (bm < 0) continue;
	var[23] = x_right;
	y = eval_tree(tree[i]);
	yp = eval_tree(tree[j]);	
	if (!isfinite(y) || !isfinite(yp)) continue;
        if((y1 <= y) && (y <= y2) && (y1 <= yp) && (yp <= y2)) {
	  /* check it is in selection */
	  intercept = TRUE;
          g_string_sprintf(buf_1, _(" %s and %s at:\n x=%%0.%df, y=%%0.%df"), 
		           formulas[i], formulas[j],
                           MAX((gint)ceil(-log10(prefs.minres)), 0),
		           MAX((gint)ceil(-log10(prefs.minres)), 0));
          g_string_sprintf(buf_2, buf_1->str, x_right, y);
          write_log(buf_2->str);
	}
      }
    }
  }
  
  if(intercept == FALSE)
    write_log(_(" none found."));

  /* free the lists and trees */
  // printf("Curve intersections\n");

  for(i = 0; i < nformulas; i++) {
    // printf("[%d] %s\n", i, formulas[i]);
    free_tree(tree[i]);
    free_list(list[i]);
  }
  
  g_string_free(buf_1, TRUE);
  g_string_free(buf_2, TRUE);
  
  g_free(tree);
  g_free(list);
  
  return(0);
}

/* solve_aint
 */
static gint
solve_aint(gchar **formulas, gint nformulas, gdouble x1, gdouble x2,
	   gdouble y1, gdouble y2)
{
  token_list **list;
  parse_tree **tree;
  gdouble x_left, x_middle, x_right, y;
  gint intercept = FALSE;
  gint i, bl, br, bm;
  GString *buf_1, *buf_2;

  buf_1 = g_string_new(NULL);
  buf_2 = g_string_new(NULL);

  list = g_new(token_list *, nformulas);
  tree = g_new(parse_tree *, nformulas);

  /* make the lists and trees */
  for(i = 0; i < nformulas; i++) {
    list[i] = make_token_list(formulas[i]);
    tree[i] = make_parse_tree(list[i]);
  }

  if((x1 < 0) && (x2 > 0)) {
    /* solve the y-axes intercept */
    /* just substitute x = 0 */
    for(i = 0; i < nformulas; i ++) {
      if (tree[i]->type >= COM) continue;
      if (tree[i]->type == DEF) {
        if (tree[i]->left) (void) eval_tree(tree[i]->right);
        continue;
      }
      /* solve the y-axes intercept */
      var[23] = 0;
      y = eval_tree(tree[i]);
      if (!isfinite(y)) continue;
      if((y1 < y) && (y < y2)) {
	intercept = TRUE;
	g_string_sprintf(buf_1, " %s, y=%%0.%df", formulas[i],
                         MAX((gint)ceil(-log10(prefs.minres)), 0));
	g_string_sprintf(buf_2, buf_1->str, y);
	write_log(buf_2->str);
      }
    }
  }

  if((y1 < 0) && (y2 > 0)) {
    /* solve the x-axis intercept */
    /* the bisection method */
    for(i = 0; i < nformulas; i++) {
      if (!tree[i] || tree[i]->type >= COM) continue;
      if (tree[i]->type == DEF) {
        if (tree[i]->left) (void) eval_tree(tree[i]->right);	
        continue;
      }
      x_left = x1;	/* preserve x1 and x2 */
      x_right = x2;
      var[23] = x_left;
      y = eval_tree(tree[i]);
      if (!isfinite(y)) continue;
      bl = (y > 0);
      var[23] = x_right;
      y = eval_tree(tree[i]);
      if (!isfinite(y)) continue;
      br = (y > 0);	    
      if(bl ^ br) {
	bm = 0;
        while((x_right - x_left) > prefs.minres / 10) {
          x_middle = (x_left + x_right) / 2;
          var[23] = x_middle;
	  y = eval_tree(tree[i]);
	  if (!isfinite(y)) { bm = -1 ; break; }
          bm = (y > 0);	  
	  if(bl ^ bm) {
	    x_right = x_middle;
	    br = bm;
          } else {
	    x_left = x_middle;
	    bl = bm;
	  }
        }
	if (bm < 0) continue;
        var[23] = x_left;
        y = eval_tree(tree[i]);
        if (!isfinite(y) || y < ranges.ymin || y > ranges.ymax) continue;
	intercept = TRUE;
        g_string_sprintf(buf_1, " %s, x=%%0.%df", formulas[i], 
                         MAX((gint)ceil(-log10(prefs.minres)), 0));
        /* give the answer as x_right, that way if we are converging on zero */
        /* we give a positive number, instead of -0.00, which looks silly */
        g_string_sprintf(buf_2, buf_1->str, x_right);
        write_log(buf_2->str);
      }
    }
  }
  
  if(intercept == FALSE)
    write_log(_(" none found."));

  // printf("Axes interceptions\n");
  /* free the lists and trees */

  for(i = 0; i < nformulas; i++) {
    // printf("[%d] %s\n", i, formulas[i]);
    free_tree(tree[i]);
    free_list(list[i]);
  }
  
  g_string_free(buf_1, TRUE);
  g_string_free(buf_2, TRUE);

  g_free(tree);
  g_free(list);
  
  return(0);
}

static void
set_inc(gdouble *p)
{
    if (ranges.mode == DEF_CARTESIAN)
      *p = ranges.xmax - ranges.xmin;
    else
    if (ranges.mode == DEF_POLAR)
      *p = ranges.amax - ranges.amin;
    else
    if (ranges.mode == DEF_PARAMETRIC)      
      *p = ranges.tmax - ranges.tmin;      
    var[INCR] = (*p) * var[RINCR] * 0.5;
    var[TGTR] = (*p) * var[RTGTR];
    var[TGTL] = - (*p) * var[RTGTL];
    var[MULT] = 1.0 / (6.0 * var[INCR]);
    var[ACTV] = (gdouble)(var[RTGTR] >=0 || var[RTGTL] >= 0);
    *p = *p / (gdouble)(ranges.width * (prefs.interp + 1));
}

/* process_formula, draws the formula onto the pixmap   
 */
gint
process_formula(gchar *formula, gint number)
{
  static GdkFont *text_font = NULL;
  static gint v_offset;
  static gint index;
  GdkColor gdk_color;
  gint i, j, flag1 = 1, flag2 = 1;
  gdouble t, x, x1, y1, x2, y2, ub, rad, s1, s2;
  gdouble x_inc, t_inc;
  gdouble rgb_value[3];
  token_list *list = NULL;
  parse_tree *tree = NULL, *rtree;

  /* This is used for numerical integration ; just reset */
  for (i = 0; i < 26; i++) integr[i].ptr = NULL;
    
  if(number == 0) {
    index = 0;
    if (prefs.text_position >= 2)
      v_offset = ranges.height + (gdk_string_height(text_font, formula) + 2) / 2;
    else
      v_offset = 0;
  }
  
  if(!text_font)
    (void)((text_font = gdk_font_load(prefs.text_font)) ||
	   (text_font = gdk_font_load("fixed")));
  
  g_assert(text_font != NULL);
  
  list = make_token_list(formula);

  g_assert(list != NULL);
  tree = make_parse_tree(list);

  if(!tree) {
    free_list(list);
    /* this will report a parsing error */
    return(FALSE);
  }

  if (tree->type == COM) {
    /* ignore comments */
    return(TRUE);
  }
  
  if (tree->type == ASK) {
    /* evaluation request */
    if (!tree->right) return(FALSE); 
    set_inc(&x_inc);
    var[23] = ranges.xmin;
    y1 = eval_tree(tree->right);
    set_output(y1);
    sprintf(template, "= %.15g", y1);
    write_log(template);
    return(TRUE);
  }

  if (tree->type == SOL) {
    /* solution of equation */
    if (!tree->right) return(FALSE); 
    rtree = tree->right;
    set_inc(&x_inc);    
    if (strcmp(gtk_entry_get_text(GTK_ENTRY(tmin_en)),"") &&
        strcmp(gtk_entry_get_text(GTK_ENTRY(tmax_en)),"")) {
      x1 = atof(gtk_entry_get_text(GTK_ENTRY(tmin_en)));
      x2 = atof(gtk_entry_get_text(GTK_ENTRY(tmax_en)));
      if (x1 > x2) {
        t = x2;
        x2 = x1;
        x1 = t;
      }
    } else {
      x1 = ranges.xmin;
      x2 = ranges.xmax;
    }
    ub = (x2-x1)*1.0E-15;
    x = sqrt(-1);
    var[23] = x1;
    y1 = eval_tree(rtree);
    var[23] = x2;
    y2 = eval_tree(rtree);
    while (y1 * y2 < 0 && x2 - x1 > ub) { 
      x = (x1 + x2) * 0.5;
      var[23] = x;
      t = eval_tree(rtree);
      if (y1 * t < 0) {
        x2 = x;
        y2 = t;
      } else {
        x1 = x;
        y1 = t;
      }
    }
    set_output(x);
    sprintf(template, "= %.16g", x);
    write_log(template);
    return(TRUE);
  }

  if (tree->type == DEF) {
    /* directive */
    /* just report those bearing a value */
    if (tree->left) {
      y1 = eval_tree(tree->right);
      if ((tree->right->type & 255) == EQU) {
	i = tree->right->type >> 8;
	if (i == LINEWIDTH) {
	  if (var[LINEWIDTH] >= 0) {
	    j = (int)(2*var[LINEWIDTH] + 0.5);
	    if (j <= 0) j = 0;
	    if (j >= 30) j = 30;	    
	  } else
	    j = 1;
          gdk_gc_set_line_attributes(func_gc, j, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND);	  
	}
	if (i >= AMIN && i<= TMAX) set_minmax_value(i);
        if (!mute) {
	  if (i < 26) {
            sprintf(template, "%c=%.15g", 'a'+i, y1);
            /* sprintf(template, "%s=%.15g", varname[i-26], y1); */
            write_log(template);
	  }
        }
        return(TRUE);	
      }
      if (!mute) {
        sprintf(template, "%.15g", y1);
        write_log(template);
      }
    }    
    return(TRUE);
  }

  if (fd) fprintf(fd, "\n%%%% %s\n", formula);  

  if (var[COLOR] < 0) {
    i = DEF_DEFAULT_COLOR + index % (prefs.n_colors - DEF_DEFAULT_COLOR);
  
    if (fd) fprintf(fd, "%d setcolor\n", i);

    if (prefs.do_color) {
      gdk_gc_set_foreground(func_gc, &prefs.gdk_color[i]);
    } else
      gdk_gc_set_foreground(func_gc, &black);
  } else {
    i = (int) (var[COLOR] + 0.5);
    j = i % 1000;
    i = (i - j)/ 1000;
    rgb_value[2] = (gdouble) (j & 255) / 255.0;
    j = i % 1000;
    i = (i - j)/ 1000;
    rgb_value[1] = (gdouble) (j & 255) / 255.0;
    j = i % 1000;
    rgb_value[0] = (gdouble) (j & 255) / 255.0;
    alloc_color(&gdk_color, rgb_value, colormap);
    gdk_gc_set_foreground(func_gc, &gdk_color);
    if (fd) fprintf(fd, "%g %g %g setrgbcolor\n",
	            rgb_value[0], rgb_value[1], rgb_value[2]);
  }

  if (fd && var[LINEWIDTH] >= 0) {
    fprintf(fd, "%g slw\n", var[LINEWIDTH]);
  }
  
  if (prefs.text_position >= 2)
    v_offset -= gdk_string_height(text_font, formula) + 2;
  else
    v_offset += gdk_string_height(text_font, formula) + 2;

  
  if (prefs.text_position == 0)
    gdk_draw_text(pixmap, text_font, func_gc,
                  2,
		  v_offset,
		  formula, strlen(formula));
  if (prefs.text_position == 1)
    gdk_draw_text(pixmap, text_font, func_gc,
                  ranges.width - gdk_string_width(text_font, formula) - 2,
		  v_offset,
		  formula, strlen(formula));
  if (prefs.text_position == 2)
    gdk_draw_text(pixmap, text_font, func_gc,
                  2,
		  v_offset,
		  formula, strlen(formula));
  if (prefs.text_position == 3)
    gdk_draw_text(pixmap, text_font, func_gc,
                  ranges.width - gdk_string_width(text_font, formula) - 2,
		  v_offset,
		  formula, strlen(formula));

  if (fd) {
    i = abs(prefs.text_position) & 3;
    x = (i & 1)? ranges.xmax : ranges.xmin + (ranges.xmax - ranges.xmin)/500;
    y1 = (ranges.xmax - ranges.xmin) * prefs.formula_size / (1000 * ratio);
    if (i < 2) 
      fprintf(fd, "helv %g %g m (", x, ranges.ymax - (index+0.9) * y1);
    else
      fprintf(fd, "helv %g %g m (", x, ranges.ymin + (index+0.3) * y1);
    for (j=0; j<strlen(formula); j++) {
      if (formula[j] == '(' || formula[j] == ')') fprintf(fd, "\\");
      fprintf(fd, "%c", formula[j]);
    }
    if (i & 1) 
      fprintf(fd, ") sr\n");
    else
      fprintf(fd, ") sl\n");
  }
  ++index;
  
  if (ranges.mode == DEF_POLAR) {
    set_inc(&t_inc);
    t = ranges.amin;
    
    var[19] = t;
    rad = var[17] = eval_tree(tree);
    flag2 = isfinite(rad);
    if (flag2) {    
      x2 = rad * cos(t);
      y2 = rad * sin(t);
      if (fd)
        fprintf(fd, "%f %f m\n", x2, y2);	  
    } 
    for(i = 0; i < (ranges.width * (prefs.interp + 1)); i++) {
      for(j = 0; j < prefs.interp + 1; j++) {
	x1 = x2;
        y1 = y2;
	flag1 = flag2;
        var[19] = t;
	rad = var[17] = eval_tree(tree);
        flag2 = isfinite(rad);
        if (flag2) {    
          x2 = rad * cos(t);
          y2 = rad * sin(t);
	}
        if (flag1) {
          if (flag2) {
            gdk_draw_line(pixmap, func_gc,
                          pixmap_x(x1), pixmap_y(y1),
		          pixmap_x(x2), pixmap_y(y2));
            if (fd)
              fprintf(fd, "%f %f l\n", x2, y2);
	  }	  
	} else {
          if (fd && flag2) {
            fprintf(fd, "s\n");
            fprintf(fd, "%f %f m\n", x2, y2);	  
	  }
	}
        if (t > ranges.amax) goto enddraw;
        t += t_inc;
      }
    }
    goto enddraw;
  }

  if (ranges.mode == DEF_PARAMETRIC) {
    set_inc(&t_inc);
    t = ranges.tmin;

    var[19] = t;
    (void)eval_tree(tree);
    x2 = var[23];
    y2 = var[24];
    flag2 = isfinite(x2) && isfinite(y2);
    if (fd && flag2) {
      fprintf(fd, "%f %f m\n", x2, y2);	  
    } 
    for(i = 0; i < (ranges.width * (prefs.interp + 1)); i++) {
      for(j = 0; j < prefs.interp + 1; j++) {
	x1 = x2;
        y1 = y2;
	flag1 = flag2;
        var[19] = t;
        eval_tree(tree);
        x2 = var[23];
        y2 = var[24];
	flag2 = isfinite(x2) && isfinite(y2);
        if (flag1) {
          if (flag2) {
            gdk_draw_line(pixmap, func_gc,
                          pixmap_x(x1), pixmap_y(y1),
		          pixmap_x(x2), pixmap_y(y2));
            if (fd)
              fprintf(fd, "%f %f l\n", x2, y2);
	  }	  
	} else {
          if (fd && flag2) {
            fprintf(fd, "s\n");
            fprintf(fd, "%f %f m\n", x2, y2);	  
	  }
	}
        if (t > ranges.tmax) goto enddraw;
        t += t_inc;
      }
    }
    goto enddraw;
  }

  if (ranges.mode == DEF_SEQUENCE) {
    if (ranges.tmin > 0)
      i = (int)(ranges.tmin + 0.00000000000001);
    else
      i = -(int)(-ranges.tmin-0.00000000000001);
    if (var[POINTRADIUS] >= 0) {
      if (fd)
        fprintf(fd, "/point_radius %f def\n", var[POINTRADIUS]);
      j = 2 * (int)(3 * var[POINTRADIUS] + 0.6);      
    } else
      j = 6;
    if (strcmp(gtk_entry_get_text(GTK_ENTRY(tmax_en)), ""))
      y2 = atof(gtk_entry_get_text(GTK_ENTRY(tmax_en)));
    else
      y2 = ranges.xmax;
    y2 += 0.000000000001;
    while (i < y2) {
      var[13] = i; /* 13 is 'n' */
      y1 = eval_tree(tree);
      if (isfinite(y1)) {
	if (fd)
          fprintf(fd, "%d %f d\n", i, y1);
	gdk_draw_arc(pixmap, func_gc, TRUE,
		     pixmap_x((gdouble)i)-j/2, pixmap_y(y1)-j/2,
		     j, j, 0, 64 * 360);
      }
      ++i;
    }
    goto enddraw;    
  }
  
  set_inc(&x_inc);  
  x = ranges.xmin;

  var[23] = x - x_inc;
  y2 = eval_tree(tree);	/* get the ball rolling */
  if (fd && isfinite(y2))
    fprintf(fd, "%f %f m\n", x, y2);

  ub = 0.5 * var[GAP] * (ranges.ymax - ranges.ymin);
  s2 = 0;
  for(i = 0; i < (ranges.width * (prefs.interp + 1)); i++) {
    for(j = 0; j < prefs.interp + 1; j++) {
      y1 = y2;
      s1 = (s2 > 0)? -1 : 1;
      var[23] = x;
      y2 = eval_tree(tree);
      s2 = y2 - y1;
      if (!isfinite(y1) || !isfinite(y2) || s1 * s2 > ub) {
	flag1 = 0;
        if (fd && isfinite(y2)) {
          fprintf(fd, "s\n");
          fprintf(fd, "%f %f m\n", x, y2);	  
	}
	goto sequel;
      }
      if (flag1)
        gdk_draw_line(pixmap, func_gc,
                      pixmap_x(x - x_inc), pixmap_y(y1),
		      pixmap_x(x), pixmap_y(y2));
      flag1 = 1;
      if (fd)
        fprintf(fd, "%f %f l\n", x, y2);
    sequel:
      if (x > ranges.xmax) goto enddraw;
      x += x_inc;
    }
  }
  
 enddraw:  

  if (fd)
    fprintf(fd, "s\n");  
  if (tree) free_tree(tree);
  if (list) free_list(list);
  return(TRUE);
}

/* range_ok, simple check to make sure the user has entered sane ranges
 */
gint
range_ok(gint dofix)
{
  if((ranges.xmin > ranges.xmax) || (ranges.ymin > ranges.ymax)) {
    write_log(_("Invalid Range"));
    if(dofix) {
      ranges.xmin = prefs.ranges.xmin;
      ranges.xmax = prefs.ranges.xmax;
      ranges.ymin = prefs.ranges.ymin;
      ranges.ymax = prefs.ranges.ymax;
    }
    return(FALSE);
  }

  return(TRUE);
}
