goptions: use gopt->gjv for option hiding
[fio.git] / gfio.c
diff --git a/gfio.c b/gfio.c
index d1d1313bdaefc279c5010bff27431138541a7da8..544d96aae03763ff89e3ba78459d47a854ad24d6 100644 (file)
--- a/gfio.c
+++ b/gfio.c
@@ -41,7 +41,7 @@ static int gfio_server_running;
 static unsigned int gfio_graph_limit = 100;
 
 GdkColor gfio_color_white;
-const char *gfio_graph_font;
+const char *gfio_graph_font = GRAPH_DEFAULT_FONT;
 
 typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
 
@@ -75,43 +75,45 @@ static struct button_spec {
        },
 };
 
-static struct graph *setup_iops_graph(void)
+static void setup_iops_graph(struct gfio_graphs *gg)
 {
        struct graph *g;
 
        g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
        graph_title(g, "IOPS (IOs/sec)");
        graph_x_title(g, "Time (secs)");
-       graph_add_label(g, "Read IOPS");
-       graph_add_label(g, "Write IOPS");
-       graph_set_color(g, "Read IOPS", 0.13, 0.54, 0.13);
-       graph_set_color(g, "Write IOPS", 1.0, 0.0, 0.0);
+       gg->read_iops = graph_add_label(g, "Read IOPS");
+       gg->write_iops = graph_add_label(g, "Write IOPS");
+       graph_set_color(g, gg->read_iops, 0.13, 0.54, 0.13);
+       graph_set_color(g, gg->write_iops, 1.0, 0.0, 0.0);
        line_graph_set_data_count_limit(g, gfio_graph_limit);
        graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
-       return g;
+       graph_set_graph_all_zeroes(g, 0);
+       gg->iops_graph = g;
 }
 
-static struct graph *setup_bandwidth_graph(void)
+static void setup_bandwidth_graph(struct gfio_graphs *gg)
 {
        struct graph *g;
 
        g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
        graph_title(g, "Bandwidth (bytes/sec)");
        graph_x_title(g, "Time (secs)");
-       graph_add_label(g, "Read Bandwidth");
-       graph_add_label(g, "Write Bandwidth");
-       graph_set_color(g, "Read Bandwidth", 0.13, 0.54, 0.13);
-       graph_set_color(g, "Write Bandwidth", 1.0, 0.0, 0.0);
+       gg->read_bw = graph_add_label(g, "Read Bandwidth");
+       gg->write_bw = graph_add_label(g, "Write Bandwidth");
+       graph_set_color(g, gg->read_bw, 0.13, 0.54, 0.13);
+       graph_set_color(g, gg->write_bw, 1.0, 0.0, 0.0);
        graph_set_base_offset(g, 1);
        line_graph_set_data_count_limit(g, 100);
        graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
-       return g;
+       graph_set_graph_all_zeroes(g, 0);
+       gg->bandwidth_graph = g;
 }
 
 static void setup_graphs(struct gfio_graphs *g)
 {
-       g->iops_graph = setup_iops_graph();
-       g->bandwidth_graph = setup_bandwidth_graph();
+       setup_iops_graph(g);
+       setup_bandwidth_graph(g);
 }
 
 void clear_ge_ui_info(struct gui_entry *ge)
@@ -376,16 +378,20 @@ static void ge_destroy(struct gui_entry *ge)
 {
        struct gfio_client *gc = ge->client;
 
-       if (gc && gc->client) {
-               if (ge->state >= GE_STATE_CONNECTED)
-                       fio_client_terminate(gc->client);
+       if (gc) {
+               if (gc->client) {
+                       if (ge->state >= GE_STATE_CONNECTED)
+                               fio_client_terminate(gc->client);
 
-               fio_put_client(gc->client);
+                       fio_put_client(gc->client);
+               }
+               free(gc);
        }
 
+       g_hash_table_remove(ge->ui->ge_hash, &ge->page_num);
+
        free(ge->job_file);
        free(ge->host);
-       flist_del(&ge->list);
        free(ge);
 }
 
@@ -398,13 +404,6 @@ static void ge_widget_destroy(GtkWidget *w, gpointer data)
 
 static void gfio_quit(struct gui *ui)
 {
-       struct gui_entry *ge;
-
-       while (!flist_empty(&ui->list)) {
-               ge = flist_entry(ui->list.next, struct gui_entry, list);
-               ge_destroy(ge);
-       }
-
         gtk_main_quit();
 }
 
@@ -431,6 +430,18 @@ static int send_job_file(struct gui_entry *ge)
        struct gfio_client *gc = ge->client;
        int ret = 0;
 
+       /*
+        * Prune old options, we are expecting the return options
+        * when the job file is parsed remotely and returned to us.
+        */
+       while (!flist_empty(&gc->o_list)) {
+               struct gfio_client_options *gco;
+
+               gco = flist_entry(gc->o_list.next, struct gfio_client_options, list);
+               flist_del(&gco->list);
+               free(gco);
+       }
+
        ret = fio_client_send_ini(gc->client, ge->job_file);
        if (!ret)
                return 0;
@@ -508,442 +519,451 @@ static void hostname_cb(GtkEntry *entry, gpointer data)
                gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
                gtk_widget_set_sensitive(cw->button, 0);
        }
-       }
+}
 
-       static int get_connection_details(struct gui_entry *ge)
-       {
-               GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
-               struct connection_widgets cw;
-               struct gui *ui = ge->ui;
-               char *typeentry;
-
-               if (ge->host)
-                       return 0;
-
-               dialog = gtk_dialog_new_with_buttons("Connection details",
-                               GTK_WINDOW(ui->window),
-                               GTK_DIALOG_DESTROY_WITH_PARENT,
-                               GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
-                               GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
-
-               frame = gtk_frame_new("Hostname / socket name");
-               /* gtk_dialog_get_content_area() is 2.14 and newer */
-               vbox = GTK_DIALOG(dialog)->vbox;
-               gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
-
-               box = gtk_vbox_new(FALSE, 6);
-               gtk_container_add(GTK_CONTAINER(frame), box);
-
-               hbox = gtk_hbox_new(TRUE, 10);
-               gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
-               cw.hentry = gtk_entry_new();
-               gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
-               gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
-
-               frame = gtk_frame_new("Port");
-               gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
-               box = gtk_vbox_new(FALSE, 10);
-               gtk_container_add(GTK_CONTAINER(frame), box);
-
-               hbox = gtk_hbox_new(TRUE, 4);
-               gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
-               pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
-
-               frame = gtk_frame_new("Type");
-               gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
-               box = gtk_vbox_new(FALSE, 10);
-               gtk_container_add(GTK_CONTAINER(frame), box);
-
-               hbox = gtk_hbox_new(TRUE, 4);
-               gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
-
-               cw.combo = gtk_combo_box_new_text();
-               gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
-               gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
-               gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
-               gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
-
-               gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
-
-               frame = gtk_frame_new("Options");
-               gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
-               box = gtk_vbox_new(FALSE, 10);
-               gtk_container_add(GTK_CONTAINER(frame), box);
-
-               hbox = gtk_hbox_new(TRUE, 4);
-               gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
-
-               cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
-               gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
-               gtk_widget_set_tooltip_text(cw.button, "When running fio locally, it is necessary to have the backend running on the same system. If this is checked, gfio will start the backend automatically for you if it isn't already running.");
-               gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
+static int get_connection_details(struct gui_entry *ge)
+{
+       GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
+       struct connection_widgets cw;
+       struct gui *ui = ge->ui;
+       char *typeentry;
 
-               /*
-                * Connect edit signal, so we can show/not-show the auto start button
-                */
-               g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
-               g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
+       if (ge->host)
+               return 0;
 
-               gtk_widget_show_all(dialog);
+       dialog = gtk_dialog_new_with_buttons("Connection details",
+                       GTK_WINDOW(ui->window),
+                       GTK_DIALOG_DESTROY_WITH_PARENT,
+                       GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+                       GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
 
-               if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
-                       gtk_widget_destroy(dialog);
-                       return 1;
-               }
+       frame = gtk_frame_new("Hostname / socket name");
+       /* gtk_dialog_get_content_area() is 2.14 and newer */
+       vbox = GTK_DIALOG(dialog)->vbox;
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+
+       box = gtk_vbox_new(FALSE, 6);
+       gtk_container_add(GTK_CONTAINER(frame), box);
+
+       hbox = gtk_hbox_new(TRUE, 10);
+       gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
+       cw.hentry = gtk_entry_new();
+       gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
+       gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
+
+       frame = gtk_frame_new("Port");
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+       box = gtk_vbox_new(FALSE, 10);
+       gtk_container_add(GTK_CONTAINER(frame), box);
+
+       hbox = gtk_hbox_new(TRUE, 4);
+       gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
+       pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
+
+       frame = gtk_frame_new("Type");
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+       box = gtk_vbox_new(FALSE, 10);
+       gtk_container_add(GTK_CONTAINER(frame), box);
+
+       hbox = gtk_hbox_new(TRUE, 4);
+       gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
 
-               ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
-               ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
+       cw.combo = gtk_combo_box_new_text();
+       gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
+       gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
+       gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
+       gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
 
-               typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
-               if (!typeentry || !strncmp(typeentry, "IPv4", 4))
-                       ge->type = Fio_client_ipv4;
-               else if (!strncmp(typeentry, "IPv6", 4))
-                       ge->type = Fio_client_ipv6;
-               else
-                       ge->type = Fio_client_socket;
-               g_free(typeentry);
+       gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
 
-               ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
+       frame = gtk_frame_new("Options");
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
+       box = gtk_vbox_new(FALSE, 10);
+       gtk_container_add(GTK_CONTAINER(frame), box);
 
+       hbox = gtk_hbox_new(TRUE, 4);
+       gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
+
+       cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
+       gtk_widget_set_tooltip_text(cw.button, "When running fio locally, it is necessary to have the backend running on the same system. If this is checked, gfio will start the backend automatically for you if it isn't already running.");
+       gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
+
+       /*
+        * Connect edit signal, so we can show/not-show the auto start button
+        */
+       g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
+       g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
+
+       gtk_widget_show_all(dialog);
+
+       if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
                gtk_widget_destroy(dialog);
-               return 0;
+               return 1;
        }
 
-       static void gfio_set_client(struct gfio_client *gc, struct fio_client *client)
-       {
-               gc->client = fio_get_client(client);
-               client->client_data = gc;
-       }
+       ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
+       ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
 
-       static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
-       {
-               struct gfio_client *gc;
-
-               gc = malloc(sizeof(*gc));
-               memset(gc, 0, sizeof(*gc));
-               options_default_fill(&gc->o);
-               gc->ge = ge;
-               ge->client = gc;
-               gfio_set_client(gc, client);
-       }
+       typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
+       if (!typeentry || !strncmp(typeentry, "IPv4", 4))
+               ge->type = Fio_client_ipv4;
+       else if (!strncmp(typeentry, "IPv6", 4))
+               ge->type = Fio_client_ipv6;
+       else
+               ge->type = Fio_client_socket;
+       g_free(typeentry);
 
-       static void connect_clicked(GtkWidget *widget, gpointer data)
-       {
-               struct gui_entry *ge = data;
-               struct gfio_client *gc = ge->client;
+       ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
 
-               if (ge->state == GE_STATE_NEW) {
-                       int ret;
+       gtk_widget_destroy(dialog);
+       return 0;
+}
 
-                       if (!ge->job_file)
-                               file_open(widget, ge->ui);
-                       if (!ge->job_file)
-                               return;
+static void gfio_set_client(struct gfio_client *gc, struct fio_client *client)
+{
+       gc->client = fio_get_client(client);
+       client->client_data = gc;
+}
 
-                       gc = ge->client;
+static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
+{
+       struct gfio_client_options *gco;
+       struct gfio_client *gc;
 
-                       if (!gc->client) {
-                               struct fio_client *client;
+       gc = calloc(1, sizeof(*gc));
+       INIT_FLIST_HEAD(&gc->o_list);
+       gc->ge = ge;
+       ge->client = gc;
+       gfio_set_client(gc, client);
 
-                               if (get_connection_details(ge)) {
-                                       gfio_report_error(ge, "Failed to get connection details\n");
-                                       return;
-                               }
+       /*
+        * Just add a default set of options, need to consider how best
+        * to handle this
+        */
+       gco = calloc(1, sizeof(*gco));
+       INIT_FLIST_HEAD(&gco->list);
+       options_default_fill(&gco->o);
+       flist_add_tail(&gco->list, &gc->o_list);
+       gc->o_list_nr++;
+}
 
-                               client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
-                               if (!client) {
-                                       gfio_report_error(ge, "Failed to add client %s\n", ge->host);
-                                       free(ge->host);
-                                       ge->host = NULL;
-                                       return;
-                               }
-                               gfio_set_client(gc, client);
+static void connect_clicked(GtkWidget *widget, gpointer data)
+{
+       struct gui_entry *ge = data;
+       struct gfio_client *gc = ge->client;
+
+       if (ge->state == GE_STATE_NEW) {
+               int ret;
+
+               if (!ge->job_file)
+                       file_open(widget, ge->ui);
+               if (!ge->job_file)
+                       return;
+
+               gc = ge->client;
+
+               if (!gc->client) {
+                       struct fio_client *client;
+
+                       if (get_connection_details(ge)) {
+                               gfio_report_error(ge, "Failed to get connection details\n");
+                               return;
                        }
 
-                       gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
-                       gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
-                       ret = fio_client_connect(gc->client);
-                       if (!ret) {
-                               if (!ge->ui->handler_running)
-                                       pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
-                               gfio_set_state(ge, GE_STATE_CONNECTED);
-                       } else {
-                               gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
+                       client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
+                       if (!client) {
+                               gfio_report_error(ge, "Failed to add client %s\n", ge->host);
+                               free(ge->host);
+                               ge->host = NULL;
+                               return;
                        }
+                       gfio_set_client(gc, client);
+               }
+
+               gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
+               gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
+               ret = fio_client_connect(gc->client);
+               if (!ret) {
+                       if (!ge->ui->handler_running)
+                               pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
+                       gfio_set_state(ge, GE_STATE_CONNECTED);
                } else {
-                       fio_client_terminate(gc->client);
-                       gfio_set_state(ge, GE_STATE_NEW);
-                       clear_ge_ui_info(ge);
+                       gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
                }
+       } else {
+               fio_client_terminate(gc->client);
+               gfio_set_state(ge, GE_STATE_NEW);
+               clear_ge_ui_info(ge);
        }
+}
 
-       static void send_clicked(GtkWidget *widget, gpointer data)
-       {
-               struct gui_entry *ge = data;
+static void send_clicked(GtkWidget *widget, gpointer data)
+{
+       struct gui_entry *ge = data;
 
-               if (send_job_file(ge))
-                       gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
-       }
+       if (send_job_file(ge))
+               gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
+}
 
-       static GtkWidget *new_client_page(struct gui_entry *ge);
+static GtkWidget *new_client_page(struct gui_entry *ge);
 
-       static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
-       {
-               struct gui_entry *ge;
-
-               ge = malloc(sizeof(*ge));
-               memset(ge, 0, sizeof(*ge));
-               ge->state = GE_STATE_NEW;
-               INIT_FLIST_HEAD(&ge->list);
-               flist_add_tail(&ge->list, &ui->list);
-               ge->ui = ui;
-               return ge;
-       }
+static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
+{
+       struct gui_entry *ge;
 
-       static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
-       {
-               struct gui_entry *ge;
+       ge = malloc(sizeof(*ge));
+       memset(ge, 0, sizeof(*ge));
+       ge->state = GE_STATE_NEW;
+       ge->ui = ui;
+       return ge;
+}
 
-               ge = alloc_new_gui_entry(ui);
+static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
+{
+       struct gui_entry *ge;
 
-               ge->vbox = new_client_page(ge);
-               g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
+       ge = alloc_new_gui_entry(ui);
 
-               ge->page_label = gtk_label_new(name);
-               ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
+       ge->vbox = new_client_page(ge);
+       g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
 
-               gtk_widget_show_all(ui->window);
-               return ge;
-       }
+       ge->page_label = gtk_label_new(name);
+       ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
 
-       static void file_new(GtkWidget *w, gpointer data)
-       {
-               struct gui *ui = (struct gui *) data;
-               struct gui_entry *ge;
+       g_hash_table_insert(ui->ge_hash, &ge->page_num, ge);
 
-               ge = get_new_ge_with_tab(ui, "Untitled");
-               gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
+       gtk_widget_show_all(ui->window);
+       return ge;
+}
+
+static void file_new(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
+       struct gui_entry *ge;
+
+       ge = get_new_ge_with_tab(ui, "Untitled");
+       gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
+}
+
+/*
+ * Return the 'ge' corresponding to the tab. If the active tab is the
+ * main tab, open a new tab.
+ */
+static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
+                                         int *created)
+{
+       if (!cur_page) {
+               if (created)
+                       *created = 1;
+               return get_new_ge_with_tab(ui, "Untitled");
        }
 
+       if (created)
+               *created = 0;
+
+       return g_hash_table_lookup(ui->ge_hash, &cur_page);
+}
+
+static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
+{
+       gint cur_page;
+
        /*
-        * Return the 'ge' corresponding to the tab. If the active tab is the
-        * main tab, open a new tab.
+        * Main tab is tab 0, so any current page other than 0 holds
+        * a ge entry.
         */
-       static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
-                                                 int *created)
-       {
-               struct flist_head *entry;
-               struct gui_entry *ge;
+       cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
+       if (cur_page)
+               return get_ge_from_page(ui, cur_page, NULL);
 
-               if (!cur_page) {
-                       if (created)
-                               *created = 1;
-                       return get_new_ge_with_tab(ui, "Untitled");
-               }
+       return NULL;
+}
 
-               if (created)
-                       *created = 0;
+static void file_close(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
+       struct gui_entry *ge;
 
-               flist_for_each(entry, &ui->list) {
-                       ge = flist_entry(entry, struct gui_entry, list);
-                       if (ge->page_num == cur_page)
-                               return ge;
-               }
+       /*
+        * Can't close the main tab
+        */
+       ge = get_ge_from_cur_tab(ui);
+       if (ge) {
+               gtk_widget_destroy(ge->vbox);
+               return;
+       }
 
-               return NULL;
+       if (g_hash_table_size(ui->ge_hash)) {
+               gfio_report_info(ui, "Error", "The main page view cannot be closed\n");
+               return;
        }
 
-       static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
-       {
-               gint cur_page;
+       gfio_quit(ui);
+}
 
-               /*
-                * Main tab is tab 0, so any current page other than 0 holds
-                * a ge entry.
-                */
-               cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
-               if (cur_page)
-                       return get_ge_from_page(ui, cur_page, NULL);
+static void file_add_recent(struct gui *ui, const gchar *uri)
+{
+       GtkRecentData grd;
 
-               return NULL;
-       }
+       memset(&grd, 0, sizeof(grd));
+       grd.display_name = strdup("gfio");
+       grd.description = strdup("Fio job file");
+       grd.mime_type = strdup(GFIO_MIME);
+       grd.app_name = strdup(g_get_application_name());
+       grd.app_exec = strdup("gfio %f/%u");
 
-       static void file_close(GtkWidget *w, gpointer data)
-       {
-               struct gui *ui = (struct gui *) data;
-               struct gui_entry *ge;
+       gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
+}
 
-               /*
-                * Can't close the main tab
-                */
-               ge = get_ge_from_cur_tab(ui);
-               if (ge) {
-                       gtk_widget_destroy(ge->vbox);
-                       return;
-               }
+static gchar *get_filename_from_uri(const gchar *uri)
+{
+       if (strncmp(uri, "file://", 7))
+               return strdup(uri);
 
-               if (!flist_empty(&ui->list)) {
-                       gfio_report_info(ui, "Error", "The main page view cannot be closed\n");
-                       return;
-               }
+       return strdup(uri + 7);
+}
 
-               gfio_quit(ui);
-       }
+static int do_file_open(struct gui_entry *ge, const gchar *uri)
+{
+       struct fio_client *client;
 
-       static void file_add_recent(struct gui *ui, const gchar *uri)
-       {
-               GtkRecentData grd;
+       assert(!ge->job_file);
 
-               memset(&grd, 0, sizeof(grd));
-               grd.display_name = strdup("gfio");
-               grd.description = strdup("Fio job file");
-               grd.mime_type = strdup(GFIO_MIME);
-               grd.app_name = strdup(g_get_application_name());
-               grd.app_exec = strdup("gfio %f/%u");
+       ge->job_file = get_filename_from_uri(uri);
 
-               gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
-       }
+       client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
+       if (client) {
+               char *label = strdup(uri);
 
-       static gchar *get_filename_from_uri(const gchar *uri)
-       {
-               if (strncmp(uri, "file://", 7))
-                       return strdup(uri);
+               basename(label);
+               gtk_label_set_text(GTK_LABEL(ge->page_label), basename(label));
+               free(label);
 
-               return strdup(uri + 7);
+               gfio_client_added(ge, client);
+               file_add_recent(ge->ui, uri);
+               return 0;
        }
 
-       static int do_file_open(struct gui_entry *ge, const gchar *uri)
-       {
-               struct fio_client *client;
+       gfio_report_error(ge, "Failed to add client %s\n", ge->host);
+       free(ge->host);
+       ge->host = NULL;
+       free(ge->job_file);
+       ge->job_file = NULL;
+       return 1;
+}
 
-               assert(!ge->job_file);
+static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
+{
+       struct gui_entry *ge;
+       gint cur_page;
+       int ret, ge_is_new = 0;
 
-               ge->job_file = get_filename_from_uri(uri);
+       /*
+        * Creates new tab if current tab is the main window, or the
+        * current tab already has a client.
+        */
+       cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
+       ge = get_ge_from_page(ui, cur_page, &ge_is_new);
+       if (ge->client) {
+               ge = get_new_ge_with_tab(ui, "Untitled");
+               ge_is_new = 1;
+       }
 
-               client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
-               if (client) {
-                       gfio_client_added(ge, client);
-                       file_add_recent(ge->ui, uri);
-                       return 0;
-               }
+       gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
 
-               gfio_report_error(ge, "Failed to add client %s\n", ge->host);
-               free(ge->host);
-               ge->host = NULL;
+       if (get_connection_details(ge)) {
+               if (ge_is_new)
+                       gtk_widget_destroy(ge->vbox);
+                       
                return 1;
        }
 
-       static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
-       {
-               struct gui_entry *ge;
-               gint cur_page;
-               int ret, ge_is_new = 0;
+       ret = do_file_open(ge, uri);
 
-               /*
-                * Creates new tab if current tab is the main window, or the
-                * current tab already has a client.
-                */
-               cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
-               ge = get_ge_from_page(ui, cur_page, &ge_is_new);
-               if (ge->client) {
-                       ge = get_new_ge_with_tab(ui, "Untitled");
-                       ge_is_new = 1;
-               }
+       if (!ret) {
+               if (ge->server_start)
+                       gfio_start_server(ui);
+       } else {
+               if (ge_is_new)
+                       gtk_widget_destroy(ge->vbox);
+       }
 
-               gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
+       return ret;
+}
 
-               if (get_connection_details(ge)) {
-                       if (ge_is_new)
-                               gtk_widget_destroy(ge->vbox);
-                               
-                       return 1;
-               }
+static void recent_open(GtkAction *action, gpointer data)
+{
+       struct gui *ui = (struct gui *) data;
+       GtkRecentInfo *info;
+       const gchar *uri;
 
-               ret = do_file_open(ge, uri);
+       info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
+       uri = gtk_recent_info_get_uri(info);
 
-               if (!ret) {
-                       if (ge->server_start)
-                               gfio_start_server(ui);
-               } else {
-                       if (ge_is_new)
-                               gtk_widget_destroy(ge->vbox);
-               }
+       do_file_open_with_tab(ui, uri);
+}
 
-               return ret;
-       }
+static void file_open(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = data;
+       GtkWidget *dialog;
+       GtkFileFilter *filter;
+       gchar *filename;
 
-       static void recent_open(GtkAction *action, gpointer data)
-       {
-               struct gui *ui = (struct gui *) data;
-               GtkRecentInfo *info;
-               const gchar *uri;
+       dialog = gtk_file_chooser_dialog_new("Open File",
+               GTK_WINDOW(ui->window),
+               GTK_FILE_CHOOSER_ACTION_OPEN,
+               GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+               GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+               NULL);
+       gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
 
-               info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
-               uri = gtk_recent_info_get_uri(info);
+       filter = gtk_file_filter_new();
+       gtk_file_filter_add_pattern(filter, "*.fio");
+       gtk_file_filter_add_pattern(filter, "*.job");
+       gtk_file_filter_add_pattern(filter, "*.ini");
+       gtk_file_filter_add_mime_type(filter, GFIO_MIME);
+       gtk_file_filter_set_name(filter, "Fio job file");
+       gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
 
-               do_file_open_with_tab(ui, uri);
+       if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
+               gtk_widget_destroy(dialog);
+               return;
        }
 
-       static void file_open(GtkWidget *w, gpointer data)
-       {
-               struct gui *ui = data;
-               GtkWidget *dialog;
-               GtkFileFilter *filter;
-               gchar *filename;
+       filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
 
-               dialog = gtk_file_chooser_dialog_new("Open File",
-                       GTK_WINDOW(ui->window),
-                       GTK_FILE_CHOOSER_ACTION_OPEN,
-                       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-                       GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
-                       NULL);
-               gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
-
-               filter = gtk_file_filter_new();
-               gtk_file_filter_add_pattern(filter, "*.fio");
-               gtk_file_filter_add_pattern(filter, "*.job");
-               gtk_file_filter_add_pattern(filter, "*.ini");
-               gtk_file_filter_add_mime_type(filter, GFIO_MIME);
-               gtk_file_filter_set_name(filter, "Fio job file");
-               gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
-
-               if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
-                       gtk_widget_destroy(dialog);
-                       return;
-               }
-
-               filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
-
-               gtk_widget_destroy(dialog);
+       gtk_widget_destroy(dialog);
 
-               do_file_open_with_tab(ui, filename);
-               g_free(filename);
-       }
+       do_file_open_with_tab(ui, filename);
+       g_free(filename);
+}
 
-       static void file_save(GtkWidget *w, gpointer data)
-       {
-               struct gui *ui = data;
-               GtkWidget *dialog;
+static void file_save(GtkWidget *w, gpointer data)
+{
+       struct gui *ui = data;
+       GtkWidget *dialog;
 
-               dialog = gtk_file_chooser_dialog_new("Save File",
-                       GTK_WINDOW(ui->window),
-                       GTK_FILE_CHOOSER_ACTION_SAVE,
-                       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-                       GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
-                       NULL);
+       dialog = gtk_file_chooser_dialog_new("Save File",
+               GTK_WINDOW(ui->window),
+               GTK_FILE_CHOOSER_ACTION_SAVE,
+               GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+               GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+               NULL);
 
-               gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
-               gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
+       gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
+       gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
 
-               if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
-                       char *filename;
+       if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+               char *filename;
 
-                       filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
-                       // save_job_file(filename);
-                       g_free(filename);
-               }
-               gtk_widget_destroy(dialog);
+               filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+               // save_job_file(filename);
+               g_free(filename);
        }
+       gtk_widget_destroy(dialog);
+}
 
 static void view_log_destroy(GtkWidget *w, gpointer data)
 {
@@ -1018,7 +1038,7 @@ static void edit_job_entry(GtkWidget *w, gpointer data)
 
        ge = get_ge_from_cur_tab(ui);
        if (ge && ge->client)
-               gopt_get_options_window(ui->window, &ge->client->o);
+               gopt_get_options_window(ui->window, ge->client);
 }
 
 static void start_job_entry(GtkWidget *w, gpointer data)
@@ -1049,23 +1069,38 @@ static void view_results(GtkWidget *w, gpointer data)
                gfio_display_end_results(gc);
 }
 
-static void __update_graph_limits(struct gfio_graphs *g)
+static void __update_graph_settings(struct gfio_graphs *g)
 {
        line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
+       graph_set_font(g->iops_graph, gfio_graph_font);
        line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
+       graph_set_font(g->bandwidth_graph, gfio_graph_font);
+}
+
+static void ge_update_settings_fn(gpointer key, gpointer value, gpointer data)
+{
+       struct gui_entry *ge = (struct gui_entry *) value;
+       GdkEvent *ev;
+
+       __update_graph_settings(&ge->graphs);
+
+       ev = gdk_event_new(GDK_EXPOSE);
+       g_signal_emit_by_name(G_OBJECT(ge->graphs.drawing_area), "expose_event", GTK_WIDGET(ge->graphs.drawing_area), ev, &ge->graphs);
+       gdk_event_free(ev);
 }
 
 static void update_graph_limits(void)
 {
-       struct flist_head *entry;
-       struct gui_entry *ge;
+       struct gui *ui = &main_ui;
+       GdkEvent *ev;
 
-       __update_graph_limits(&main_ui.graphs);
+       __update_graph_settings(&ui->graphs);
 
-       flist_for_each(entry, &main_ui.list) {
-               ge = flist_entry(entry, struct gui_entry, list);
-               __update_graph_limits(&ge->graphs);
-       }
+       ev = gdk_event_new(GDK_EXPOSE);
+       g_signal_emit_by_name(G_OBJECT(ui->graphs.drawing_area), "expose_event", GTK_WIDGET(ui->graphs.drawing_area), ev, &ui->graphs);
+       gdk_event_free(ev);
+
+       g_hash_table_foreach(ui->ge_hash, ge_update_settings_fn, NULL);
 }
 
 static void preferences(GtkWidget *w, gpointer data)
@@ -1092,7 +1127,7 @@ static void preferences(GtkWidget *w, gpointer data)
        entry = gtk_label_new("Font face to use for graph labels");
        gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
 
-       font = gtk_font_button_new();
+       font = gtk_font_button_new_with_font(gfio_graph_font);
        gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
 
        box = gtk_vbox_new(FALSE, 6);
@@ -1390,7 +1425,7 @@ static GtkWidget *new_client_page(struct gui_entry *ge)
        gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
        gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
 
-       add_buttons(ge, buttonspeclist, ARRAYSIZE(buttonspeclist));
+       add_buttons(ge, buttonspeclist, ARRAY_SIZE(buttonspeclist));
 
        /*
         * Set up thread status progress bar
@@ -1675,13 +1710,19 @@ int main(int argc, char *argv[], char *envp[])
        if (fio_init_options())
                return 1;
 
+       gopt_init();
+
        memset(&main_ui, 0, sizeof(main_ui));
-       INIT_FLIST_HEAD(&main_ui.list);
+       main_ui.ge_hash = g_hash_table_new(g_int_hash, g_int_equal);
 
        init_ui(&argc, &argv, &main_ui);
 
        gdk_threads_enter();
        gtk_main();
        gdk_threads_leave();
+
+       g_hash_table_destroy(main_ui.ge_hash);
+
+       gopt_exit();
        return 0;
 }