/* 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 */

/* 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/reset.xpm>
#include <pixmaps/in.xpm>
#include <pixmaps/prefs.xpm>
#include <pixmaps/line.xpm>
#include <pixmaps/sine.xpm>
#include <pixmaps/exit.xpm>
#include <pixmaps/out.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 <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.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"

/* gdk_string_height() is new to GTK+-1.1 */
#if((GTK_MAJOR_VERSION == 1) && (GTK_MINOR_VERSION == 0))
#define gdk_string_height(a, b) 12
#endif

/* global data */
extern misc_colors m_colors;
extern struct_prefs prefs;
gdouble xmin, xmax, ymin, ymax;			/* range limits */
gchar *spacing;

/* 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 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 draw_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 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 reset_event(GtkWidget *widget, gpointer data);
static void new_event(GtkWidget *widget, gpointer data);
static void update_ranges(void);
static void entry_signals(gchar *flag);
static void realise_preferences(GtkWidget *widget, gpointer data);
static void set_spacing(GtkWidget *widget, gpointer data);
static void compound_entry(char *title, GtkWidget **entry, GtkWidget *vbox);

static GtkWidget *create_pixmap(GtkWidget *widget, gchar **data);
static GtkWidget *tb, *xmin_en, *xmax_en, *ymin_en, *ymax_en, *da;
static GdkPixmap *pixmap = NULL;
static gint pixmap_width, pixmap_height;
static GdkGC *back_gc = NULL, *func_gc = NULL;
static GdkGC *numb_gc = NULL, *axes_gc = NULL, *sel_gc = NULL;
static gint x_down, y_down;
static GdkCursor *cursors[4];

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

/* delete_event, called when window manager tries to delete window
 */
static gint
delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
  return(FALSE);
}

/* app, creates the main interface, called once by main
 */
int
app(void)
{
  GtkWidget *wi, *main_vb, *work_hb, *hs, *entry_hb;
  GtkWidget *mb, *file_me, *view_me, *help_me, *temp_mi, *new_mi;
  GtkWidget *info_vb, *range_vb;
  GtkWidget *status_la;
  GtkWidget *temp_fr;
  GtkWidget *graph_fr;
  GtkWidget *temp_la, *temp_bu, *eq_co;

  /* 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(gtk_main_quit), 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);
  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();

  new_mi = gtk_menu_item_new_with_label("New All");
  gtk_menu_append(GTK_MENU(file_me), new_mi);
  gtk_widget_show(new_mi);

  temp_mi = gtk_menu_item_new_with_label("Clear Log");
  gtk_menu_append(GTK_MENU(file_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("Preferences...");
  gtk_menu_append(GTK_MENU(file_me), temp_mi);
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
                     GTK_SIGNAL_FUNC(prefs_event),
		     GTK_SIGNAL_FUNC(realise_preferences));
  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(gtk_main_quit), 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);
  
  /* view menu */
  view_me = gtk_menu_new();
  temp_mi = gtk_menu_item_new_with_label("Erase Formulas...");
  gtk_menu_append(GTK_MENU(view_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("Reset Zoom");
  gtk_menu_append(GTK_MENU(view_me), temp_mi);
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
                     GTK_SIGNAL_FUNC(reset_event), NULL);
  gtk_widget_show(temp_mi);
  
  temp_mi = gtk_menu_item_new_with_label("Decimal Spacing");
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
		     GTK_SIGNAL_FUNC(set_spacing), "Decimal");
  gtk_menu_append(GTK_MENU(view_me), temp_mi);
  gtk_widget_show(temp_mi);

  temp_mi = gtk_menu_item_new_with_label("Radian Spacing");
  gtk_signal_connect(GTK_OBJECT(temp_mi), "activate",
		     GTK_SIGNAL_FUNC(set_spacing), "Radian");
  gtk_menu_append(GTK_MENU(view_me), temp_mi);
  gtk_widget_show(temp_mi);

  temp_mi = gtk_menu_item_new_with_label("View");
  gtk_menu_item_set_submenu(GTK_MENU_ITEM(temp_mi), view_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("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 */
  temp_fr = gtk_frame_new("Range");
  gtk_box_pack_start(GTK_BOX(info_vb), temp_fr, FALSE, FALSE, 0);

  range_vb = gtk_vbox_new(TRUE, 0);
  gtk_container_add(GTK_CONTAINER(temp_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);

  xmin = prefs.xmin;
  xmax = prefs.xmax;
  ymin = prefs.ymin;
  ymax = prefs.ymax;

  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_widget_show(range_vb);
  gtk_widget_show(temp_fr);

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

  create_log(temp_fr);

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

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

  gtk_widget_show(info_vb);

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

  temp_la = gtk_label_new("f(x) =");
  gtk_box_pack_start(GTK_BOX(entry_hb), temp_la, FALSE, FALSE, 5);
  gtk_widget_show(temp_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);

  /* connect the New All in the menubar up now that eq_co has been created */
  gtk_signal_connect(GTK_OBJECT(new_mi), "activate",
                     GTK_SIGNAL_FUNC(new_event), GTK_COMBO(eq_co)->entry);

  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.width, prefs.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);

  /* toolbar buttons */
  gtk_toolbar_append_item(GTK_TOOLBAR(tb), "New", 
                          "Erase all formulas and reset zoom", NULL,
			  create_pixmap(wi, new_xpm), 
			  GTK_SIGNAL_FUNC(new_event), GTK_COMBO(eq_co)->entry);

  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));

  gtk_toolbar_append_item(GTK_TOOLBAR(tb), "Reset", "Reset zoom to default",
                          NULL, create_pixmap(wi, reset_xpm),
			  GTK_SIGNAL_FUNC(reset_event), NULL);
  
  gtk_toolbar_append_space(GTK_TOOLBAR(tb));

  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), "Decimal", 
			  "Use decimal spacing", NULL,
			  create_pixmap(wi, line_xpm),
			  GTK_SIGNAL_FUNC(set_spacing), "Decimal");
			  
  gtk_toolbar_append_item(GTK_TOOLBAR(tb), "Radian", "Use radian spacing",
			  NULL, create_pixmap(wi, sine_xpm),
			  GTK_SIGNAL_FUNC(set_spacing), "Radian");
  
  gtk_toolbar_append_space(GTK_TOOLBAR(tb));
  gtk_toolbar_append_item(GTK_TOOLBAR(tb), "Prefs", "Edit preferences",
                          NULL, create_pixmap(wi, prefs_xpm), 
			  GTK_SIGNAL_FUNC(prefs_event),
                          GTK_SIGNAL_FUNC(realise_preferences));
  
  gtk_toolbar_append_item(GTK_TOOLBAR(tb), "Exit", "Exit geg",
			  NULL, create_pixmap(wi, exit_xpm),
			  GTK_SIGNAL_FUNC(gtk_main_quit), NULL);
  
  /* time to realise the preferences */
  realise_preferences(wi, "firstcall");

  if(prefs.spacing == GEG_RC_DECIMAL)
    spacing = "Decimal";
  else if(prefs.spacing == GEG_RC_RADIAN)
    spacing = "Radian";
  else
    g_assert_not_reached();
  
  gtk_widget_show(tb);

  gtk_widget_show(wi);

  gtk_widget_grab_focus(GTK_COMBO(eq_co)->entry);

  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);
}

/* realise preferences, make the non-startup preferences take effect       
 * the startup preferences are handled elsewhere
 */
static void
realise_preferences(GtkWidget *widget, gpointer data)
{
  GdkColormap *colormap;

  /* 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();

  /* colors */
  colormap = gdk_window_get_colormap(widget->window);
  g_assert(colormap != NULL);
  alloc_color(&m_colors.back, &prefs.back_vals[0], colormap);
  alloc_color(&m_colors.numb, &prefs.numb_vals[0], colormap);
  alloc_color(&m_colors.axes, &prefs.axes_vals[0], colormap);
  alloc_color(&m_colors.sel,  &prefs.sel_vals[0],  colormap);

  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, 80, -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  = xmax - xmin;
  gdouble height = ymax - 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, 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, 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, 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, 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;

  if(!range_ok(FALSE))
   return;

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

  if(!strcmp((char *)data, "in")) {
    if((width < prefs.minres) ||
       (height < prefs.minres)) {
      write_log(NULL, "Minimum Resolution");
      return;
    }

    x = - prefs.zoom / 2 * (width / (1 + prefs.zoom));
    y = - prefs.zoom / 2 * (height / (1 + prefs.zoom));


    write_log(NULL, "Zoom In");
  }
  else if(!strcmp((char *)data, "out")) {
    if((width > prefs.maxres) ||
       (height > prefs.maxres)) {
      write_log(NULL, "Maximum Resolution");
      return;
    }

    x = prefs.zoom / 2 * width;
    y = prefs.zoom / 2 * height;

    write_log(NULL, "Zoom Out");
  }
  else
    g_assert_not_reached();

  xmin -= x;
  xmax += x;
  ymin -= y;
  ymax += y;

  update_ranges();
  clear_pixmap();
  draw_axes();
  formula_foreach((FormulaFunc)draw_formula);
  redraw_event(da, NULL);
}

/* go_event, called when the go button is clicked, draw the formula
 */
static void
go_event(GtkWidget *widget, gpointer data)
{
  GtkWidget *combo;
  gchar *formula;
#if(!((GTK_MAJOR_VERSION == 1) && (GTK_MINOR_VERSION == 0)))
  GList *temp_list;
#endif
  static GList *list = NULL;

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

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

  /* remove the formula from the list if it is already in there somewhere */
#if(!((GTK_MAJOR_VERSION == 1) && (GTK_MINOR_VERSION == 0)))
  temp_list = g_list_find_custom(list, formula, (GCompareFunc)g_strcasecmp);
  if(temp_list)
    list = g_list_remove_link(list, temp_list);
#endif

  /* add the formula to the top of the list */
  list = g_list_prepend(list, strdup(formula));
  gtk_combo_set_popdown_strings(GTK_COMBO(combo), list);

  /* keep the list a reasonable length */
  while(g_list_length(list) > 8)
    g_list_remove_link(list, g_list_last(list));

  redraw_event(da, NULL);
}

/* reset_event,
 */
static void
refresh_graph(GtkWidget *widget, gpointer data)
{
  if(!range_ok(FALSE))
    return;
  update_ranges();
  clear_pixmap();
  draw_axes();
  formula_foreach((FormulaFunc)draw_formula);
  redraw_event(da, NULL);	/* expose entire area */
}

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

static void
entry_changed_event(GtkWidget *widget, gpointer data)
{
  char *entry;

  entry = (char *)data;
  if(!strcmp("xmin", data))
    xmin = atof(gtk_entry_get_text(GTK_ENTRY(xmin_en)));
  else if(!strcmp("xmax", data))
    xmax = atof(gtk_entry_get_text(GTK_ENTRY(xmax_en)));
  else if(!strcmp("ymin", data))
    ymin = atof(gtk_entry_get_text(GTK_ENTRY(ymin_en)));
  else if(!strcmp("ymax", data))
    ymax = atof(gtk_entry_get_text(GTK_ENTRY(ymax_en)));
}

/* new_event, resets zoom, erases all functions
 */
static void
new_event(GtkWidget *widget, gpointer data)
{
  GtkWidget *entry;

  entry = (GtkWidget *)data;

  xmin = prefs.xmin;
  ymin = prefs.ymin;
  xmax = prefs.xmax;
  ymax = prefs.ymax;

  if(prefs.spacing == GEG_RC_DECIMAL)
    set_spacing(widget, "Decimal");
  else if(prefs.spacing == GEG_RC_RADIAN)
    set_spacing(widget, "Radian");
  else
    g_assert_not_reached();

  remove_all_event(NULL, NULL);
  refresh_graph(NULL, NULL);
  clear_log(NULL, NULL);
  write_log(NULL, "New All");
  gtk_entry_set_text(GTK_ENTRY(entry), "");	/* do we want this ? */
  gtk_widget_grab_focus(entry);
}

/* reset_event, resets zoom
 */
static void
reset_event(GtkWidget *widget, gpointer data)
{
  xmin = prefs.xmin;
  ymin = prefs.ymin;
  xmax = prefs.xmax;
  ymax = prefs.ymax;

  /*
  if(prefs.spacing == GEG_RC_DECIMAL)
    set_spacing(widget, "Decimal");
  else if(prefs.spacing == GEG_RC_RADIAN)
    set_spacing(widget, "Radian");
  else
    g_assert_not_reached();
    */

  refresh_graph(NULL, NULL);
  write_log(NULL, "Reset Zoom");
}

/* 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;

  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");
  }
  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);
  }
  else
    g_assert_not_reached();
}

/* set_spacing
 */
static void
set_spacing(GtkWidget *widget, gpointer data)
{
  gchar buf[100];
  spacing = (gchar *)data;
  sprintf(buf, "Using %s spacing", spacing);
  write_log(NULL, buf);
  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]) ||
       !strcmp("-v", argv[i]) || !strcmp("--version", argv[i])) {
      g_print("geg Version "
#ifdef VERSION
	      VERSION
#else
	      "?.?.?"
#endif
	      " by David Bryant\n");
      exit(0);
    }
}

/* pixmap_x, converts the real x-coordinate to its x-coordinate in the pixmap
 */
gint pixmap_x(gdouble x)
{
  return(rint((x - xmin) / (xmax - xmin) * (gdouble)pixmap_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)pixmap_height -
         ((y - ymin) / (ymax - ymin) * (gdouble)pixmap_height));

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

  if(temp < (pixmap_height / 2  - pixmap_height))
    return(pixmap_height / 2 - pixmap_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)pixmap_width) * (xmax - xmin)) + xmin);
}

/* real_y, converts the pixmap y-coordinate to its real y-coordinate
 */
gdouble real_y(gint y)
{
  return((((pixmap_height - y) / (gdouble)pixmap_height) * 
	  (ymax - ymin)) + 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_coef, pi_inc, sub_pi;
  gint x_radix, y_radix, x_count;
  gint x_coef, y_coef, y_count;
  gint i, j;
  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);
  
  range_ok(TRUE);

  gdk_gc_set_foreground(axes_gc, m_colors.axes);
  gdk_gc_set_foreground(numb_gc, m_colors.numb);

  /* draw the horizontal axis */
  gdk_draw_line(pixmap, axes_gc,
  		0               , pixmap_y(0),
  		pixmap_width - 1, pixmap_y(0));
  /* draw the vertical axis */
  gdk_draw_line(pixmap, axes_gc,
                pixmap_x(0), 0,
		pixmap_x(0), pixmap_height - 1);

  /* now the tricky bit... the notches and numbers */

  x_range = (xmax - xmin);
  y_range = (ymax - 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)) * pixmap_width / x_range) > prefs.space) {
      x_coef = 1;
      break;
    }
    if(((2 * pow(10, x_radix)) * pixmap_width / x_range) > prefs.space) {
      x_coef = 2;
      break;
    }
    if(((5 * pow(10, x_radix)) * pixmap_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)) * pixmap_height / y_range) > prefs.space) {
      y_coef = 1;
      break;
    }
    if(((2 * pow(10, y_radix)) * pixmap_height / y_range) > prefs.space) {
      y_coef = 2;
      break;
    }
    if(((5 * pow(10, y_radix)) * pixmap_height / y_range) > prefs.space) {
      y_coef = 5;
      break;
    }
    y_radix++;
  }

  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);

  x = (floor(xmin / x_spacing)) * x_spacing;

  /* draw notch and label */
  if(!strcmp(spacing, "Decimal")) {
    for(i = 0; i <= x_count; i++, x += x_spacing) {
      gdk_draw_line(pixmap, axes_gc,
                    pixmap_x(x), pixmap_y(0) - 2,
		    pixmap_x(x), pixmap_y(0) + 2);

      /* test for origin */
      if(fabs(x) < x_spacing / 2)
	g_string_sprintf(buf_1, "0");
      else {
	g_string_sprintf(buf_2, "%%0.%df", 
			 MAX((gint)ceil(-log10(x_spacing)), 0));
	g_string_sprintf(buf_1, buf_2->str, x);
      }

      gdk_draw_text(pixmap, numb_font, numb_gc,
                    pixmap_x(x) - gdk_string_width(numb_font, buf_1->str) / 2,
		    pixmap_y(0) + gdk_string_height(numb_font, buf_1->str) + 5,
		    buf_1->str, buf_1->len);
    }
  }

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

  for(i = 0; i <= y_count; i++, y += y_spacing) {
    gdk_draw_line(pixmap, axes_gc,
                  pixmap_x(0) - 2, pixmap_y(y),
		  pixmap_x(0) + 2, pixmap_y(y));

    /* test for origin */
    if(fabs(y) < y_spacing / 2)
      g_string_sprintf(buf_1, "0");
    else {
      g_string_sprintf(buf_2, "%%0.%df", MAX((gint)ceil(-log10(y_spacing)), 0));
      g_string_sprintf(buf_1, buf_2->str, y);
    }

    gdk_draw_text(pixmap, numb_font, numb_gc,
                  pixmap_x(0) + 5,
		  pixmap_y(y) + gdk_string_height(numb_font, buf_1->str) / 2,
		  buf_1->str, buf_1->len);
  }

  /* pi values */
  /* what factor of pi to use */
  pi_inc = (gint)ceil((gdouble)prefs.space * x_range / pixmap_width / M_PI);
  x_spacing = pi_inc * M_PI;
  pi_coef = pi_inc * (gint)floor(xmin / (pi_inc * M_PI));
  /* starting point */
  x = pi_coef * M_PI;
  x_count = ceil(x_range / (pi_inc * M_PI));

  sub_pi = (gint)MIN(pow(2, floor(log(x_spacing * pixmap_width / (xmax - xmin) 
				  / prefs.space) / log(2))), 128);

  if(!strcmp(spacing, "Radian")) {
    for(i = 0; i <= x_count; i++, x += x_spacing, pi_coef += pi_inc) {
      gdk_draw_line(pixmap, axes_gc,
                    pixmap_x(x), pixmap_y(0) - 2,
		    pixmap_x(x), pixmap_y(0) + 2);
      switch(pi_coef) {
      case -1:
	g_string_assign(buf_1, "-p");	/* greek-pi in symbol font */
        break;
      case 0:
	g_string_assign(buf_1, "0");
        break;
      case 1:
	g_string_assign(buf_1, "p");
        break;
      default:
	g_string_sprintf(buf_1, "%dp", pi_coef);
	break;
      }
      
      gdk_draw_text(pixmap, numb_font, numb_gc,
                    pixmap_x(x) - gdk_string_width(numb_font, buf_1->str) / 2,
		    pixmap_y(0) + gdk_string_height(numb_font, buf_1->str) + 5,
		    buf_1->str, buf_1->len);

      for(j = 1; j < sub_pi; j++) {
	/* no point drawing off the pixmap */
	if(x + j * x_spacing / sub_pi > xmax + x_spacing / sub_pi)
	  break;

	if(pi_coef == 0)
	  g_string_sprintf(buf_1, "%d/%dp", j, sub_pi);
	if(pi_coef > 0)
	  g_string_sprintf(buf_1, "%d %d/%dp", pi_coef, j, sub_pi);
	if(pi_coef == -1)
	  g_string_sprintf(buf_1, "-%d/%dp", sub_pi - j, sub_pi);
	if(pi_coef < -1)
	  g_string_sprintf(buf_1, "%d %d/%dp", pi_coef + 1, sub_pi - j, sub_pi);

	/* use the color of the axes to write the fractions so they */
	/* are more subtle */
	gdk_draw_text(pixmap, numb_font, axes_gc,
		      pixmap_x(x + (j * x_spacing / sub_pi)) -
		      gdk_string_width(numb_font, buf_1->str) / 2,
		      pixmap_y(0) + 
		      gdk_string_height(numb_font, buf_1->str) + 7,
		      buf_1->str, buf_1->len);
      }
    }
  }

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

/* 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 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);

  switch(event->button) {
  case 1:	/* zoom to the selection */
    /* check for minimum resolution */
    if((x2 - x1 < prefs.minres) ||
       (y2 - y1 < prefs.minres)) {
      write_log(NULL, "Minimum Resolution");
      xmin = x1 - MAX((prefs.minres - (x2 - x1)) / 2, 0);
      xmax = x2 + MAX((prefs.minres - (x2 - x1)) / 2, 0);
      ymin = y1 - MAX((prefs.minres - (y2 - y1)) / 2, 0);
      ymax = y2 + MAX((prefs.minres - (y2 - y1)) / 2, 0);
    }
    else {
      xmin = x1;
      xmax = x2;
      ymin = y1;
      ymax = y2;
    }

    write_log(NULL, "Zoom Selection");
    break;
  case 2:	/* solve function intercepts */
    formula_forall(solve_fint, x1, x2, y1, y2);
    write_log(NULL, "Function Intercepts:-");
    break;
  case 3:	/* solve axes intercepts */
    formula_forall(solve_aint, x1, x2, y1, y2);
    write_log(NULL, "Axis Intercepts:-");
    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  = xmax - xmin;
  gdouble height = ymax - ymin;

  label = (GtkWidget *)data;

  g_string_sprintf(buf_1, "X: %%0.%df, Y: %%0.%df",
                   CLAMP((gint)ceil(-log10(width / pixmap_width)), 0, 6),
                   CLAMP((gint)ceil(-log10(height / pixmap_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, m_colors.sel);
  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)
{
  /* delete the old pixmap if it exists */
  if(pixmap)
    gdk_pixmap_unref(pixmap);

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

  /* create a new pixmap of a size equal to the drawing area */
  pixmap = gdk_pixmap_new(widget->window, pixmap_width, pixmap_height, -1);
  /* clear the pixmap to the user's background color */
  clear_pixmap();
  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, m_colors.back);
  gdk_draw_rectangle(pixmap, back_gc, TRUE, 0, 0,
                     pixmap_width,
		     pixmap_height);
}

/* drawing_init, this function initialises stuff  
 */
void
drawing_init(GtkWidget *widget, gpointer data)
{
  GdkColormap *colormap;
  GdkPixmap *source, *mask;
  GdkColor black, white;
  GtkStyle *style;

  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);
  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;
  gdouble y_temp;
  gint intercept = FALSE;
  gint i, j;
  GString *buf_1 = g_string_new(NULL);
  GString *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++)
    for(j = i + 1; j < nformulas; j++) {
      x_left = x1;
      x_right = x2;
      if((eval_tree(tree[i], x_left) - eval_tree(tree[j], x_left) > 0) ^
	 (eval_tree(tree[i], x_right) - eval_tree(tree[j], x_right) > 0)) {
	while((x_right - x_left) > prefs.minres / 10) {
	  x_middle = (x_left + x_right) / 2;
          if((eval_tree(tree[i], x_left) - eval_tree(tree[j], x_left) > 0) ^
	     (eval_tree(tree[i], x_middle) - eval_tree(tree[j], x_middle) > 0))
	    x_right = x_middle;
	  else
	    x_left = x_middle;
	}
	y_temp = eval_tree(tree[i], x_right);
	if((y1 < y_temp) && (y_temp < y2)) {	/* check its 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_temp);
          write_log(NULL, buf_2->str);
	}
      }
    }
  
  if(intercept == FALSE)
    write_log(NULL, " none found.");

  /* free the lists and trees */
  for(i = 0; i < nformulas; 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;
  gdouble y_temp;
  gint intercept = FALSE;
  gint i;
  GString *buf_1 = g_string_new(NULL);
  GString *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 ++) {
      /* solve the y-axes intercept */
      y_temp = eval_tree(tree[i], 0);
      if((y1 < y_temp) && (y_temp < 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_temp);
	write_log(NULL, buf_2->str);
      }
    }
  }
  
  if((y1 < 0) && (y2 > 0)) {
    /* solve the x-axis intercept */
    /* the bisection method */
    for(i = 0; i < nformulas; i++) {
      x_left = x1;	/* preserve x1 and x2 */
      x_right = x2;
      if((eval_tree(tree[i], x_left) > 0) ^ (eval_tree(tree[i], x_right) > 0)) {
        while((x_right - x_left) > prefs.minres / 10) {
          x_middle = (x_left + x_right) / 2;
          if((eval_tree(tree[i], x_left) > 0) ^ 
	     (eval_tree(tree[i], x_middle) > 0))
	    x_right = x_middle;
          else
	    x_left = x_middle;
        }
	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(NULL, buf_2->str);
      }
    }
  }
  
  if(intercept == FALSE)
    write_log(NULL, " none found.");

  /* free the lists and trees */
  for(i = 0; i < nformulas; 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);
}

/* draw_formula, draws the formula onto the pixmap   
 */
gint
draw_formula(gchar *formula, gint number)
{
  static GdkFont *text_font = NULL;
  static gint v_offset;
  gint i, j;
  gdouble x, y1, y2;
  gdouble x_inc;
  token_list *list;
  parse_tree *tree;

  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);
    return(FALSE);
  }

  if(number == 0)
    v_offset = 0;
  
  v_offset += gdk_string_height(text_font, formula) + 2;

  gdk_gc_set_foreground(func_gc, function_color(number));
  
  gdk_draw_text(pixmap, text_font, func_gc,
                pixmap_width - gdk_string_width(text_font, formula) - 2,
		v_offset,
		formula, strlen(formula));

  x = xmin;
  x_inc = (xmax - xmin) / (gdouble)(pixmap_width * (prefs.interp + 1));

  y2 = eval_tree(tree, x - x_inc);	/* get the ball rolling */

  for(i = 0; i < (pixmap_width * (prefs.interp + 1)); i++) {
    for(j = 0; j < prefs.interp + 1; j++) {
      y1 = y2;
      y2 = eval_tree(tree, x);
      gdk_draw_line(pixmap, func_gc,
                    pixmap_x(x - x_inc), pixmap_y(y1),
		    pixmap_x(x), pixmap_y(y2));
      x += x_inc;
    }
  }

  free_tree(tree);
  free_list(list);
  return(TRUE);
}

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

  return(TRUE);
}


syntax highlighted by Code2HTML, v. 0.9.1