/* app.c * geg, a GTK+ Equation Grapher * David Bryant 1998 */ #ifdef HAVE_CONFIG_H #include #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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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); }