graph: stop matching tooltips if X diff is too large
[fio.git] / gfio.c
CommitLineData
ff1f3280
SC
1/*
2 * gfio - gui front end for fio - the flexible io tester
3 *
4 * Copyright (C) 2012 Stephen M. Cameron <stephenmcameron@gmail.com>
c0187f3b 5 * Copyright (C) 2012 Jens Axboe <axboe@kernel.dk>
ff1f3280
SC
6 *
7 * The license below covers all files distributed with fio unless otherwise
8 * noted in the file itself.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2 as
12 * published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 *
23 */
8232e285 24#include <locale.h>
60f6b330 25#include <malloc.h>
6b79c80c 26#include <string.h>
8232e285 27
5b7573ab 28#include <glib.h>
2fd3bb0e 29#include <cairo.h>
ff1f3280
SC
30#include <gtk/gtk.h>
31
8232e285 32#include "fio.h"
53e0e85d
JA
33#include "gfio.h"
34#include "ghelpers.h"
9af4a244 35#include "goptions.h"
41666588 36#include "gerror.h"
1252d8f2 37#include "gclient.h"
2fd3bb0e 38#include "graph.h"
8232e285 39
63a130b7 40static int gfio_server_running;
8577f4fd 41static unsigned int gfio_graph_limit = 100;
63a130b7 42
1252d8f2
JA
43GdkColor gfio_color_white;
44const char *gfio_graph_font;
3e47bd25 45
f3074008
SC
46typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
47
3e47bd25 48static void connect_clicked(GtkWidget *widget, gpointer data);
f3074008 49static void start_job_clicked(GtkWidget *widget, gpointer data);
b9d2f30a 50static void send_clicked(GtkWidget *widget, gpointer data);
f3074008
SC
51
52static struct button_spec {
53 const char *buttontext;
54 clickfunction f;
014f4024
JA
55 const char *tooltiptext[2];
56 const int start_sensitive;
f3074008 57} buttonspeclist[] = {
53e0e85d
JA
58 {
59 .buttontext = "Connect",
60 .f = connect_clicked,
61 .tooltiptext = { "Disconnect from host", "Connect to host" },
62 .start_sensitive = 1,
63 },
64 {
65 .buttontext = "Send",
66 .f = send_clicked,
67 .tooltiptext = { "Send job description to host", NULL },
68 .start_sensitive = 0,
69 },
70 {
71 .buttontext = "Start Job",
72 .f = start_job_clicked,
73 .tooltiptext = { "Start the current job on the server", NULL },
74 .start_sensitive = 0,
75 },
e0681f3e
JA
76};
77
2f99deb0
JA
78static struct graph *setup_iops_graph(void)
79{
80 struct graph *g;
81
82 g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
d8fbeefb 83 graph_title(g, "IOPS (IOs/sec)");
2f99deb0 84 graph_x_title(g, "Time (secs)");
2f99deb0
JA
85 graph_add_label(g, "Read IOPS");
86 graph_add_label(g, "Write IOPS");
87 graph_set_color(g, "Read IOPS", 0.13, 0.54, 0.13);
88 graph_set_color(g, "Write IOPS", 1.0, 0.0, 0.0);
8577f4fd 89 line_graph_set_data_count_limit(g, gfio_graph_limit);
d8fbeefb 90 graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
2f99deb0
JA
91 return g;
92}
93
94static struct graph *setup_bandwidth_graph(void)
95{
96 struct graph *g;
97
98 g = graph_new(DRAWING_AREA_XDIM / 2.0, DRAWING_AREA_YDIM, gfio_graph_font);
d8fbeefb 99 graph_title(g, "Bandwidth (bytes/sec)");
2f99deb0 100 graph_x_title(g, "Time (secs)");
2f99deb0
JA
101 graph_add_label(g, "Read Bandwidth");
102 graph_add_label(g, "Write Bandwidth");
103 graph_set_color(g, "Read Bandwidth", 0.13, 0.54, 0.13);
104 graph_set_color(g, "Write Bandwidth", 1.0, 0.0, 0.0);
d8fbeefb 105 graph_set_base_offset(g, 1);
2f99deb0 106 line_graph_set_data_count_limit(g, 100);
d8fbeefb 107 graph_add_extra_space(g, 0.0, 0.0, 0.0, 0.0);
2f99deb0
JA
108 return g;
109}
110
111static void setup_graphs(struct gfio_graphs *g)
112{
113 g->iops_graph = setup_iops_graph();
114 g->bandwidth_graph = setup_bandwidth_graph();
115}
116
1252d8f2 117void clear_ge_ui_info(struct gui_entry *ge)
2f99deb0
JA
118{
119 gtk_label_set_text(GTK_LABEL(ge->probe.hostname), "");
120 gtk_label_set_text(GTK_LABEL(ge->probe.os), "");
121 gtk_label_set_text(GTK_LABEL(ge->probe.arch), "");
122 gtk_label_set_text(GTK_LABEL(ge->probe.fio_ver), "");
3863d1ad
JA
123#if 0
124 /* should we empty it... */
2f99deb0 125 gtk_entry_set_text(GTK_ENTRY(ge->eta.name), "");
3863d1ad 126#endif
c80b74b0 127 multitext_update_entry(&ge->eta.iotype, 0, "");
99d633af 128 multitext_update_entry(&ge->eta.bs, 0, "");
c80b74b0
JA
129 multitext_update_entry(&ge->eta.ioengine, 0, "");
130 multitext_update_entry(&ge->eta.iodepth, 0, "");
2f99deb0
JA
131 gtk_entry_set_text(GTK_ENTRY(ge->eta.jobs), "");
132 gtk_entry_set_text(GTK_ENTRY(ge->eta.files), "");
133 gtk_entry_set_text(GTK_ENTRY(ge->eta.read_bw), "");
134 gtk_entry_set_text(GTK_ENTRY(ge->eta.read_iops), "");
135 gtk_entry_set_text(GTK_ENTRY(ge->eta.write_bw), "");
136 gtk_entry_set_text(GTK_ENTRY(ge->eta.write_iops), "");
8663ea65
JA
137}
138
781ccba6
JA
139static void set_menu_entry_text(struct gui *ui, const char *path,
140 const char *text)
141{
142 GtkWidget *w;
143
144 w = gtk_ui_manager_get_widget(ui->uimanager, path);
145 if (w)
146 gtk_menu_item_set_label(GTK_MENU_ITEM(w), text);
147 else
148 fprintf(stderr, "gfio: can't find path %s\n", path);
149}
150
151
152static void set_menu_entry_visible(struct gui *ui, const char *path, int show)
153{
154 GtkWidget *w;
155
156 w = gtk_ui_manager_get_widget(ui->uimanager, path);
157 if (w)
158 gtk_widget_set_sensitive(w, show);
159 else
160 fprintf(stderr, "gfio: can't find path %s\n", path);
161}
162
163static void set_job_menu_visible(struct gui *ui, int visible)
164{
165 set_menu_entry_visible(ui, "/MainMenu/JobMenu", visible);
166}
167
168static void set_view_results_visible(struct gui *ui, int visible)
169{
170 set_menu_entry_visible(ui, "/MainMenu/ViewMenu/Results", visible);
171}
172
014f4024
JA
173static const char *get_button_tooltip(struct button_spec *s, int sensitive)
174{
175 if (s->tooltiptext[sensitive])
176 return s->tooltiptext[sensitive];
177
178 return s->tooltiptext[0];
179}
180
181static GtkWidget *add_button(GtkWidget *buttonbox,
182 struct button_spec *buttonspec, gpointer data)
183{
184 GtkWidget *button = gtk_button_new_with_label(buttonspec->buttontext);
185 gboolean sens = buttonspec->start_sensitive;
186
187 g_signal_connect(button, "clicked", G_CALLBACK(buttonspec->f), data);
188 gtk_box_pack_start(GTK_BOX(buttonbox), button, FALSE, FALSE, 3);
189
190 sens = buttonspec->start_sensitive;
191 gtk_widget_set_tooltip_text(button, get_button_tooltip(buttonspec, sens));
192 gtk_widget_set_sensitive(button, sens);
193
194 return button;
195}
196
197static void add_buttons(struct gui_entry *ge, struct button_spec *buttonlist,
198 int nbuttons)
199{
200 int i;
201
202 for (i = 0; i < nbuttons; i++)
203 ge->button[i] = add_button(ge->buttonbox, &buttonlist[i], ge);
204}
205
85dd01e7
JA
206/*
207 * Update sensitivity of job buttons and job menu items, based on the
208 * state of the client.
209 */
210static void update_button_states(struct gui *ui, struct gui_entry *ge)
211{
212 unsigned int connect_state, send_state, start_state, edit_state;
213 const char *connect_str = NULL;
85dd01e7
JA
214
215 switch (ge->state) {
1252d8f2
JA
216 default:
217 gfio_report_error(ge, "Bad client state: %u\n", ge->state);
85dd01e7 218 /* fall through to new state */
85dd01e7
JA
219 case GE_STATE_NEW:
220 connect_state = 1;
9af4a244 221 edit_state = 1;
85dd01e7
JA
222 connect_str = "Connect";
223 send_state = 0;
224 start_state = 0;
225 break;
226 case GE_STATE_CONNECTED:
227 connect_state = 1;
9af4a244 228 edit_state = 1;
85dd01e7
JA
229 connect_str = "Disconnect";
230 send_state = 1;
231 start_state = 0;
232 break;
233 case GE_STATE_JOB_SENT:
234 connect_state = 1;
9af4a244 235 edit_state = 1;
85dd01e7
JA
236 connect_str = "Disconnect";
237 send_state = 0;
238 start_state = 1;
239 break;
240 case GE_STATE_JOB_STARTED:
241 connect_state = 1;
242 edit_state = 1;
243 connect_str = "Disconnect";
244 send_state = 0;
245 start_state = 1;
246 break;
247 case GE_STATE_JOB_RUNNING:
248 connect_state = 1;
249 edit_state = 0;
250 connect_str = "Disconnect";
251 send_state = 0;
252 start_state = 0;
253 break;
254 case GE_STATE_JOB_DONE:
255 connect_state = 1;
256 edit_state = 0;
257 connect_str = "Connect";
258 send_state = 0;
259 start_state = 0;
260 break;
261 }
262
53e0e85d
JA
263 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_CONNECT], connect_state);
264 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_SEND], send_state);
265 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], start_state);
266 gtk_button_set_label(GTK_BUTTON(ge->button[GFIO_BUTTON_CONNECT]), connect_str);
267 gtk_widget_set_tooltip_text(ge->button[GFIO_BUTTON_CONNECT], get_button_tooltip(&buttonspeclist[GFIO_BUTTON_CONNECT], connect_state));
85dd01e7 268
781ccba6
JA
269 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Connect", connect_state);
270 set_menu_entry_text(ui, "/MainMenu/JobMenu/Connect", connect_str);
85dd01e7 271
781ccba6
JA
272 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Edit job", edit_state);
273 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Send job", send_state);
274 set_menu_entry_visible(ui, "/MainMenu/JobMenu/Start job", start_state);
85dd01e7 275
781ccba6
JA
276 if (ge->client && ge->client->nr_results)
277 set_view_results_visible(ui, 1);
278 else
279 set_view_results_visible(ui, 0);
85dd01e7
JA
280}
281
1252d8f2 282void gfio_set_state(struct gui_entry *ge, unsigned int state)
85dd01e7
JA
283{
284 ge->state = state;
285 update_button_states(ge->ui, ge);
286}
287
9b260bdf
JA
288static void gfio_ui_setup_log(struct gui *ui)
289{
290 GtkTreeSelection *selection;
291 GtkListStore *model;
292 GtkWidget *tree_view;
293
294 model = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING);
295
296 tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
297 gtk_widget_set_can_focus(tree_view, FALSE);
298
299 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
300 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
661f741a
JA
301 g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
302 "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, NULL);
9b260bdf
JA
303
304 tree_view_column(tree_view, 0, "Time", ALIGN_RIGHT | UNSORTABLE);
305 tree_view_column(tree_view, 1, "Host", ALIGN_RIGHT | UNSORTABLE);
306 tree_view_column(tree_view, 2, "Level", ALIGN_RIGHT | UNSORTABLE);
f095d563 307 tree_view_column(tree_view, 3, "Text", ALIGN_LEFT | UNSORTABLE);
9b260bdf
JA
308
309 ui->log_model = model;
310 ui->log_tree = tree_view;
311}
312
1252d8f2
JA
313static gint on_config_drawing_area(GtkWidget *w, GdkEventConfigure *event,
314 gpointer data)
c57f254c 315{
1252d8f2 316 struct gfio_graphs *g = data;
c57f254c 317
1252d8f2
JA
318 graph_set_size(g->iops_graph, w->allocation.width / 2.0, w->allocation.height);
319 graph_set_position(g->iops_graph, w->allocation.width / 2.0, 0.0);
320 graph_set_size(g->bandwidth_graph, w->allocation.width / 2.0, w->allocation.height);
321 graph_set_position(g->bandwidth_graph, 0, 0);
322 return TRUE;
c57f254c
SC
323}
324
1252d8f2 325static void draw_graph(struct graph *g, cairo_t *cr)
a269790c 326{
1252d8f2
JA
327 line_graph_draw(g, cr);
328 cairo_stroke(cr);
329}
a269790c 330
1252d8f2
JA
331static gboolean graph_tooltip(GtkWidget *w, gint x, gint y,
332 gboolean keyboard_mode, GtkTooltip *tooltip,
333 gpointer data)
334{
335 struct gfio_graphs *g = data;
336 const char *text = NULL;
a269790c 337
1252d8f2
JA
338 if (graph_contains_xy(g->iops_graph, x, y))
339 text = graph_find_tooltip(g->iops_graph, x, y);
340 else if (graph_contains_xy(g->bandwidth_graph, x, y))
341 text = graph_find_tooltip(g->bandwidth_graph, x, y);
a269790c 342
1252d8f2
JA
343 if (text) {
344 gtk_tooltip_set_text(tooltip, text);
345 return TRUE;
e0681f3e 346 }
a269790c 347
1252d8f2 348 return FALSE;
a269790c
JA
349}
350
1252d8f2 351static int on_expose_drawing_area(GtkWidget *w, GdkEvent *event, gpointer p)
c57f254c 352{
1252d8f2 353 struct gfio_graphs *g = p;
c57f254c
SC
354 cairo_t *cr;
355
356 cr = gdk_cairo_create(w->window);
1252d8f2
JA
357
358 if (graph_has_tooltips(g->iops_graph) ||
359 graph_has_tooltips(g->bandwidth_graph)) {
c57f254c 360 g_object_set(w, "has-tooltip", TRUE, NULL);
1252d8f2 361 g_signal_connect(w, "query-tooltip", G_CALLBACK(graph_tooltip), g);
c57f254c 362 }
1252d8f2 363
c57f254c 364 cairo_set_source_rgb(cr, 0, 0, 0);
1252d8f2
JA
365 draw_graph(g->iops_graph, cr);
366 draw_graph(g->bandwidth_graph, cr);
c57f254c
SC
367 cairo_destroy(cr);
368
369 return FALSE;
370}
371
1252d8f2
JA
372/*
373 * FIXME: need more handling here
374 */
375static void ge_destroy(struct gui_entry *ge)
c57f254c 376{
1252d8f2 377 struct gfio_client *gc = ge->client;
c57f254c 378
470cdbb7
JA
379 if (gc) {
380 if (gc->client) {
381 if (ge->state >= GE_STATE_CONNECTED)
382 fio_client_terminate(gc->client);
383
384 fio_put_client(gc->client);
385 }
386 free(gc);
a269790c
JA
387 }
388
b98ab718
JA
389 g_hash_table_remove(ge->ui->ge_hash, &ge->page_num);
390
1252d8f2
JA
391 free(ge->job_file);
392 free(ge->host);
1252d8f2 393 free(ge);
a269790c
JA
394}
395
1252d8f2 396static void ge_widget_destroy(GtkWidget *w, gpointer data)
3650a3ca 397{
1252d8f2 398 struct gui_entry *ge = (struct gui_entry *) data;
3650a3ca 399
1252d8f2 400 ge_destroy(ge);
3650a3ca
JA
401}
402
1252d8f2 403static void gfio_quit(struct gui *ui)
3650a3ca 404{
1252d8f2
JA
405 gtk_main_quit();
406}
3650a3ca 407
1252d8f2
JA
408static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
409 __attribute__((unused)) gpointer data)
410{
411 struct gui *ui = (struct gui *) data;
3650a3ca 412
1252d8f2
JA
413 gfio_quit(ui);
414}
3650a3ca 415
1252d8f2
JA
416static void *job_thread(void *arg)
417{
418 struct gui *ui = arg;
3650a3ca 419
1252d8f2
JA
420 ui->handler_running = 1;
421 fio_handle_clients(&gfio_client_ops);
422 ui->handler_running = 0;
423 return NULL;
424}
ca850992 425
1252d8f2
JA
426static int send_job_file(struct gui_entry *ge)
427{
428 struct gfio_client *gc = ge->client;
429 int ret = 0;
ca850992 430
1252d8f2
JA
431 ret = fio_client_send_ini(gc->client, ge->job_file);
432 if (!ret)
433 return 0;
ca850992 434
1252d8f2
JA
435 gfio_report_error(ge, "Failed to send file %s: %s\n", ge->job_file, strerror(-ret));
436 return 1;
437}
ca850992 438
1252d8f2
JA
439static void *server_thread(void *arg)
440{
441 is_backend = 1;
442 gfio_server_running = 1;
443 fio_start_server(NULL);
444 gfio_server_running = 0;
445 return NULL;
446}
ca850992 447
1252d8f2
JA
448static void gfio_start_server(struct gui *ui)
449{
450 if (!gfio_server_running) {
451 gfio_server_running = 1;
452 pthread_create(&ui->server_t, NULL, server_thread, NULL);
453 pthread_detach(ui->server_t);
2b089892 454 }
3650a3ca
JA
455}
456
1252d8f2
JA
457static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
458 gpointer data)
09d574e3 459{
1252d8f2
JA
460 struct gui_entry *ge = data;
461 struct gfio_client *gc = ge->client;
09d574e3 462
1252d8f2
JA
463 if (gc)
464 fio_start_client(gc->client);
09d574e3
JA
465}
466
1252d8f2 467static void file_open(GtkWidget *w, gpointer data);
e5bd1347 468
1252d8f2
JA
469struct connection_widgets
470{
471 GtkWidget *hentry;
472 GtkWidget *combo;
473 GtkWidget *button;
474};
e5bd1347 475
1252d8f2
JA
476static void hostname_cb(GtkEntry *entry, gpointer data)
477{
478 struct connection_widgets *cw = data;
479 int uses_net = 0, is_localhost = 0;
480 const gchar *text;
481 gchar *ctext;
e5bd1347 482
1252d8f2
JA
483 /*
484 * Check whether to display the 'auto start backend' box
485 * or not. Show it if we are a localhost and using network,
486 * or using a socket.
487 */
488 ctext = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw->combo));
489 if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
490 uses_net = 1;
491 g_free(ctext);
e5bd1347 492
1252d8f2
JA
493 if (uses_net) {
494 text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
495 if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
496 !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
497 !strcmp(text, "ip6-loopback"))
498 is_localhost = 1;
499 }
e5bd1347 500
1252d8f2
JA
501 if (!uses_net || is_localhost) {
502 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
503 gtk_widget_set_sensitive(cw->button, 1);
504 } else {
505 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
506 gtk_widget_set_sensitive(cw->button, 0);
507 }
b0500ccb 508}
661f741a 509
b0500ccb
JA
510static int get_connection_details(struct gui_entry *ge)
511{
512 GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
513 struct connection_widgets cw;
514 struct gui *ui = ge->ui;
515 char *typeentry;
2e33101f 516
b0500ccb
JA
517 if (ge->host)
518 return 0;
519
520 dialog = gtk_dialog_new_with_buttons("Connection details",
521 GTK_WINDOW(ui->window),
522 GTK_DIALOG_DESTROY_WITH_PARENT,
523 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
524 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
525
526 frame = gtk_frame_new("Hostname / socket name");
527 /* gtk_dialog_get_content_area() is 2.14 and newer */
528 vbox = GTK_DIALOG(dialog)->vbox;
529 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
530
531 box = gtk_vbox_new(FALSE, 6);
532 gtk_container_add(GTK_CONTAINER(frame), box);
533
534 hbox = gtk_hbox_new(TRUE, 10);
535 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
536 cw.hentry = gtk_entry_new();
537 gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
538 gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
539
540 frame = gtk_frame_new("Port");
541 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
542 box = gtk_vbox_new(FALSE, 10);
543 gtk_container_add(GTK_CONTAINER(frame), box);
544
545 hbox = gtk_hbox_new(TRUE, 4);
546 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
547 pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
548
549 frame = gtk_frame_new("Type");
550 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
551 box = gtk_vbox_new(FALSE, 10);
552 gtk_container_add(GTK_CONTAINER(frame), box);
553
554 hbox = gtk_hbox_new(TRUE, 4);
555 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
556
557 cw.combo = gtk_combo_box_new_text();
558 gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
559 gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
560 gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
561 gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
562
563 gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
564
565 frame = gtk_frame_new("Options");
566 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
567 box = gtk_vbox_new(FALSE, 10);
568 gtk_container_add(GTK_CONTAINER(frame), box);
569
570 hbox = gtk_hbox_new(TRUE, 4);
571 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
572
573 cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
574 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
575 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.");
576 gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
2e33101f 577
b0500ccb
JA
578 /*
579 * Connect edit signal, so we can show/not-show the auto start button
580 */
581 g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
582 g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
2e33101f 583
b0500ccb 584 gtk_widget_show_all(dialog);
2e33101f 585
b0500ccb 586 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1252d8f2 587 gtk_widget_destroy(dialog);
b0500ccb 588 return 1;
1252d8f2 589 }
2e33101f 590
b0500ccb
JA
591 ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
592 ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
2e33101f 593
b0500ccb
JA
594 typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
595 if (!typeentry || !strncmp(typeentry, "IPv4", 4))
596 ge->type = Fio_client_ipv4;
597 else if (!strncmp(typeentry, "IPv6", 4))
598 ge->type = Fio_client_ipv6;
599 else
600 ge->type = Fio_client_socket;
601 g_free(typeentry);
2e33101f 602
b0500ccb 603 ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
2e33101f 604
b0500ccb
JA
605 gtk_widget_destroy(dialog);
606 return 0;
607}
1252d8f2 608
b0500ccb
JA
609static void gfio_set_client(struct gfio_client *gc, struct fio_client *client)
610{
611 gc->client = fio_get_client(client);
612 client->client_data = gc;
613}
614
615static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
616{
617 struct gfio_client *gc;
618
619 gc = malloc(sizeof(*gc));
620 memset(gc, 0, sizeof(*gc));
621 options_default_fill(&gc->o);
622 gc->ge = ge;
623 ge->client = gc;
624 gfio_set_client(gc, client);
625}
626
627static void connect_clicked(GtkWidget *widget, gpointer data)
628{
629 struct gui_entry *ge = data;
630 struct gfio_client *gc = ge->client;
631
632 if (ge->state == GE_STATE_NEW) {
633 int ret;
19998dbc 634
b0500ccb
JA
635 if (!ge->job_file)
636 file_open(widget, ge->ui);
637 if (!ge->job_file)
638 return;
2e33101f 639
b0500ccb 640 gc = ge->client;
2e33101f 641
b0500ccb
JA
642 if (!gc->client) {
643 struct fio_client *client;
19998dbc 644
b0500ccb
JA
645 if (get_connection_details(ge)) {
646 gfio_report_error(ge, "Failed to get connection details\n");
647 return;
1252d8f2 648 }
2e33101f 649
b0500ccb
JA
650 client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
651 if (!client) {
652 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
653 free(ge->host);
654 ge->host = NULL;
655 return;
1252d8f2 656 }
b0500ccb
JA
657 gfio_set_client(gc, client);
658 }
659
660 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
661 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
662 ret = fio_client_connect(gc->client);
663 if (!ret) {
664 if (!ge->ui->handler_running)
665 pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
666 gfio_set_state(ge, GE_STATE_CONNECTED);
1252d8f2 667 } else {
b0500ccb 668 gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
2e33101f 669 }
b0500ccb
JA
670 } else {
671 fio_client_terminate(gc->client);
672 gfio_set_state(ge, GE_STATE_NEW);
673 clear_ge_ui_info(ge);
2e33101f 674 }
b0500ccb 675}
2e33101f 676
b0500ccb
JA
677static void send_clicked(GtkWidget *widget, gpointer data)
678{
679 struct gui_entry *ge = data;
2e33101f 680
b0500ccb
JA
681 if (send_job_file(ge))
682 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
683}
62bc937f 684
b0500ccb 685static GtkWidget *new_client_page(struct gui_entry *ge);
62bc937f 686
b0500ccb
JA
687static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
688{
689 struct gui_entry *ge;
a7a42ce1 690
b0500ccb
JA
691 ge = malloc(sizeof(*ge));
692 memset(ge, 0, sizeof(*ge));
693 ge->state = GE_STATE_NEW;
b0500ccb
JA
694 ge->ui = ui;
695 return ge;
696}
a7a42ce1 697
b0500ccb
JA
698static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
699{
700 struct gui_entry *ge;
a7a42ce1 701
b0500ccb 702 ge = alloc_new_gui_entry(ui);
a7a42ce1 703
b0500ccb
JA
704 ge->vbox = new_client_page(ge);
705 g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
a7a42ce1 706
b0500ccb
JA
707 ge->page_label = gtk_label_new(name);
708 ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
b9f3c7ed 709
b98ab718
JA
710 g_hash_table_insert(ui->ge_hash, &ge->page_num, ge);
711
b0500ccb
JA
712 gtk_widget_show_all(ui->window);
713 return ge;
714}
b9f3c7ed 715
b0500ccb
JA
716static void file_new(GtkWidget *w, gpointer data)
717{
718 struct gui *ui = (struct gui *) data;
719 struct gui_entry *ge;
62bc937f 720
b0500ccb
JA
721 ge = get_new_ge_with_tab(ui, "Untitled");
722 gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
723}
a7a42ce1 724
b0500ccb
JA
725/*
726 * Return the 'ge' corresponding to the tab. If the active tab is the
727 * main tab, open a new tab.
728 */
729static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
730 int *created)
731{
b0500ccb 732 if (!cur_page) {
1252d8f2 733 if (created)
b0500ccb
JA
734 *created = 1;
735 return get_new_ge_with_tab(ui, "Untitled");
736 }
b9f3c7ed 737
b0500ccb
JA
738 if (created)
739 *created = 0;
a7a42ce1 740
b98ab718 741 return g_hash_table_lookup(ui->ge_hash, &cur_page);
b0500ccb 742}
e0681f3e 743
b0500ccb
JA
744static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
745{
746 gint cur_page;
e0681f3e 747
b0500ccb
JA
748 /*
749 * Main tab is tab 0, so any current page other than 0 holds
750 * a ge entry.
751 */
752 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
753 if (cur_page)
754 return get_ge_from_page(ui, cur_page, NULL);
0cf3ece0 755
b0500ccb
JA
756 return NULL;
757}
0cf3ece0 758
b0500ccb
JA
759static void file_close(GtkWidget *w, gpointer data)
760{
761 struct gui *ui = (struct gui *) data;
762 struct gui_entry *ge;
2f99deb0 763
b0500ccb
JA
764 /*
765 * Can't close the main tab
766 */
767 ge = get_ge_from_cur_tab(ui);
768 if (ge) {
769 gtk_widget_destroy(ge->vbox);
770 return;
1252d8f2 771 }
16ce5adf 772
b98ab718 773 if (g_hash_table_size(ui->ge_hash)) {
b0500ccb
JA
774 gfio_report_info(ui, "Error", "The main page view cannot be closed\n");
775 return;
38634cb1
JA
776 }
777
b0500ccb
JA
778 gfio_quit(ui);
779}
2f99deb0 780
b0500ccb
JA
781static void file_add_recent(struct gui *ui, const gchar *uri)
782{
783 GtkRecentData grd;
2f99deb0 784
b0500ccb
JA
785 memset(&grd, 0, sizeof(grd));
786 grd.display_name = strdup("gfio");
787 grd.description = strdup("Fio job file");
788 grd.mime_type = strdup(GFIO_MIME);
789 grd.app_name = strdup(g_get_application_name());
790 grd.app_exec = strdup("gfio %f/%u");
85dd01e7 791
b0500ccb
JA
792 gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
793}
85dd01e7 794
b0500ccb
JA
795static gchar *get_filename_from_uri(const gchar *uri)
796{
797 if (strncmp(uri, "file://", 7))
798 return strdup(uri);
16ce5adf 799
b0500ccb
JA
800 return strdup(uri + 7);
801}
16ce5adf 802
b0500ccb
JA
803static int do_file_open(struct gui_entry *ge, const gchar *uri)
804{
805 struct fio_client *client;
f5c6726e 806
b0500ccb 807 assert(!ge->job_file);
38634cb1 808
b0500ccb 809 ge->job_file = get_filename_from_uri(uri);
38634cb1 810
b0500ccb
JA
811 client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
812 if (client) {
832fde79
JA
813 char *label = strdup(uri);
814
815 basename(label);
816 gtk_label_set_text(GTK_LABEL(ge->page_label), basename(label));
817 free(label);
818
b0500ccb
JA
819 gfio_client_added(ge, client);
820 file_add_recent(ge->ui, uri);
821 return 0;
822 }
38634cb1 823
b0500ccb
JA
824 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
825 free(ge->host);
826 ge->host = NULL;
b98ab718
JA
827 free(ge->job_file);
828 ge->job_file = NULL;
b0500ccb
JA
829 return 1;
830}
38634cb1 831
b0500ccb
JA
832static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
833{
834 struct gui_entry *ge;
835 gint cur_page;
836 int ret, ge_is_new = 0;
2f99deb0 837
b0500ccb
JA
838 /*
839 * Creates new tab if current tab is the main window, or the
840 * current tab already has a client.
841 */
842 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
843 ge = get_ge_from_page(ui, cur_page, &ge_is_new);
844 if (ge->client) {
845 ge = get_new_ge_with_tab(ui, "Untitled");
846 ge_is_new = 1;
38634cb1 847 }
2f99deb0 848
b0500ccb
JA
849 gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
850
851 if (get_connection_details(ge)) {
852 if (ge_is_new)
853 gtk_widget_destroy(ge->vbox);
854
855 return 1;
856 }
a6790906 857
b0500ccb 858 ret = do_file_open(ge, uri);
a6790906 859
b0500ccb
JA
860 if (!ret) {
861 if (ge->server_start)
862 gfio_start_server(ui);
863 } else {
864 if (ge_is_new)
865 gtk_widget_destroy(ge->vbox);
a6790906
JA
866 }
867
b0500ccb
JA
868 return ret;
869}
a6790906 870
b0500ccb
JA
871static void recent_open(GtkAction *action, gpointer data)
872{
873 struct gui *ui = (struct gui *) data;
874 GtkRecentInfo *info;
875 const gchar *uri;
0420ba6a 876
b0500ccb
JA
877 info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
878 uri = gtk_recent_info_get_uri(info);
0420ba6a 879
b0500ccb
JA
880 do_file_open_with_tab(ui, uri);
881}
a7a42ce1 882
b0500ccb
JA
883static void file_open(GtkWidget *w, gpointer data)
884{
885 struct gui *ui = data;
886 GtkWidget *dialog;
887 GtkFileFilter *filter;
888 gchar *filename;
889
890 dialog = gtk_file_chooser_dialog_new("Open File",
891 GTK_WINDOW(ui->window),
892 GTK_FILE_CHOOSER_ACTION_OPEN,
893 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
894 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
895 NULL);
896 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
897
898 filter = gtk_file_filter_new();
899 gtk_file_filter_add_pattern(filter, "*.fio");
900 gtk_file_filter_add_pattern(filter, "*.job");
901 gtk_file_filter_add_pattern(filter, "*.ini");
902 gtk_file_filter_add_mime_type(filter, GFIO_MIME);
903 gtk_file_filter_set_name(filter, "Fio job file");
904 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
905
906 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
907 gtk_widget_destroy(dialog);
908 return;
1252d8f2 909 }
0420ba6a 910
b0500ccb 911 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
0420ba6a 912
b0500ccb 913 gtk_widget_destroy(dialog);
0420ba6a 914
b0500ccb
JA
915 do_file_open_with_tab(ui, filename);
916 g_free(filename);
917}
0420ba6a 918
b0500ccb
JA
919static void file_save(GtkWidget *w, gpointer data)
920{
921 struct gui *ui = data;
922 GtkWidget *dialog;
0420ba6a 923
b0500ccb
JA
924 dialog = gtk_file_chooser_dialog_new("Save File",
925 GTK_WINDOW(ui->window),
926 GTK_FILE_CHOOSER_ACTION_SAVE,
927 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
928 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
929 NULL);
930
931 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
932 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
933
934 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
935 char *filename;
936
937 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
938 // save_job_file(filename);
939 g_free(filename);
0420ba6a 940 }
b0500ccb
JA
941 gtk_widget_destroy(dialog);
942}
0420ba6a 943
9b260bdf
JA
944static void view_log_destroy(GtkWidget *w, gpointer data)
945{
946 struct gui *ui = (struct gui *) data;
947
948 gtk_widget_ref(ui->log_tree);
949 gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
950 gtk_widget_destroy(w);
4cbe7211 951 ui->log_view = NULL;
9b260bdf
JA
952}
953
1252d8f2 954void gfio_view_log(struct gui *ui)
9b260bdf 955{
4cbe7211 956 GtkWidget *win, *scroll, *vbox, *box;
9b260bdf 957
4cbe7211
JA
958 if (ui->log_view)
959 return;
960
961 ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
9b260bdf 962 gtk_window_set_title(GTK_WINDOW(win), "Log");
4cbe7211 963 gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
9b260bdf 964
4cbe7211
JA
965 scroll = gtk_scrolled_window_new(NULL, NULL);
966
967 gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
968
969 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
970
971 box = gtk_hbox_new(TRUE, 0);
972 gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
973 g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
974 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
975
976 vbox = gtk_vbox_new(TRUE, 5);
977 gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
9b260bdf 978
4cbe7211 979 gtk_container_add(GTK_CONTAINER(win), vbox);
9b260bdf
JA
980 gtk_widget_show_all(win);
981}
982
1252d8f2
JA
983static void view_log(GtkWidget *w, gpointer data)
984{
985 struct gui *ui = (struct gui *) data;
986
987 gfio_view_log(ui);
988}
989
85dd01e7
JA
990static void connect_job_entry(GtkWidget *w, gpointer data)
991{
992 struct gui *ui = (struct gui *) data;
993 struct gui_entry *ge;
994
995 ge = get_ge_from_cur_tab(ui);
996 if (ge)
997 connect_clicked(w, ge);
998}
999
1000static void send_job_entry(GtkWidget *w, gpointer data)
1001{
1002 struct gui *ui = (struct gui *) data;
1003 struct gui_entry *ge;
1004
1005 ge = get_ge_from_cur_tab(ui);
1006 if (ge)
1007 send_clicked(w, ge);
85dd01e7
JA
1008}
1009
1010static void edit_job_entry(GtkWidget *w, gpointer data)
16ce5adf 1011{
9af4a244 1012 struct gui *ui = (struct gui *) data;
789f4ccd 1013 struct gui_entry *ge;
9af4a244 1014
789f4ccd
JA
1015 ge = get_ge_from_cur_tab(ui);
1016 if (ge && ge->client)
1017 gopt_get_options_window(ui->window, &ge->client->o);
16ce5adf
JA
1018}
1019
85dd01e7
JA
1020static void start_job_entry(GtkWidget *w, gpointer data)
1021{
1022 struct gui *ui = (struct gui *) data;
1023 struct gui_entry *ge;
1024
1025 ge = get_ge_from_cur_tab(ui);
1026 if (ge)
1027 start_job_clicked(w, ge);
1028}
1029
781ccba6
JA
1030static void view_results(GtkWidget *w, gpointer data)
1031{
1032 struct gui *ui = (struct gui *) data;
1033 struct gfio_client *gc;
1034 struct gui_entry *ge;
1035
1036 ge = get_ge_from_cur_tab(ui);
1037 if (!ge)
1038 return;
1039
1040 if (ge->results_window)
1041 return;
1042
1043 gc = ge->client;
1044 if (gc && gc->nr_results)
1045 gfio_display_end_results(gc);
1046}
1047
8577f4fd
JA
1048static void __update_graph_limits(struct gfio_graphs *g)
1049{
1050 line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
1051 line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
1052}
1053
b98ab718
JA
1054static void ge_update_lim_fn(gpointer key, gpointer value, gpointer data)
1055{
1056 struct gui_entry *ge = (struct gui_entry *) value;
1057
1058 __update_graph_limits(&ge->graphs);
1059}
1060
8577f4fd
JA
1061static void update_graph_limits(void)
1062{
b98ab718 1063 struct gui *ui = &main_ui;
8577f4fd 1064
b98ab718 1065 __update_graph_limits(&ui->graphs);
8577f4fd 1066
b98ab718 1067 g_hash_table_foreach(ui->ge_hash, ge_update_lim_fn, NULL);
8577f4fd
JA
1068}
1069
46974a7d
JA
1070static void preferences(GtkWidget *w, gpointer data)
1071{
f3e8440f 1072 GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
1cf6bca5 1073 GtkWidget *hbox, *spin, *entry, *spin_int;
6e02ad6c 1074 struct gui *ui = (struct gui *) data;
46974a7d
JA
1075 int i;
1076
1077 dialog = gtk_dialog_new_with_buttons("Preferences",
6e02ad6c 1078 GTK_WINDOW(ui->window),
46974a7d
JA
1079 GTK_DIALOG_DESTROY_WITH_PARENT,
1080 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1081 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1082 NULL);
1083
8577f4fd 1084 frame = gtk_frame_new("Graphing");
f3e8440f
JA
1085 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1086 vbox = gtk_vbox_new(FALSE, 6);
1087 gtk_container_add(GTK_CONTAINER(frame), vbox);
1088
1cf6bca5
JA
1089 hbox = gtk_hbox_new(FALSE, 5);
1090 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
1091 entry = gtk_label_new("Font face to use for graph labels");
1092 gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
1093
f3e8440f 1094 font = gtk_font_button_new();
1cf6bca5 1095 gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
f3e8440f 1096
8577f4fd
JA
1097 box = gtk_vbox_new(FALSE, 6);
1098 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1099
1100 hbox = gtk_hbox_new(FALSE, 5);
1cf6bca5 1101 gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
8577f4fd
JA
1102 entry = gtk_label_new("Maximum number of data points in graph (seconds)");
1103 gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1104
c05d9056 1105 spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
8577f4fd 1106
1cf6bca5
JA
1107 box = gtk_vbox_new(FALSE, 6);
1108 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1109
1110 hbox = gtk_hbox_new(FALSE, 5);
1111 gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1112 entry = gtk_label_new("Client ETA request interval (msec)");
1113 gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1114
1115 spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
a31d9fa4
JA
1116 frame = gtk_frame_new("Debug logging");
1117 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1118 vbox = gtk_vbox_new(FALSE, 6);
1119 gtk_container_add(GTK_CONTAINER(frame), vbox);
1120
1121 box = gtk_hbox_new(FALSE, 6);
1122 gtk_container_add(GTK_CONTAINER(vbox), box);
1123
1124 buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1125
1126 for (i = 0; i < FD_DEBUG_MAX; i++) {
1127 if (i == 7) {
1128 box = gtk_hbox_new(FALSE, 6);
1129 gtk_container_add(GTK_CONTAINER(vbox), box);
1130 }
1131
1132
1133 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
1134 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1135 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1136 }
1137
46974a7d
JA
1138 gtk_widget_show_all(dialog);
1139
1140 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1141 gtk_widget_destroy(dialog);
1142 return;
1143 }
1144
1145 for (i = 0; i < FD_DEBUG_MAX; i++) {
1146 int set;
1147
1148 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1149 if (set)
1150 fio_debug |= (1UL << i);
1151 }
1152
f3e8440f 1153 gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
8577f4fd
JA
1154 gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
1155 update_graph_limits();
1cf6bca5 1156 gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
8577f4fd 1157
46974a7d
JA
1158 gtk_widget_destroy(dialog);
1159}
1160
0420ba6a
JA
1161static void about_dialog(GtkWidget *w, gpointer data)
1162{
81e4ea6e
JA
1163 const char *authors[] = {
1164 "Jens Axboe <axboe@kernel.dk>",
1165 "Stephen Carmeron <stephenmcameron@gmail.com>",
1166 NULL
1167 };
84a72ed3
JA
1168 const char *license[] = {
1169 "Fio is free software; you can redistribute it and/or modify "
1170 "it under the terms of the GNU General Public License as published by "
1171 "the Free Software Foundation; either version 2 of the License, or "
1172 "(at your option) any later version.\n",
1173 "Fio is distributed in the hope that it will be useful, "
1174 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
1175 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
1176 "GNU General Public License for more details.\n",
1177 "You should have received a copy of the GNU General Public License "
1178 "along with Fio; if not, write to the Free Software Foundation, Inc., "
1179 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n"
1180 };
1181 char *license_trans;
1182
1183 license_trans = g_strconcat(license[0], "\n", license[1], "\n",
1184 license[2], "\n", NULL);
81e4ea6e 1185
0420ba6a
JA
1186 gtk_show_about_dialog(NULL,
1187 "program-name", "gfio",
1188 "comments", "Gtk2 UI for fio",
84a72ed3 1189 "license", license_trans,
81e4ea6e
JA
1190 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
1191 "authors", authors,
0420ba6a 1192 "version", fio_version_string,
81e4ea6e 1193 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
0420ba6a
JA
1194 "logo-icon-name", "fio",
1195 /* Must be last: */
81e4ea6e 1196 "wrap-license", TRUE,
0420ba6a 1197 NULL);
84a72ed3 1198
2f99deb0 1199 g_free(license_trans);
0420ba6a
JA
1200}
1201
1202static GtkActionEntry menu_items[] = {
46974a7d 1203 { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
9b260bdf 1204 { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
16ce5adf 1205 { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
46974a7d 1206 { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
2f99deb0 1207 { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
16ce5adf 1208 { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
46974a7d
JA
1209 { "OpenFile", GTK_STOCK_OPEN, NULL, "<Control>O", NULL, G_CALLBACK(file_open) },
1210 { "SaveFile", GTK_STOCK_SAVE, NULL, "<Control>S", NULL, G_CALLBACK(file_save) },
1211 { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
9b260bdf 1212 { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
781ccba6 1213 { "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
bc271d87 1214 { "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
85dd01e7
JA
1215 { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
1216 { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
1217 { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
46974a7d
JA
1218 { "Quit", GTK_STOCK_QUIT, NULL, "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1219 { "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_dialog) },
0420ba6a 1220};
3e47bd25 1221static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
0420ba6a
JA
1222
1223static const gchar *ui_string = " \
1224 <ui> \
1225 <menubar name=\"MainMenu\"> \
1226 <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
2f99deb0 1227 <menuitem name=\"New\" action=\"NewFile\" /> \
bf64138b 1228 <menuitem name=\"Open\" action=\"OpenFile\" /> \
16ce5adf 1229 <menuitem name=\"Close\" action=\"CloseFile\" /> \
2f99deb0 1230 <separator name=\"Separator1\"/> \
0420ba6a 1231 <menuitem name=\"Save\" action=\"SaveFile\" /> \
46974a7d 1232 <separator name=\"Separator2\"/> \
2f99deb0
JA
1233 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1234 <separator name=\"Separator3\"/> \
261f21d0
JA
1235 <placeholder name=\"FileRecentFiles\"/> \
1236 <separator name=\"Separator4\"/> \
0420ba6a
JA
1237 <menuitem name=\"Quit\" action=\"Quit\" /> \
1238 </menu> \
16ce5adf 1239 <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
85dd01e7 1240 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
261f21d0 1241 <separator name=\"Separator5\"/> \
85dd01e7
JA
1242 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
1243 <menuitem name=\"Send job\" action=\"SendJob\" /> \
261f21d0 1244 <separator name=\"Separator6\"/> \
85dd01e7 1245 <menuitem name=\"Start job\" action=\"StartJob\" /> \
16ce5adf 1246 </menu>\
9b260bdf 1247 <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
781ccba6
JA
1248 <menuitem name=\"Results\" action=\"ViewResults\" /> \
1249 <separator name=\"Separator7\"/> \
9b260bdf
JA
1250 <menuitem name=\"Log\" action=\"ViewLog\" /> \
1251 </menu>\
0420ba6a
JA
1252 <menu name=\"Help\" action=\"HelpMenuAction\"> \
1253 <menuitem name=\"About\" action=\"About\" /> \
1254 </menu> \
1255 </menubar> \
1256 </ui> \
1257";
1258
4cbe7211
JA
1259static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
1260 struct gui *ui)
0420ba6a 1261{
ca664f49 1262 GtkActionGroup *action_group;
0420ba6a
JA
1263 GError *error = 0;
1264
1265 action_group = gtk_action_group_new("Menu");
4cbe7211 1266 gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
0420ba6a
JA
1267
1268 gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1269 gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1270
1271 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
02421e69 1272
0420ba6a
JA
1273 return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1274}
1275
1276void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1252d8f2 1277 GtkWidget *vbox, GtkUIManager *ui_manager)
0420ba6a 1278{
1252d8f2 1279 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
0420ba6a
JA
1280}
1281
c80b74b0
JA
1282static void combo_entry_changed(GtkComboBox *box, gpointer data)
1283{
1284 struct gui_entry *ge = (struct gui_entry *) data;
1285 gint index;
1286
1287 index = gtk_combo_box_get_active(box);
1288
1289 multitext_set_entry(&ge->eta.iotype, index);
99d633af 1290 multitext_set_entry(&ge->eta.bs, index);
c80b74b0
JA
1291 multitext_set_entry(&ge->eta.ioengine, index);
1292 multitext_set_entry(&ge->eta.iodepth, index);
1293}
1294
1295static void combo_entry_destroy(GtkWidget *widget, gpointer data)
1296{
1297 struct gui_entry *ge = (struct gui_entry *) data;
1298
1299 multitext_free(&ge->eta.iotype);
99d633af 1300 multitext_free(&ge->eta.bs);
c80b74b0
JA
1301 multitext_free(&ge->eta.ioengine);
1302 multitext_free(&ge->eta.iodepth);
1303}
1304
2f99deb0 1305static GtkWidget *new_client_page(struct gui_entry *ge)
ff1f3280 1306{
2f99deb0 1307 GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
65476336 1308 GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
0420ba6a 1309
2f99deb0 1310 main_vbox = gtk_vbox_new(FALSE, 3);
45032dd8 1311
65476336
JA
1312 top_align = gtk_alignment_new(0, 0, 1, 0);
1313 top_vbox = gtk_vbox_new(FALSE, 3);
1314 gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1315 gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2f99deb0
JA
1316
1317 probe = gtk_frame_new("Job");
1318 gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1319 probe_frame = gtk_vbox_new(FALSE, 3);
1320 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1321
1322 probe_box = gtk_hbox_new(FALSE, 3);
1323 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1324 ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1325 ge->probe.os = new_info_label_in_frame(probe_box, "OS");
1326 ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1327 ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1328
1329 probe_box = gtk_hbox_new(FALSE, 3);
1330 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1331
3863d1ad 1332 ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
c80b74b0
JA
1333 g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
1334 g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
1335 ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
99d633af 1336 ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write)");
c80b74b0
JA
1337 ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
1338 ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
2f99deb0
JA
1339 ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
1340 ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
1341
1342 probe_box = gtk_hbox_new(FALSE, 3);
1343 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1344 ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1345 ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1346 ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1347 ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1348
1349 /*
1350 * Only add this if we have a commit rate
2839f0c6 1351 */
2f99deb0
JA
1352#if 0
1353 probe_box = gtk_hbox_new(FALSE, 3);
1354 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2839f0c6 1355
2f99deb0
JA
1356 ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1357 ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
ff1f3280 1358
2f99deb0
JA
1359 ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1360 ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1361#endif
ff1f3280 1362
2f99deb0
JA
1363 /*
1364 * Set up a drawing area and IOPS and bandwidth graphs
1365 */
2f99deb0 1366 ge->graphs.drawing_area = gtk_drawing_area_new();
2f99deb0 1367 gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
57f9d28e 1368 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1252d8f2 1369 gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
2f99deb0
JA
1370 g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
1371 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
1372 g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
1373 G_CALLBACK(on_config_drawing_area), &ge->graphs);
65476336
JA
1374 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1375 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2f99deb0 1376 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
65476336 1377 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2f99deb0 1378 ge->graphs.drawing_area);
65476336 1379 gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
04cc6b77 1380
2f99deb0
JA
1381 setup_graphs(&ge->graphs);
1382
1383 /*
1384 * Set up alignments for widgets at the bottom of ui,
1385 * align bottom left, expand horizontally but not vertically
1386 */
65476336 1387 bottom_align = gtk_alignment_new(0, 1, 1, 0);
2f99deb0 1388 ge->buttonbox = gtk_hbox_new(FALSE, 0);
65476336
JA
1389 gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
1390 gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2f99deb0
JA
1391
1392 add_buttons(ge, buttonspeclist, ARRAYSIZE(buttonspeclist));
0420ba6a 1393
c36f98d9 1394 /*
2f99deb0
JA
1395 * Set up thread status progress bar
1396 */
1397 ge->thread_status_pb = gtk_progress_bar_new();
1398 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1399 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
1400 gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
1401
1402
1403 return main_vbox;
1404}
1405
1406static GtkWidget *new_main_page(struct gui *ui)
1407{
1408 GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
65476336 1409 GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2f99deb0
JA
1410
1411 main_vbox = gtk_vbox_new(FALSE, 3);
1412
1413 /*
1414 * Set up alignments for widgets at the top of ui,
c36f98d9
SC
1415 * align top left, expand horizontally but not vertically
1416 */
65476336
JA
1417 top_align = gtk_alignment_new(0, 0, 1, 0);
1418 top_vbox = gtk_vbox_new(FALSE, 0);
1419 gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1420 gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
c36f98d9 1421
2f99deb0
JA
1422 probe = gtk_frame_new("Run statistics");
1423 gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
843ad237
JA
1424 probe_frame = gtk_vbox_new(FALSE, 3);
1425 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1426
1427 probe_box = gtk_hbox_new(FALSE, 3);
2f99deb0 1428 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
3863d1ad 1429 ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
ca850992
JA
1430 ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1431 ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1432 ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1433 ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
3e47bd25 1434
807f9971
JA
1435 /*
1436 * Only add this if we have a commit rate
1437 */
1438#if 0
3e47bd25
JA
1439 probe_box = gtk_hbox_new(FALSE, 3);
1440 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
807f9971
JA
1441
1442 ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1443 ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1444
3e47bd25
JA
1445 ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1446 ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
807f9971 1447#endif
3e47bd25 1448
736f2dff 1449 /*
2fd3bb0e 1450 * Set up a drawing area and IOPS and bandwidth graphs
736f2dff 1451 */
2f99deb0 1452 ui->graphs.drawing_area = gtk_drawing_area_new();
2f99deb0 1453 gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
57f9d28e 1454 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1252d8f2 1455 gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
2f99deb0
JA
1456 g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
1457 G_CALLBACK(on_expose_drawing_area), &ui->graphs);
1458 g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
1459 G_CALLBACK(on_config_drawing_area), &ui->graphs);
65476336
JA
1460 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1461 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
736f2dff 1462 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
65476336 1463 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2f99deb0 1464 ui->graphs.drawing_area);
65476336 1465 gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
e164534f 1466 TRUE, TRUE, 0);
736f2dff 1467
2f99deb0 1468 setup_graphs(&ui->graphs);
2fd3bb0e 1469
c36f98d9
SC
1470 /*
1471 * Set up alignments for widgets at the bottom of ui,
1472 * align bottom left, expand horizontally but not vertically
1473 */
65476336 1474 bottom_align = gtk_alignment_new(0, 1, 1, 0);
c36f98d9 1475 ui->buttonbox = gtk_hbox_new(FALSE, 0);
65476336
JA
1476 gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
1477 gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
c36f98d9 1478
3ec62ec4
JA
1479 /*
1480 * Set up thread status progress bar
1481 */
1482 ui->thread_status_pb = gtk_progress_bar_new();
1483 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
8663ea65 1484 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
3ec62ec4
JA
1485 gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1486
2f99deb0
JA
1487 return main_vbox;
1488}
1489
1490static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
1491 guint page, gpointer data)
1492
1493{
02421e69 1494 struct gui *ui = (struct gui *) data;
85dd01e7
JA
1495 struct gui_entry *ge;
1496
1497 if (!page) {
1498 set_job_menu_visible(ui, 0);
781ccba6 1499 set_view_results_visible(ui, 0);
85dd01e7
JA
1500 return TRUE;
1501 }
1502
1503 set_job_menu_visible(ui, 1);
6e02ad6c 1504 ge = get_ge_from_page(ui, page, NULL);
85dd01e7
JA
1505 if (ge)
1506 update_button_states(ui, ge);
02421e69 1507
2f99deb0
JA
1508 return TRUE;
1509}
1510
38634cb1
JA
1511static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
1512{
1513 time_t time_a = gtk_recent_info_get_visited(a);
1514 time_t time_b = gtk_recent_info_get_visited(b);
1515
1516 return time_b - time_a;
1517}
1518
1519static void add_recent_file_items(struct gui *ui)
1520{
1521 const gchar *gfio = g_get_application_name();
1522 GList *items, *item;
1523 int i = 0;
1524
1525 if (ui->recent_ui_id) {
1526 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
1527 gtk_ui_manager_ensure_update(ui->uimanager);
1528 }
1529 ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
1530
1531 if (ui->actiongroup) {
1532 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
1533 g_object_unref(ui->actiongroup);
1534 }
1535 ui->actiongroup = gtk_action_group_new("RecentFileActions");
1536
1537 gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
1538
1539 items = gtk_recent_manager_get_items(ui->recentmanager);
1540 items = g_list_sort(items, (GCompareFunc) compare_recent_items);
1541
1542 for (item = items; item && item->data; item = g_list_next(item)) {
1543 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
1544 gchar *action_name;
1545 const gchar *label;
1546 GtkAction *action;
1547
1548 if (!gtk_recent_info_has_application(info, gfio))
1549 continue;
1550
1551 /*
1552 * We only support local files for now
1553 */
1554 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
1555 continue;
1556
1557 action_name = g_strdup_printf("RecentFile%u", i++);
1558 label = gtk_recent_info_get_display_name(info);
1559
1560 action = g_object_new(GTK_TYPE_ACTION,
1561 "name", action_name,
1562 "label", label, NULL);
1563
1564 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
1565 gtk_recent_info_ref(info),
1566 (GDestroyNotify) gtk_recent_info_unref);
1567
1568
1569 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
1570
1571 gtk_action_group_add_action(ui->actiongroup, action);
1572 g_object_unref(action);
1573
1574 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
1575 "/MainMenu/FileMenu/FileRecentFiles",
1576 label, action_name,
1577 GTK_UI_MANAGER_MENUITEM, FALSE);
1578
1579 g_free(action_name);
1580
1581 if (i == 8)
1582 break;
1583 }
1584
1585 g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
1586 g_list_free(items);
1587}
1588
a6790906 1589static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
6e02ad6c
JA
1590 gint x, gint y, GtkSelectionData *seldata,
1591 guint info, guint time, gpointer *data)
a6790906 1592{
6e02ad6c 1593 struct gui *ui = (struct gui *) data;
a6790906
JA
1594 gchar **uris;
1595 GtkWidget *source;
a6790906
JA
1596
1597 source = gtk_drag_get_source_widget(ctx);
1598 if (source && widget == gtk_widget_get_toplevel(source)) {
1599 gtk_drag_finish(ctx, FALSE, FALSE, time);
1600 return;
1601 }
1602
6e02ad6c 1603 uris = gtk_selection_data_get_uris(seldata);
a6790906
JA
1604 if (!uris) {
1605 gtk_drag_finish(ctx, FALSE, FALSE, time);
1606 return;
1607 }
1608
0cf3ece0
JA
1609 if (uris[0])
1610 do_file_open_with_tab(ui, uris[0]);
a6790906
JA
1611
1612 gtk_drag_finish(ctx, TRUE, FALSE, time);
1613 g_strfreev(uris);
1614}
1615
2f99deb0
JA
1616static void init_ui(int *argc, char **argv[], struct gui *ui)
1617{
1618 GtkSettings *settings;
02421e69 1619 GtkWidget *vbox;
2f99deb0
JA
1620
1621 /* Magical g*thread incantation, you just need this thread stuff.
1622 * Without it, the update that happens in gfio_update_thread_status
1623 * doesn't really happen in a timely fashion, you need expose events
1624 */
1625 if (!g_thread_supported())
1626 g_thread_init(NULL);
1627 gdk_threads_init();
1628
1629 gtk_init(argc, argv);
1630 settings = gtk_settings_get_default();
1631 gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1632 g_type_init();
1252d8f2 1633 gdk_color_parse("white", &gfio_color_white);
2f99deb0
JA
1634
1635 ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
814479d5 1636 gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
2f99deb0
JA
1637 gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
1638
6e02ad6c
JA
1639 g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), ui);
1640 g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), ui);
2f99deb0
JA
1641
1642 ui->vbox = gtk_vbox_new(FALSE, 0);
1643 gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
1644
02421e69
JA
1645 ui->uimanager = gtk_ui_manager_new();
1646 ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
1647 gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
2f99deb0 1648
38634cb1
JA
1649 ui->recentmanager = gtk_recent_manager_get_default();
1650 add_recent_file_items(ui);
1651
2f99deb0
JA
1652 ui->notebook = gtk_notebook_new();
1653 g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
b870c31b 1654 gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
0aa928c4 1655 gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
2f99deb0
JA
1656 gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
1657
1658 vbox = new_main_page(ui);
0cf3ece0 1659 gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 1, GDK_ACTION_COPY);
a6790906
JA
1660 gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
1661 g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
2f99deb0
JA
1662
1663 gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
1664
9b260bdf 1665 gfio_ui_setup_log(ui);
3ec62ec4 1666
ff1f3280
SC
1667 gtk_widget_show_all(ui->window);
1668}
1669
8232e285 1670int main(int argc, char *argv[], char *envp[])
ff1f3280 1671{
8232e285
SC
1672 if (initialize_fio(envp))
1673 return 1;
0420ba6a
JA
1674 if (fio_init_options())
1675 return 1;
a1820207 1676
2f99deb0 1677 memset(&main_ui, 0, sizeof(main_ui));
b98ab718 1678 main_ui.ge_hash = g_hash_table_new(g_int_hash, g_int_equal);
2f99deb0
JA
1679
1680 init_ui(&argc, &argv, &main_ui);
5b7573ab 1681
2839f0c6 1682 gdk_threads_enter();
ff1f3280 1683 gtk_main();
2839f0c6 1684 gdk_threads_leave();
b98ab718
JA
1685
1686 g_hash_table_destroy(main_ui.ge_hash);
ff1f3280
SC
1687 return 0;
1688}