gfio: fix gc leak
[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
1252d8f2
JA
389 free(ge->job_file);
390 free(ge->host);
391 flist_del(&ge->list);
392 free(ge);
a269790c
JA
393}
394
1252d8f2 395static void ge_widget_destroy(GtkWidget *w, gpointer data)
3650a3ca 396{
1252d8f2 397 struct gui_entry *ge = (struct gui_entry *) data;
3650a3ca 398
1252d8f2 399 ge_destroy(ge);
3650a3ca
JA
400}
401
1252d8f2 402static void gfio_quit(struct gui *ui)
3650a3ca 403{
1252d8f2 404 struct gui_entry *ge;
3650a3ca 405
1252d8f2
JA
406 while (!flist_empty(&ui->list)) {
407 ge = flist_entry(ui->list.next, struct gui_entry, list);
408 ge_destroy(ge);
409 }
3650a3ca 410
1252d8f2
JA
411 gtk_main_quit();
412}
3650a3ca 413
1252d8f2
JA
414static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
415 __attribute__((unused)) gpointer data)
416{
417 struct gui *ui = (struct gui *) data;
3650a3ca 418
1252d8f2
JA
419 gfio_quit(ui);
420}
3650a3ca 421
1252d8f2
JA
422static void *job_thread(void *arg)
423{
424 struct gui *ui = arg;
3650a3ca 425
1252d8f2
JA
426 ui->handler_running = 1;
427 fio_handle_clients(&gfio_client_ops);
428 ui->handler_running = 0;
429 return NULL;
430}
ca850992 431
1252d8f2
JA
432static int send_job_file(struct gui_entry *ge)
433{
434 struct gfio_client *gc = ge->client;
435 int ret = 0;
ca850992 436
1252d8f2
JA
437 ret = fio_client_send_ini(gc->client, ge->job_file);
438 if (!ret)
439 return 0;
ca850992 440
1252d8f2
JA
441 gfio_report_error(ge, "Failed to send file %s: %s\n", ge->job_file, strerror(-ret));
442 return 1;
443}
ca850992 444
1252d8f2
JA
445static void *server_thread(void *arg)
446{
447 is_backend = 1;
448 gfio_server_running = 1;
449 fio_start_server(NULL);
450 gfio_server_running = 0;
451 return NULL;
452}
ca850992 453
1252d8f2
JA
454static void gfio_start_server(struct gui *ui)
455{
456 if (!gfio_server_running) {
457 gfio_server_running = 1;
458 pthread_create(&ui->server_t, NULL, server_thread, NULL);
459 pthread_detach(ui->server_t);
2b089892 460 }
3650a3ca
JA
461}
462
1252d8f2
JA
463static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
464 gpointer data)
09d574e3 465{
1252d8f2
JA
466 struct gui_entry *ge = data;
467 struct gfio_client *gc = ge->client;
09d574e3 468
1252d8f2
JA
469 if (gc)
470 fio_start_client(gc->client);
09d574e3
JA
471}
472
1252d8f2 473static void file_open(GtkWidget *w, gpointer data);
e5bd1347 474
1252d8f2
JA
475struct connection_widgets
476{
477 GtkWidget *hentry;
478 GtkWidget *combo;
479 GtkWidget *button;
480};
e5bd1347 481
1252d8f2
JA
482static void hostname_cb(GtkEntry *entry, gpointer data)
483{
484 struct connection_widgets *cw = data;
485 int uses_net = 0, is_localhost = 0;
486 const gchar *text;
487 gchar *ctext;
e5bd1347 488
1252d8f2
JA
489 /*
490 * Check whether to display the 'auto start backend' box
491 * or not. Show it if we are a localhost and using network,
492 * or using a socket.
493 */
494 ctext = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw->combo));
495 if (!ctext || !strncmp(ctext, "IPv4", 4) || !strncmp(ctext, "IPv6", 4))
496 uses_net = 1;
497 g_free(ctext);
e5bd1347 498
1252d8f2
JA
499 if (uses_net) {
500 text = gtk_entry_get_text(GTK_ENTRY(cw->hentry));
501 if (!strcmp(text, "127.0.0.1") || !strcmp(text, "localhost") ||
502 !strcmp(text, "::1") || !strcmp(text, "ip6-localhost") ||
503 !strcmp(text, "ip6-loopback"))
504 is_localhost = 1;
505 }
e5bd1347 506
1252d8f2
JA
507 if (!uses_net || is_localhost) {
508 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 1);
509 gtk_widget_set_sensitive(cw->button, 1);
510 } else {
511 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw->button), 0);
512 gtk_widget_set_sensitive(cw->button, 0);
513 }
b0500ccb 514}
661f741a 515
b0500ccb
JA
516static int get_connection_details(struct gui_entry *ge)
517{
518 GtkWidget *dialog, *box, *vbox, *hbox, *frame, *pentry;
519 struct connection_widgets cw;
520 struct gui *ui = ge->ui;
521 char *typeentry;
2e33101f 522
b0500ccb
JA
523 if (ge->host)
524 return 0;
525
526 dialog = gtk_dialog_new_with_buttons("Connection details",
527 GTK_WINDOW(ui->window),
528 GTK_DIALOG_DESTROY_WITH_PARENT,
529 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
530 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
531
532 frame = gtk_frame_new("Hostname / socket name");
533 /* gtk_dialog_get_content_area() is 2.14 and newer */
534 vbox = GTK_DIALOG(dialog)->vbox;
535 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
536
537 box = gtk_vbox_new(FALSE, 6);
538 gtk_container_add(GTK_CONTAINER(frame), box);
539
540 hbox = gtk_hbox_new(TRUE, 10);
541 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
542 cw.hentry = gtk_entry_new();
543 gtk_entry_set_text(GTK_ENTRY(cw.hentry), "localhost");
544 gtk_box_pack_start(GTK_BOX(hbox), cw.hentry, TRUE, TRUE, 0);
545
546 frame = gtk_frame_new("Port");
547 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
548 box = gtk_vbox_new(FALSE, 10);
549 gtk_container_add(GTK_CONTAINER(frame), box);
550
551 hbox = gtk_hbox_new(TRUE, 4);
552 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
553 pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
554
555 frame = gtk_frame_new("Type");
556 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
557 box = gtk_vbox_new(FALSE, 10);
558 gtk_container_add(GTK_CONTAINER(frame), box);
559
560 hbox = gtk_hbox_new(TRUE, 4);
561 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
562
563 cw.combo = gtk_combo_box_new_text();
564 gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv4");
565 gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "IPv6");
566 gtk_combo_box_append_text(GTK_COMBO_BOX(cw.combo), "local socket");
567 gtk_combo_box_set_active(GTK_COMBO_BOX(cw.combo), 0);
568
569 gtk_container_add(GTK_CONTAINER(hbox), cw.combo);
570
571 frame = gtk_frame_new("Options");
572 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
573 box = gtk_vbox_new(FALSE, 10);
574 gtk_container_add(GTK_CONTAINER(frame), box);
575
576 hbox = gtk_hbox_new(TRUE, 4);
577 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
578
579 cw.button = gtk_check_button_new_with_label("Auto-spawn fio backend");
580 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cw.button), 1);
581 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.");
582 gtk_box_pack_start(GTK_BOX(hbox), cw.button, FALSE, FALSE, 6);
2e33101f 583
b0500ccb
JA
584 /*
585 * Connect edit signal, so we can show/not-show the auto start button
586 */
587 g_signal_connect(GTK_OBJECT(cw.hentry), "changed", G_CALLBACK(hostname_cb), &cw);
588 g_signal_connect(GTK_OBJECT(cw.combo), "changed", G_CALLBACK(hostname_cb), &cw);
2e33101f 589
b0500ccb 590 gtk_widget_show_all(dialog);
2e33101f 591
b0500ccb 592 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1252d8f2 593 gtk_widget_destroy(dialog);
b0500ccb 594 return 1;
1252d8f2 595 }
2e33101f 596
b0500ccb
JA
597 ge->host = strdup(gtk_entry_get_text(GTK_ENTRY(cw.hentry)));
598 ge->port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
2e33101f 599
b0500ccb
JA
600 typeentry = gtk_combo_box_get_active_text(GTK_COMBO_BOX(cw.combo));
601 if (!typeentry || !strncmp(typeentry, "IPv4", 4))
602 ge->type = Fio_client_ipv4;
603 else if (!strncmp(typeentry, "IPv6", 4))
604 ge->type = Fio_client_ipv6;
605 else
606 ge->type = Fio_client_socket;
607 g_free(typeentry);
2e33101f 608
b0500ccb 609 ge->server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cw.button));
2e33101f 610
b0500ccb
JA
611 gtk_widget_destroy(dialog);
612 return 0;
613}
1252d8f2 614
b0500ccb
JA
615static void gfio_set_client(struct gfio_client *gc, struct fio_client *client)
616{
617 gc->client = fio_get_client(client);
618 client->client_data = gc;
619}
620
621static void gfio_client_added(struct gui_entry *ge, struct fio_client *client)
622{
623 struct gfio_client *gc;
624
625 gc = malloc(sizeof(*gc));
626 memset(gc, 0, sizeof(*gc));
627 options_default_fill(&gc->o);
628 gc->ge = ge;
629 ge->client = gc;
630 gfio_set_client(gc, client);
631}
632
633static void connect_clicked(GtkWidget *widget, gpointer data)
634{
635 struct gui_entry *ge = data;
636 struct gfio_client *gc = ge->client;
637
638 if (ge->state == GE_STATE_NEW) {
639 int ret;
19998dbc 640
b0500ccb
JA
641 if (!ge->job_file)
642 file_open(widget, ge->ui);
643 if (!ge->job_file)
644 return;
2e33101f 645
b0500ccb 646 gc = ge->client;
2e33101f 647
b0500ccb
JA
648 if (!gc->client) {
649 struct fio_client *client;
19998dbc 650
b0500ccb
JA
651 if (get_connection_details(ge)) {
652 gfio_report_error(ge, "Failed to get connection details\n");
653 return;
1252d8f2 654 }
2e33101f 655
b0500ccb
JA
656 client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
657 if (!client) {
658 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
659 free(ge->host);
660 ge->host = NULL;
661 return;
1252d8f2 662 }
b0500ccb
JA
663 gfio_set_client(gc, client);
664 }
665
666 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No jobs running");
667 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
668 ret = fio_client_connect(gc->client);
669 if (!ret) {
670 if (!ge->ui->handler_running)
671 pthread_create(&ge->ui->t, NULL, job_thread, ge->ui);
672 gfio_set_state(ge, GE_STATE_CONNECTED);
1252d8f2 673 } else {
b0500ccb 674 gfio_report_error(ge, "Failed to connect to %s: %s\n", ge->client->client->hostname, strerror(-ret));
2e33101f 675 }
b0500ccb
JA
676 } else {
677 fio_client_terminate(gc->client);
678 gfio_set_state(ge, GE_STATE_NEW);
679 clear_ge_ui_info(ge);
2e33101f 680 }
b0500ccb 681}
2e33101f 682
b0500ccb
JA
683static void send_clicked(GtkWidget *widget, gpointer data)
684{
685 struct gui_entry *ge = data;
2e33101f 686
b0500ccb
JA
687 if (send_job_file(ge))
688 gtk_widget_set_sensitive(ge->button[GFIO_BUTTON_START], 1);
689}
62bc937f 690
b0500ccb 691static GtkWidget *new_client_page(struct gui_entry *ge);
62bc937f 692
b0500ccb
JA
693static struct gui_entry *alloc_new_gui_entry(struct gui *ui)
694{
695 struct gui_entry *ge;
a7a42ce1 696
b0500ccb
JA
697 ge = malloc(sizeof(*ge));
698 memset(ge, 0, sizeof(*ge));
699 ge->state = GE_STATE_NEW;
700 INIT_FLIST_HEAD(&ge->list);
701 flist_add_tail(&ge->list, &ui->list);
702 ge->ui = ui;
703 return ge;
704}
a7a42ce1 705
b0500ccb
JA
706static struct gui_entry *get_new_ge_with_tab(struct gui *ui, const char *name)
707{
708 struct gui_entry *ge;
a7a42ce1 709
b0500ccb 710 ge = alloc_new_gui_entry(ui);
a7a42ce1 711
b0500ccb
JA
712 ge->vbox = new_client_page(ge);
713 g_signal_connect(ge->vbox, "destroy", G_CALLBACK(ge_widget_destroy), ge);
a7a42ce1 714
b0500ccb
JA
715 ge->page_label = gtk_label_new(name);
716 ge->page_num = gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), ge->vbox, ge->page_label);
b9f3c7ed 717
b0500ccb
JA
718 gtk_widget_show_all(ui->window);
719 return ge;
720}
b9f3c7ed 721
b0500ccb
JA
722static void file_new(GtkWidget *w, gpointer data)
723{
724 struct gui *ui = (struct gui *) data;
725 struct gui_entry *ge;
62bc937f 726
b0500ccb
JA
727 ge = get_new_ge_with_tab(ui, "Untitled");
728 gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
729}
a7a42ce1 730
b0500ccb
JA
731/*
732 * Return the 'ge' corresponding to the tab. If the active tab is the
733 * main tab, open a new tab.
734 */
735static struct gui_entry *get_ge_from_page(struct gui *ui, gint cur_page,
736 int *created)
737{
738 struct flist_head *entry;
739 struct gui_entry *ge;
a7a42ce1 740
b0500ccb 741 if (!cur_page) {
1252d8f2 742 if (created)
b0500ccb
JA
743 *created = 1;
744 return get_new_ge_with_tab(ui, "Untitled");
745 }
b9f3c7ed 746
b0500ccb
JA
747 if (created)
748 *created = 0;
a7a42ce1 749
b0500ccb
JA
750 flist_for_each(entry, &ui->list) {
751 ge = flist_entry(entry, struct gui_entry, list);
752 if (ge->page_num == cur_page)
753 return ge;
1252d8f2 754 }
0cf3ece0 755
b0500ccb
JA
756 return NULL;
757}
e0681f3e 758
b0500ccb
JA
759static struct gui_entry *get_ge_from_cur_tab(struct gui *ui)
760{
761 gint cur_page;
e0681f3e 762
b0500ccb
JA
763 /*
764 * Main tab is tab 0, so any current page other than 0 holds
765 * a ge entry.
766 */
767 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
768 if (cur_page)
769 return get_ge_from_page(ui, cur_page, NULL);
0cf3ece0 770
b0500ccb
JA
771 return NULL;
772}
0cf3ece0 773
b0500ccb
JA
774static void file_close(GtkWidget *w, gpointer data)
775{
776 struct gui *ui = (struct gui *) data;
777 struct gui_entry *ge;
2f99deb0 778
b0500ccb
JA
779 /*
780 * Can't close the main tab
781 */
782 ge = get_ge_from_cur_tab(ui);
783 if (ge) {
784 gtk_widget_destroy(ge->vbox);
785 return;
1252d8f2 786 }
16ce5adf 787
b0500ccb
JA
788 if (!flist_empty(&ui->list)) {
789 gfio_report_info(ui, "Error", "The main page view cannot be closed\n");
790 return;
38634cb1
JA
791 }
792
b0500ccb
JA
793 gfio_quit(ui);
794}
2f99deb0 795
b0500ccb
JA
796static void file_add_recent(struct gui *ui, const gchar *uri)
797{
798 GtkRecentData grd;
2f99deb0 799
b0500ccb
JA
800 memset(&grd, 0, sizeof(grd));
801 grd.display_name = strdup("gfio");
802 grd.description = strdup("Fio job file");
803 grd.mime_type = strdup(GFIO_MIME);
804 grd.app_name = strdup(g_get_application_name());
805 grd.app_exec = strdup("gfio %f/%u");
85dd01e7 806
b0500ccb
JA
807 gtk_recent_manager_add_full(ui->recentmanager, uri, &grd);
808}
85dd01e7 809
b0500ccb
JA
810static gchar *get_filename_from_uri(const gchar *uri)
811{
812 if (strncmp(uri, "file://", 7))
813 return strdup(uri);
16ce5adf 814
b0500ccb
JA
815 return strdup(uri + 7);
816}
16ce5adf 817
b0500ccb
JA
818static int do_file_open(struct gui_entry *ge, const gchar *uri)
819{
820 struct fio_client *client;
f5c6726e 821
b0500ccb 822 assert(!ge->job_file);
38634cb1 823
b0500ccb 824 ge->job_file = get_filename_from_uri(uri);
38634cb1 825
b0500ccb
JA
826 client = fio_client_add_explicit(&gfio_client_ops, ge->host, ge->type, ge->port);
827 if (client) {
828 gfio_client_added(ge, client);
829 file_add_recent(ge->ui, uri);
830 return 0;
831 }
38634cb1 832
b0500ccb
JA
833 gfio_report_error(ge, "Failed to add client %s\n", ge->host);
834 free(ge->host);
835 ge->host = NULL;
836 return 1;
837}
38634cb1 838
b0500ccb
JA
839static int do_file_open_with_tab(struct gui *ui, const gchar *uri)
840{
841 struct gui_entry *ge;
842 gint cur_page;
843 int ret, ge_is_new = 0;
2f99deb0 844
b0500ccb
JA
845 /*
846 * Creates new tab if current tab is the main window, or the
847 * current tab already has a client.
848 */
849 cur_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(ui->notebook));
850 ge = get_ge_from_page(ui, cur_page, &ge_is_new);
851 if (ge->client) {
852 ge = get_new_ge_with_tab(ui, "Untitled");
853 ge_is_new = 1;
38634cb1 854 }
2f99deb0 855
b0500ccb
JA
856 gtk_notebook_set_current_page(GTK_NOTEBOOK(ui->notebook), ge->page_num);
857
858 if (get_connection_details(ge)) {
859 if (ge_is_new)
860 gtk_widget_destroy(ge->vbox);
861
862 return 1;
863 }
a6790906 864
b0500ccb 865 ret = do_file_open(ge, uri);
a6790906 866
b0500ccb
JA
867 if (!ret) {
868 if (ge->server_start)
869 gfio_start_server(ui);
870 } else {
871 if (ge_is_new)
872 gtk_widget_destroy(ge->vbox);
a6790906
JA
873 }
874
b0500ccb
JA
875 return ret;
876}
a6790906 877
b0500ccb
JA
878static void recent_open(GtkAction *action, gpointer data)
879{
880 struct gui *ui = (struct gui *) data;
881 GtkRecentInfo *info;
882 const gchar *uri;
0420ba6a 883
b0500ccb
JA
884 info = g_object_get_data(G_OBJECT(action), "gtk-recent-info");
885 uri = gtk_recent_info_get_uri(info);
0420ba6a 886
b0500ccb
JA
887 do_file_open_with_tab(ui, uri);
888}
a7a42ce1 889
b0500ccb
JA
890static void file_open(GtkWidget *w, gpointer data)
891{
892 struct gui *ui = data;
893 GtkWidget *dialog;
894 GtkFileFilter *filter;
895 gchar *filename;
896
897 dialog = gtk_file_chooser_dialog_new("Open File",
898 GTK_WINDOW(ui->window),
899 GTK_FILE_CHOOSER_ACTION_OPEN,
900 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
901 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
902 NULL);
903 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
904
905 filter = gtk_file_filter_new();
906 gtk_file_filter_add_pattern(filter, "*.fio");
907 gtk_file_filter_add_pattern(filter, "*.job");
908 gtk_file_filter_add_pattern(filter, "*.ini");
909 gtk_file_filter_add_mime_type(filter, GFIO_MIME);
910 gtk_file_filter_set_name(filter, "Fio job file");
911 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
912
913 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
914 gtk_widget_destroy(dialog);
915 return;
1252d8f2 916 }
0420ba6a 917
b0500ccb 918 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
0420ba6a 919
b0500ccb 920 gtk_widget_destroy(dialog);
0420ba6a 921
b0500ccb
JA
922 do_file_open_with_tab(ui, filename);
923 g_free(filename);
924}
0420ba6a 925
b0500ccb
JA
926static void file_save(GtkWidget *w, gpointer data)
927{
928 struct gui *ui = data;
929 GtkWidget *dialog;
0420ba6a 930
b0500ccb
JA
931 dialog = gtk_file_chooser_dialog_new("Save File",
932 GTK_WINDOW(ui->window),
933 GTK_FILE_CHOOSER_ACTION_SAVE,
934 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
935 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
936 NULL);
937
938 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
939 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
940
941 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
942 char *filename;
943
944 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
945 // save_job_file(filename);
946 g_free(filename);
0420ba6a 947 }
b0500ccb
JA
948 gtk_widget_destroy(dialog);
949}
0420ba6a 950
9b260bdf
JA
951static void view_log_destroy(GtkWidget *w, gpointer data)
952{
953 struct gui *ui = (struct gui *) data;
954
955 gtk_widget_ref(ui->log_tree);
956 gtk_container_remove(GTK_CONTAINER(w), ui->log_tree);
957 gtk_widget_destroy(w);
4cbe7211 958 ui->log_view = NULL;
9b260bdf
JA
959}
960
1252d8f2 961void gfio_view_log(struct gui *ui)
9b260bdf 962{
4cbe7211 963 GtkWidget *win, *scroll, *vbox, *box;
9b260bdf 964
4cbe7211
JA
965 if (ui->log_view)
966 return;
967
968 ui->log_view = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
9b260bdf 969 gtk_window_set_title(GTK_WINDOW(win), "Log");
4cbe7211 970 gtk_window_set_default_size(GTK_WINDOW(win), 700, 500);
9b260bdf 971
4cbe7211
JA
972 scroll = gtk_scrolled_window_new(NULL, NULL);
973
974 gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
975
976 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
977
978 box = gtk_hbox_new(TRUE, 0);
979 gtk_box_pack_start_defaults(GTK_BOX(box), ui->log_tree);
980 g_signal_connect(box, "destroy", G_CALLBACK(view_log_destroy), ui);
981 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scroll), box);
982
983 vbox = gtk_vbox_new(TRUE, 5);
984 gtk_box_pack_start_defaults(GTK_BOX(vbox), scroll);
9b260bdf 985
4cbe7211 986 gtk_container_add(GTK_CONTAINER(win), vbox);
9b260bdf
JA
987 gtk_widget_show_all(win);
988}
989
1252d8f2
JA
990static void view_log(GtkWidget *w, gpointer data)
991{
992 struct gui *ui = (struct gui *) data;
993
994 gfio_view_log(ui);
995}
996
85dd01e7
JA
997static void connect_job_entry(GtkWidget *w, gpointer data)
998{
999 struct gui *ui = (struct gui *) data;
1000 struct gui_entry *ge;
1001
1002 ge = get_ge_from_cur_tab(ui);
1003 if (ge)
1004 connect_clicked(w, ge);
1005}
1006
1007static void send_job_entry(GtkWidget *w, gpointer data)
1008{
1009 struct gui *ui = (struct gui *) data;
1010 struct gui_entry *ge;
1011
1012 ge = get_ge_from_cur_tab(ui);
1013 if (ge)
1014 send_clicked(w, ge);
85dd01e7
JA
1015}
1016
1017static void edit_job_entry(GtkWidget *w, gpointer data)
16ce5adf 1018{
9af4a244 1019 struct gui *ui = (struct gui *) data;
789f4ccd 1020 struct gui_entry *ge;
9af4a244 1021
789f4ccd
JA
1022 ge = get_ge_from_cur_tab(ui);
1023 if (ge && ge->client)
1024 gopt_get_options_window(ui->window, &ge->client->o);
16ce5adf
JA
1025}
1026
85dd01e7
JA
1027static void start_job_entry(GtkWidget *w, gpointer data)
1028{
1029 struct gui *ui = (struct gui *) data;
1030 struct gui_entry *ge;
1031
1032 ge = get_ge_from_cur_tab(ui);
1033 if (ge)
1034 start_job_clicked(w, ge);
1035}
1036
781ccba6
JA
1037static void view_results(GtkWidget *w, gpointer data)
1038{
1039 struct gui *ui = (struct gui *) data;
1040 struct gfio_client *gc;
1041 struct gui_entry *ge;
1042
1043 ge = get_ge_from_cur_tab(ui);
1044 if (!ge)
1045 return;
1046
1047 if (ge->results_window)
1048 return;
1049
1050 gc = ge->client;
1051 if (gc && gc->nr_results)
1052 gfio_display_end_results(gc);
1053}
1054
8577f4fd
JA
1055static void __update_graph_limits(struct gfio_graphs *g)
1056{
1057 line_graph_set_data_count_limit(g->iops_graph, gfio_graph_limit);
1058 line_graph_set_data_count_limit(g->bandwidth_graph, gfio_graph_limit);
1059}
1060
1061static void update_graph_limits(void)
1062{
1063 struct flist_head *entry;
1064 struct gui_entry *ge;
1065
1066 __update_graph_limits(&main_ui.graphs);
1067
1068 flist_for_each(entry, &main_ui.list) {
1069 ge = flist_entry(entry, struct gui_entry, list);
1070 __update_graph_limits(&ge->graphs);
1071 }
1072}
1073
46974a7d
JA
1074static void preferences(GtkWidget *w, gpointer data)
1075{
f3e8440f 1076 GtkWidget *dialog, *frame, *box, **buttons, *vbox, *font;
1cf6bca5 1077 GtkWidget *hbox, *spin, *entry, *spin_int;
6e02ad6c 1078 struct gui *ui = (struct gui *) data;
46974a7d
JA
1079 int i;
1080
1081 dialog = gtk_dialog_new_with_buttons("Preferences",
6e02ad6c 1082 GTK_WINDOW(ui->window),
46974a7d
JA
1083 GTK_DIALOG_DESTROY_WITH_PARENT,
1084 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1085 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1086 NULL);
1087
8577f4fd 1088 frame = gtk_frame_new("Graphing");
f3e8440f
JA
1089 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1090 vbox = gtk_vbox_new(FALSE, 6);
1091 gtk_container_add(GTK_CONTAINER(frame), vbox);
1092
1cf6bca5
JA
1093 hbox = gtk_hbox_new(FALSE, 5);
1094 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5);
1095 entry = gtk_label_new("Font face to use for graph labels");
1096 gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 5);
1097
f3e8440f 1098 font = gtk_font_button_new();
1cf6bca5 1099 gtk_box_pack_start(GTK_BOX(hbox), font, FALSE, FALSE, 5);
f3e8440f 1100
8577f4fd
JA
1101 box = gtk_vbox_new(FALSE, 6);
1102 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1103
1104 hbox = gtk_hbox_new(FALSE, 5);
1cf6bca5 1105 gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
8577f4fd
JA
1106 entry = gtk_label_new("Maximum number of data points in graph (seconds)");
1107 gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1108
c05d9056 1109 spin = create_spinbutton(hbox, 10, 1000000, gfio_graph_limit);
8577f4fd 1110
1cf6bca5
JA
1111 box = gtk_vbox_new(FALSE, 6);
1112 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
1113
1114 hbox = gtk_hbox_new(FALSE, 5);
1115 gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, TRUE, 5);
1116 entry = gtk_label_new("Client ETA request interval (msec)");
1117 gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 5);
1118
1119 spin_int = create_spinbutton(hbox, 100, 100000, gfio_client_ops.eta_msec);
a31d9fa4
JA
1120 frame = gtk_frame_new("Debug logging");
1121 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1122 vbox = gtk_vbox_new(FALSE, 6);
1123 gtk_container_add(GTK_CONTAINER(frame), vbox);
1124
1125 box = gtk_hbox_new(FALSE, 6);
1126 gtk_container_add(GTK_CONTAINER(vbox), box);
1127
1128 buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1129
1130 for (i = 0; i < FD_DEBUG_MAX; i++) {
1131 if (i == 7) {
1132 box = gtk_hbox_new(FALSE, 6);
1133 gtk_container_add(GTK_CONTAINER(vbox), box);
1134 }
1135
1136
1137 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
1138 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1139 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1140 }
1141
46974a7d
JA
1142 gtk_widget_show_all(dialog);
1143
1144 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1145 gtk_widget_destroy(dialog);
1146 return;
1147 }
1148
1149 for (i = 0; i < FD_DEBUG_MAX; i++) {
1150 int set;
1151
1152 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1153 if (set)
1154 fio_debug |= (1UL << i);
1155 }
1156
f3e8440f 1157 gfio_graph_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
8577f4fd
JA
1158 gfio_graph_limit = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
1159 update_graph_limits();
1cf6bca5 1160 gfio_client_ops.eta_msec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin_int));
8577f4fd 1161
46974a7d
JA
1162 gtk_widget_destroy(dialog);
1163}
1164
0420ba6a
JA
1165static void about_dialog(GtkWidget *w, gpointer data)
1166{
81e4ea6e
JA
1167 const char *authors[] = {
1168 "Jens Axboe <axboe@kernel.dk>",
1169 "Stephen Carmeron <stephenmcameron@gmail.com>",
1170 NULL
1171 };
84a72ed3
JA
1172 const char *license[] = {
1173 "Fio is free software; you can redistribute it and/or modify "
1174 "it under the terms of the GNU General Public License as published by "
1175 "the Free Software Foundation; either version 2 of the License, or "
1176 "(at your option) any later version.\n",
1177 "Fio is distributed in the hope that it will be useful, "
1178 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
1179 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
1180 "GNU General Public License for more details.\n",
1181 "You should have received a copy of the GNU General Public License "
1182 "along with Fio; if not, write to the Free Software Foundation, Inc., "
1183 "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n"
1184 };
1185 char *license_trans;
1186
1187 license_trans = g_strconcat(license[0], "\n", license[1], "\n",
1188 license[2], "\n", NULL);
81e4ea6e 1189
0420ba6a
JA
1190 gtk_show_about_dialog(NULL,
1191 "program-name", "gfio",
1192 "comments", "Gtk2 UI for fio",
84a72ed3 1193 "license", license_trans,
81e4ea6e
JA
1194 "website", "http://git.kernel.dk/?p=fio.git;a=summary",
1195 "authors", authors,
0420ba6a 1196 "version", fio_version_string,
81e4ea6e 1197 "copyright", "© 2012 Jens Axboe <axboe@kernel.dk>",
0420ba6a
JA
1198 "logo-icon-name", "fio",
1199 /* Must be last: */
81e4ea6e 1200 "wrap-license", TRUE,
0420ba6a 1201 NULL);
84a72ed3 1202
2f99deb0 1203 g_free(license_trans);
0420ba6a
JA
1204}
1205
1206static GtkActionEntry menu_items[] = {
46974a7d 1207 { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
9b260bdf 1208 { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL},
16ce5adf 1209 { "JobMenuAction", GTK_STOCK_FILE, "Job", NULL, NULL, NULL},
46974a7d 1210 { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
2f99deb0 1211 { "NewFile", GTK_STOCK_NEW, "New", "<Control>N", NULL, G_CALLBACK(file_new) },
16ce5adf 1212 { "CloseFile", GTK_STOCK_CLOSE, "Close", "<Control>W", NULL, G_CALLBACK(file_close) },
46974a7d
JA
1213 { "OpenFile", GTK_STOCK_OPEN, NULL, "<Control>O", NULL, G_CALLBACK(file_open) },
1214 { "SaveFile", GTK_STOCK_SAVE, NULL, "<Control>S", NULL, G_CALLBACK(file_save) },
1215 { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
9b260bdf 1216 { "ViewLog", NULL, "Log", "<Control>l", NULL, G_CALLBACK(view_log) },
781ccba6 1217 { "ViewResults", NULL, "Results", "<Control>R", NULL, G_CALLBACK(view_results) },
bc271d87 1218 { "ConnectJob", NULL, "Connect", "<Control>D", NULL, G_CALLBACK(connect_job_entry) },
85dd01e7
JA
1219 { "EditJob", NULL, "Edit job", "<Control>E", NULL, G_CALLBACK(edit_job_entry) },
1220 { "SendJob", NULL, "Send job", "<Control>X", NULL, G_CALLBACK(send_job_entry) },
1221 { "StartJob", NULL, "Start job", "<Control>L", NULL, G_CALLBACK(start_job_entry) },
46974a7d
JA
1222 { "Quit", GTK_STOCK_QUIT, NULL, "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1223 { "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_dialog) },
0420ba6a 1224};
3e47bd25 1225static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
0420ba6a
JA
1226
1227static const gchar *ui_string = " \
1228 <ui> \
1229 <menubar name=\"MainMenu\"> \
1230 <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
2f99deb0 1231 <menuitem name=\"New\" action=\"NewFile\" /> \
bf64138b 1232 <menuitem name=\"Open\" action=\"OpenFile\" /> \
16ce5adf 1233 <menuitem name=\"Close\" action=\"CloseFile\" /> \
2f99deb0 1234 <separator name=\"Separator1\"/> \
0420ba6a 1235 <menuitem name=\"Save\" action=\"SaveFile\" /> \
46974a7d 1236 <separator name=\"Separator2\"/> \
2f99deb0
JA
1237 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1238 <separator name=\"Separator3\"/> \
261f21d0
JA
1239 <placeholder name=\"FileRecentFiles\"/> \
1240 <separator name=\"Separator4\"/> \
0420ba6a
JA
1241 <menuitem name=\"Quit\" action=\"Quit\" /> \
1242 </menu> \
16ce5adf 1243 <menu name=\"JobMenu\" action=\"JobMenuAction\"> \
85dd01e7 1244 <menuitem name=\"Connect\" action=\"ConnectJob\" /> \
261f21d0 1245 <separator name=\"Separator5\"/> \
85dd01e7
JA
1246 <menuitem name=\"Edit job\" action=\"EditJob\" /> \
1247 <menuitem name=\"Send job\" action=\"SendJob\" /> \
261f21d0 1248 <separator name=\"Separator6\"/> \
85dd01e7 1249 <menuitem name=\"Start job\" action=\"StartJob\" /> \
16ce5adf 1250 </menu>\
9b260bdf 1251 <menu name=\"ViewMenu\" action=\"ViewMenuAction\"> \
781ccba6
JA
1252 <menuitem name=\"Results\" action=\"ViewResults\" /> \
1253 <separator name=\"Separator7\"/> \
9b260bdf
JA
1254 <menuitem name=\"Log\" action=\"ViewLog\" /> \
1255 </menu>\
0420ba6a
JA
1256 <menu name=\"Help\" action=\"HelpMenuAction\"> \
1257 <menuitem name=\"About\" action=\"About\" /> \
1258 </menu> \
1259 </menubar> \
1260 </ui> \
1261";
1262
4cbe7211
JA
1263static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager,
1264 struct gui *ui)
0420ba6a 1265{
ca664f49 1266 GtkActionGroup *action_group;
0420ba6a
JA
1267 GError *error = 0;
1268
1269 action_group = gtk_action_group_new("Menu");
4cbe7211 1270 gtk_action_group_add_actions(action_group, menu_items, nmenu_items, ui);
0420ba6a
JA
1271
1272 gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1273 gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1274
1275 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
02421e69 1276
0420ba6a
JA
1277 return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1278}
1279
1280void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1252d8f2 1281 GtkWidget *vbox, GtkUIManager *ui_manager)
0420ba6a 1282{
1252d8f2 1283 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
0420ba6a
JA
1284}
1285
c80b74b0
JA
1286static void combo_entry_changed(GtkComboBox *box, gpointer data)
1287{
1288 struct gui_entry *ge = (struct gui_entry *) data;
1289 gint index;
1290
1291 index = gtk_combo_box_get_active(box);
1292
1293 multitext_set_entry(&ge->eta.iotype, index);
99d633af 1294 multitext_set_entry(&ge->eta.bs, index);
c80b74b0
JA
1295 multitext_set_entry(&ge->eta.ioengine, index);
1296 multitext_set_entry(&ge->eta.iodepth, index);
1297}
1298
1299static void combo_entry_destroy(GtkWidget *widget, gpointer data)
1300{
1301 struct gui_entry *ge = (struct gui_entry *) data;
1302
1303 multitext_free(&ge->eta.iotype);
99d633af 1304 multitext_free(&ge->eta.bs);
c80b74b0
JA
1305 multitext_free(&ge->eta.ioengine);
1306 multitext_free(&ge->eta.iodepth);
1307}
1308
2f99deb0 1309static GtkWidget *new_client_page(struct gui_entry *ge)
ff1f3280 1310{
2f99deb0 1311 GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
65476336 1312 GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
0420ba6a 1313
2f99deb0 1314 main_vbox = gtk_vbox_new(FALSE, 3);
45032dd8 1315
65476336
JA
1316 top_align = gtk_alignment_new(0, 0, 1, 0);
1317 top_vbox = gtk_vbox_new(FALSE, 3);
1318 gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1319 gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
2f99deb0
JA
1320
1321 probe = gtk_frame_new("Job");
1322 gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
1323 probe_frame = gtk_vbox_new(FALSE, 3);
1324 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1325
1326 probe_box = gtk_hbox_new(FALSE, 3);
1327 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1328 ge->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1329 ge->probe.os = new_info_label_in_frame(probe_box, "OS");
1330 ge->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1331 ge->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1332
1333 probe_box = gtk_hbox_new(FALSE, 3);
1334 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1335
3863d1ad 1336 ge->eta.names = new_combo_entry_in_frame(probe_box, "Jobs");
c80b74b0
JA
1337 g_signal_connect(ge->eta.names, "changed", G_CALLBACK(combo_entry_changed), ge);
1338 g_signal_connect(ge->eta.names, "destroy", G_CALLBACK(combo_entry_destroy), ge);
1339 ge->eta.iotype.entry = new_info_entry_in_frame(probe_box, "IO");
99d633af 1340 ge->eta.bs.entry = new_info_entry_in_frame(probe_box, "Blocksize (Read/Write)");
c80b74b0
JA
1341 ge->eta.ioengine.entry = new_info_entry_in_frame(probe_box, "IO Engine");
1342 ge->eta.iodepth.entry = new_info_entry_in_frame(probe_box, "IO Depth");
2f99deb0
JA
1343 ge->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
1344 ge->eta.files = new_info_entry_in_frame(probe_box, "Open files");
1345
1346 probe_box = gtk_hbox_new(FALSE, 3);
1347 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
1348 ge->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1349 ge->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1350 ge->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1351 ge->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1352
1353 /*
1354 * Only add this if we have a commit rate
2839f0c6 1355 */
2f99deb0
JA
1356#if 0
1357 probe_box = gtk_hbox_new(FALSE, 3);
1358 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
2839f0c6 1359
2f99deb0
JA
1360 ge->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1361 ge->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
ff1f3280 1362
2f99deb0
JA
1363 ge->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1364 ge->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1365#endif
ff1f3280 1366
2f99deb0
JA
1367 /*
1368 * Set up a drawing area and IOPS and bandwidth graphs
1369 */
2f99deb0 1370 ge->graphs.drawing_area = gtk_drawing_area_new();
2f99deb0 1371 gtk_widget_set_size_request(GTK_WIDGET(ge->graphs.drawing_area),
57f9d28e 1372 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1252d8f2 1373 gtk_widget_modify_bg(ge->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
2f99deb0
JA
1374 g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "expose_event",
1375 G_CALLBACK(on_expose_drawing_area), &ge->graphs);
1376 g_signal_connect(G_OBJECT(ge->graphs.drawing_area), "configure_event",
1377 G_CALLBACK(on_config_drawing_area), &ge->graphs);
65476336
JA
1378 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1379 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2f99deb0 1380 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
65476336 1381 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2f99deb0 1382 ge->graphs.drawing_area);
65476336 1383 gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window, TRUE, TRUE, 0);
04cc6b77 1384
2f99deb0
JA
1385 setup_graphs(&ge->graphs);
1386
1387 /*
1388 * Set up alignments for widgets at the bottom of ui,
1389 * align bottom left, expand horizontally but not vertically
1390 */
65476336 1391 bottom_align = gtk_alignment_new(0, 1, 1, 0);
2f99deb0 1392 ge->buttonbox = gtk_hbox_new(FALSE, 0);
65476336
JA
1393 gtk_container_add(GTK_CONTAINER(bottom_align), ge->buttonbox);
1394 gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
2f99deb0
JA
1395
1396 add_buttons(ge, buttonspeclist, ARRAYSIZE(buttonspeclist));
0420ba6a 1397
c36f98d9 1398 /*
2f99deb0
JA
1399 * Set up thread status progress bar
1400 */
1401 ge->thread_status_pb = gtk_progress_bar_new();
1402 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ge->thread_status_pb), 0.0);
1403 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ge->thread_status_pb), "No connections");
1404 gtk_container_add(GTK_CONTAINER(ge->buttonbox), ge->thread_status_pb);
1405
1406
1407 return main_vbox;
1408}
1409
1410static GtkWidget *new_main_page(struct gui *ui)
1411{
1412 GtkWidget *main_vbox, *probe, *probe_frame, *probe_box;
65476336 1413 GtkWidget *scrolled_window, *bottom_align, *top_align, *top_vbox;
2f99deb0
JA
1414
1415 main_vbox = gtk_vbox_new(FALSE, 3);
1416
1417 /*
1418 * Set up alignments for widgets at the top of ui,
c36f98d9
SC
1419 * align top left, expand horizontally but not vertically
1420 */
65476336
JA
1421 top_align = gtk_alignment_new(0, 0, 1, 0);
1422 top_vbox = gtk_vbox_new(FALSE, 0);
1423 gtk_container_add(GTK_CONTAINER(top_align), top_vbox);
1424 gtk_box_pack_start(GTK_BOX(main_vbox), top_align, FALSE, FALSE, 0);
c36f98d9 1425
2f99deb0
JA
1426 probe = gtk_frame_new("Run statistics");
1427 gtk_box_pack_start(GTK_BOX(main_vbox), probe, FALSE, FALSE, 3);
843ad237
JA
1428 probe_frame = gtk_vbox_new(FALSE, 3);
1429 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1430
1431 probe_box = gtk_hbox_new(FALSE, 3);
2f99deb0 1432 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, FALSE, FALSE, 3);
3863d1ad 1433 ui->eta.jobs = new_info_entry_in_frame(probe_box, "Running");
ca850992
JA
1434 ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1435 ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1436 ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1437 ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
3e47bd25 1438
807f9971
JA
1439 /*
1440 * Only add this if we have a commit rate
1441 */
1442#if 0
3e47bd25
JA
1443 probe_box = gtk_hbox_new(FALSE, 3);
1444 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
807f9971
JA
1445
1446 ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1447 ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1448
3e47bd25
JA
1449 ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1450 ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
807f9971 1451#endif
3e47bd25 1452
736f2dff 1453 /*
2fd3bb0e 1454 * Set up a drawing area and IOPS and bandwidth graphs
736f2dff 1455 */
2f99deb0 1456 ui->graphs.drawing_area = gtk_drawing_area_new();
2f99deb0 1457 gtk_widget_set_size_request(GTK_WIDGET(ui->graphs.drawing_area),
57f9d28e 1458 DRAWING_AREA_XDIM, DRAWING_AREA_YDIM);
1252d8f2 1459 gtk_widget_modify_bg(ui->graphs.drawing_area, GTK_STATE_NORMAL, &gfio_color_white);
2f99deb0
JA
1460 g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "expose_event",
1461 G_CALLBACK(on_expose_drawing_area), &ui->graphs);
1462 g_signal_connect(G_OBJECT(ui->graphs.drawing_area), "configure_event",
1463 G_CALLBACK(on_config_drawing_area), &ui->graphs);
65476336
JA
1464 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1465 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
736f2dff 1466 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
65476336 1467 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
2f99deb0 1468 ui->graphs.drawing_area);
65476336 1469 gtk_box_pack_start(GTK_BOX(main_vbox), scrolled_window,
e164534f 1470 TRUE, TRUE, 0);
736f2dff 1471
2f99deb0 1472 setup_graphs(&ui->graphs);
2fd3bb0e 1473
c36f98d9
SC
1474 /*
1475 * Set up alignments for widgets at the bottom of ui,
1476 * align bottom left, expand horizontally but not vertically
1477 */
65476336 1478 bottom_align = gtk_alignment_new(0, 1, 1, 0);
c36f98d9 1479 ui->buttonbox = gtk_hbox_new(FALSE, 0);
65476336
JA
1480 gtk_container_add(GTK_CONTAINER(bottom_align), ui->buttonbox);
1481 gtk_box_pack_start(GTK_BOX(main_vbox), bottom_align, FALSE, FALSE, 0);
c36f98d9 1482
3ec62ec4
JA
1483 /*
1484 * Set up thread status progress bar
1485 */
1486 ui->thread_status_pb = gtk_progress_bar_new();
1487 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
8663ea65 1488 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
3ec62ec4
JA
1489 gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1490
2f99deb0
JA
1491 return main_vbox;
1492}
1493
1494static gboolean notebook_switch_page(GtkNotebook *notebook, GtkWidget *widget,
1495 guint page, gpointer data)
1496
1497{
02421e69 1498 struct gui *ui = (struct gui *) data;
85dd01e7
JA
1499 struct gui_entry *ge;
1500
1501 if (!page) {
1502 set_job_menu_visible(ui, 0);
781ccba6 1503 set_view_results_visible(ui, 0);
85dd01e7
JA
1504 return TRUE;
1505 }
1506
1507 set_job_menu_visible(ui, 1);
6e02ad6c 1508 ge = get_ge_from_page(ui, page, NULL);
85dd01e7
JA
1509 if (ge)
1510 update_button_states(ui, ge);
02421e69 1511
2f99deb0
JA
1512 return TRUE;
1513}
1514
38634cb1
JA
1515static gint compare_recent_items(GtkRecentInfo *a, GtkRecentInfo *b)
1516{
1517 time_t time_a = gtk_recent_info_get_visited(a);
1518 time_t time_b = gtk_recent_info_get_visited(b);
1519
1520 return time_b - time_a;
1521}
1522
1523static void add_recent_file_items(struct gui *ui)
1524{
1525 const gchar *gfio = g_get_application_name();
1526 GList *items, *item;
1527 int i = 0;
1528
1529 if (ui->recent_ui_id) {
1530 gtk_ui_manager_remove_ui(ui->uimanager, ui->recent_ui_id);
1531 gtk_ui_manager_ensure_update(ui->uimanager);
1532 }
1533 ui->recent_ui_id = gtk_ui_manager_new_merge_id(ui->uimanager);
1534
1535 if (ui->actiongroup) {
1536 gtk_ui_manager_remove_action_group(ui->uimanager, ui->actiongroup);
1537 g_object_unref(ui->actiongroup);
1538 }
1539 ui->actiongroup = gtk_action_group_new("RecentFileActions");
1540
1541 gtk_ui_manager_insert_action_group(ui->uimanager, ui->actiongroup, -1);
1542
1543 items = gtk_recent_manager_get_items(ui->recentmanager);
1544 items = g_list_sort(items, (GCompareFunc) compare_recent_items);
1545
1546 for (item = items; item && item->data; item = g_list_next(item)) {
1547 GtkRecentInfo *info = (GtkRecentInfo *) item->data;
1548 gchar *action_name;
1549 const gchar *label;
1550 GtkAction *action;
1551
1552 if (!gtk_recent_info_has_application(info, gfio))
1553 continue;
1554
1555 /*
1556 * We only support local files for now
1557 */
1558 if (!gtk_recent_info_is_local(info) || !gtk_recent_info_exists(info))
1559 continue;
1560
1561 action_name = g_strdup_printf("RecentFile%u", i++);
1562 label = gtk_recent_info_get_display_name(info);
1563
1564 action = g_object_new(GTK_TYPE_ACTION,
1565 "name", action_name,
1566 "label", label, NULL);
1567
1568 g_object_set_data_full(G_OBJECT(action), "gtk-recent-info",
1569 gtk_recent_info_ref(info),
1570 (GDestroyNotify) gtk_recent_info_unref);
1571
1572
1573 g_signal_connect(action, "activate", G_CALLBACK(recent_open), ui);
1574
1575 gtk_action_group_add_action(ui->actiongroup, action);
1576 g_object_unref(action);
1577
1578 gtk_ui_manager_add_ui(ui->uimanager, ui->recent_ui_id,
1579 "/MainMenu/FileMenu/FileRecentFiles",
1580 label, action_name,
1581 GTK_UI_MANAGER_MENUITEM, FALSE);
1582
1583 g_free(action_name);
1584
1585 if (i == 8)
1586 break;
1587 }
1588
1589 g_list_foreach(items, (GFunc) gtk_recent_info_unref, NULL);
1590 g_list_free(items);
1591}
1592
a6790906 1593static void drag_and_drop_received(GtkWidget *widget, GdkDragContext *ctx,
6e02ad6c
JA
1594 gint x, gint y, GtkSelectionData *seldata,
1595 guint info, guint time, gpointer *data)
a6790906 1596{
6e02ad6c 1597 struct gui *ui = (struct gui *) data;
a6790906
JA
1598 gchar **uris;
1599 GtkWidget *source;
a6790906
JA
1600
1601 source = gtk_drag_get_source_widget(ctx);
1602 if (source && widget == gtk_widget_get_toplevel(source)) {
1603 gtk_drag_finish(ctx, FALSE, FALSE, time);
1604 return;
1605 }
1606
6e02ad6c 1607 uris = gtk_selection_data_get_uris(seldata);
a6790906
JA
1608 if (!uris) {
1609 gtk_drag_finish(ctx, FALSE, FALSE, time);
1610 return;
1611 }
1612
0cf3ece0
JA
1613 if (uris[0])
1614 do_file_open_with_tab(ui, uris[0]);
a6790906
JA
1615
1616 gtk_drag_finish(ctx, TRUE, FALSE, time);
1617 g_strfreev(uris);
1618}
1619
2f99deb0
JA
1620static void init_ui(int *argc, char **argv[], struct gui *ui)
1621{
1622 GtkSettings *settings;
02421e69 1623 GtkWidget *vbox;
2f99deb0
JA
1624
1625 /* Magical g*thread incantation, you just need this thread stuff.
1626 * Without it, the update that happens in gfio_update_thread_status
1627 * doesn't really happen in a timely fashion, you need expose events
1628 */
1629 if (!g_thread_supported())
1630 g_thread_init(NULL);
1631 gdk_threads_init();
1632
1633 gtk_init(argc, argv);
1634 settings = gtk_settings_get_default();
1635 gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1636 g_type_init();
1252d8f2 1637 gdk_color_parse("white", &gfio_color_white);
2f99deb0
JA
1638
1639 ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
814479d5 1640 gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
2f99deb0
JA
1641 gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
1642
6e02ad6c
JA
1643 g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), ui);
1644 g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), ui);
2f99deb0
JA
1645
1646 ui->vbox = gtk_vbox_new(FALSE, 0);
1647 gtk_container_add(GTK_CONTAINER(ui->window), ui->vbox);
1648
02421e69
JA
1649 ui->uimanager = gtk_ui_manager_new();
1650 ui->menu = get_menubar_menu(ui->window, ui->uimanager, ui);
1651 gfio_ui_setup(settings, ui->menu, ui->vbox, ui->uimanager);
2f99deb0 1652
38634cb1
JA
1653 ui->recentmanager = gtk_recent_manager_get_default();
1654 add_recent_file_items(ui);
1655
2f99deb0
JA
1656 ui->notebook = gtk_notebook_new();
1657 g_signal_connect(ui->notebook, "switch-page", G_CALLBACK(notebook_switch_page), ui);
b870c31b 1658 gtk_notebook_set_scrollable(GTK_NOTEBOOK(ui->notebook), 1);
0aa928c4 1659 gtk_notebook_popup_enable(GTK_NOTEBOOK(ui->notebook));
2f99deb0
JA
1660 gtk_container_add(GTK_CONTAINER(ui->vbox), ui->notebook);
1661
1662 vbox = new_main_page(ui);
0cf3ece0 1663 gtk_drag_dest_set(GTK_WIDGET(ui->window), GTK_DEST_DEFAULT_ALL, NULL, 1, GDK_ACTION_COPY);
a6790906
JA
1664 gtk_drag_dest_add_uri_targets(GTK_WIDGET(ui->window));
1665 g_signal_connect(ui->window, "drag-data-received", G_CALLBACK(drag_and_drop_received), ui);
2f99deb0
JA
1666
1667 gtk_notebook_append_page(GTK_NOTEBOOK(ui->notebook), vbox, gtk_label_new("Main"));
1668
9b260bdf 1669 gfio_ui_setup_log(ui);
3ec62ec4 1670
ff1f3280
SC
1671 gtk_widget_show_all(ui->window);
1672}
1673
8232e285 1674int main(int argc, char *argv[], char *envp[])
ff1f3280 1675{
8232e285
SC
1676 if (initialize_fio(envp))
1677 return 1;
0420ba6a
JA
1678 if (fio_init_options())
1679 return 1;
a1820207 1680
2f99deb0
JA
1681 memset(&main_ui, 0, sizeof(main_ui));
1682 INIT_FLIST_HEAD(&main_ui.list);
1683
1684 init_ui(&argc, &argv, &main_ui);
5b7573ab 1685
2839f0c6 1686 gdk_threads_enter();
ff1f3280 1687 gtk_main();
2839f0c6 1688 gdk_threads_leave();
ff1f3280
SC
1689 return 0;
1690}