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