1 /* mainwin.c - main window
2 *
3 * Copyright 2010 Petteri Hintsanen <petterih@iki.fi>
4 *
5 * This file is part of abx.
6 *
7 * abx is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * abx is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
15 * License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with abx. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "gtkui.h"
22 #include "test.h"
23 #include <string.h>
24
25 GtkWidget *main_window;
26
27 /* How often we should update the location scale (in ms). Note that
28 GTK+ can't handle very rapid updates. */
29 static const int UPDATE_LOCATION_TIMEOUT = 1000;
30
31 /* Glib event source id for update_location */
32 static guint timeout_id;
33
34 static GtkWidget *new_test_menu_item;
35 static GtkWidget *about_menu_item;
36
37 static GtkWidget *status_bar;
38 static gint play_context;
39 static gint non_play_context;
40
41 static GtkWidget *first_sample;
42 static GtkWidget *second_sample;
43 static GtkWidget *pause_button;
44 static GtkWidget *test_sample;
45 static GtkWidget *prev_marker;
46 static GtkWidget *next_marker;
47
48 static GtkObject *adjustment;
49 static GtkWidget *scale;
50 static int is_user_seeking;
51
52 static GtkWidget *markers;
53 static GtkListStore *marker_list;
54
55 static GtkWidget *trial_label;
56 static GtkWidget *meta_a_label;
57 static GtkWidget *meta_b_label;
58
59 static gboolean recurrent_update_location(gpointer data);
60 static void clear_status_bar(void);
61 static GtkWidget *create_metadata_box(void);
62 static GtkWidget *create_test_box(void);
63 static GtkWidget *create_menu_bar(void);
64 static void create_main_window(void);
65 static void create_status_bar(void);
66 static GtkWidget *create_playback_box(void);
67 static GtkWidget *create_marker_box(void);
68
69 static void destroy_event_handler(GtkWidget *widget,
70 gpointer data);
71 static void marker_activated(GtkTreeView *markers,
72 GtkTreePath *path,
73 GtkTreeViewColumn *column,
74 gpointer user_data);
75 static void rewind_button_clicked(GtkWidget *widget, gpointer data);
76 static void pause_button_clicked(GtkWidget *widget, gpointer data);
77 static void play_button_clicked(GtkWidget *widget, gpointer data);
78 static void decide_button_clicked(GtkWidget *widget, gpointer data);
79 static gboolean scale_button_pressed_or_released(GtkWidget *widget,
80 GdkEventButton *event,
81 gpointer data);
82 static void add_marker_clicked(GtkWidget *widget, gpointer data);
83 static void remove_marker_clicked(GtkWidget *widget, gpointer data);
84 static void menu_item_clicked(GtkWidget *widget, gpointer data);
85
86 /*
87 * Show the main window, creating it if necessary.
88 */
89 void
show_main_window(void)90 show_main_window(void)
91 {
92 if (!main_window) {
93 create_main_window();
94 gtk_widget_show_all(main_window);
95 } else {
96 gtk_window_present(GTK_WINDOW(main_window));
97 }
98 }
99
100 /*
101 * Update trial and metadata if test is in progress, that is, if
102 * current_trial >= 0.
103 */
104 void
update_main_window(void)105 update_main_window(void)
106 {
107 static GString *label = NULL;
108 static GString *meta_a = NULL;
109 static GString *meta_b = NULL;
110
111 if (!label) label = g_string_new(NULL);
112 if (!meta_a) meta_a = g_string_new(NULL);
113 if (!meta_b) meta_b = g_string_new(NULL);
114
115 if (current_trial < 0) {
116 g_string_printf(label, "No test in progress.");
117 g_string_printf(meta_a, "A: (none)");
118 g_string_printf(meta_b, "B: (none)");
119 g_object_set(adjustment, "upper", 0.0, "value", 0.0, NULL);
120 gtk_list_store_clear(marker_list);
121 } else if (current_trial == 0) {
122 g_string_printf(label, "Test trial 1 of %d", num_test_trials());
123 g_string_printf(meta_a,
124 "Sample A: '%s', %d Hz, %d bits, %d channels",
125 basename_a, metadata_a.rate,
126 metadata_a.bits, metadata_a.channels);
127 g_string_printf(meta_b,
128 "Sample B: '%s', %d Hz, %d bits, %d channels",
129 basename_b, metadata_b.rate,
130 metadata_b.bits, metadata_b.channels);
131 g_object_set(adjustment, "upper", (gdouble) metadata_a.duration,
132 "value", 0.0, NULL);
133 gtk_list_store_clear(marker_list);
134 } else {
135 g_string_printf(label, "Test trial %d of %d",
136 current_trial + 1, num_test_trials());
137 }
138
139 gtk_label_set_text(GTK_LABEL(trial_label), label->str);
140 gtk_label_set_text(GTK_LABEL(meta_a_label), meta_a->str);
141 gtk_label_set_text(GTK_LABEL(meta_b_label), meta_b->str);
142 }
143
144 /*
145 * Update time slider to the current playback location. Remove
146 * possible previous recurrent update timer, and reinstall a new one.
147 * (This is done mostly for consistent UI behaviour.)
148 */
149 static void
update_location(void)150 update_location(void)
151 {
152 if (timeout_id) {
153 g_source_remove(timeout_id);
154 timeout_id = 0;
155 }
156 if (recurrent_update_location(NULL)) {
157 timeout_id = g_timeout_add(UPDATE_LOCATION_TIMEOUT,
158 recurrent_update_location, NULL);
159 }
160 }
161
162 /*
163 * Update current location to the time slider. This function is added
164 * to the Glib main event loop whenever update_location is called.
165 *
166 * Return FALSE if playback has been stopped, so that glib will remove
167 * the function from its event loop. Otherwise, return TRUE.
168 */
169 static gboolean
recurrent_update_location(gpointer data)170 recurrent_update_location(gpointer data)
171 {
172 if (!is_user_seeking) {
173 Player_state state;
174 if (get_playback_state(&state) != -1) {
175 gtk_range_set_value(GTK_RANGE(scale), state.location);
176 if (state.playback == STOPPED) {
177 /* g_debug("removing update 1"); */
178 timeout_id = 0;
179 clear_status_bar();
180 return FALSE;
181 }
182 } else {
183 /* g_debug("removing update 2"); */
184 timeout_id = 0;
185 return FALSE;
186 }
187 }
188 return TRUE;
189 }
190
191 /*
192 * Restore "Stopped" to the status bar.
193 */
194 static void
clear_status_bar(void)195 clear_status_bar(void)
196 {
197 /* pop twice to remove possible "Paused" from the status bar */
198 gtk_statusbar_pop(GTK_STATUSBAR(status_bar), play_context);
199 gtk_statusbar_pop(GTK_STATUSBAR(status_bar), play_context);
200 }
201
202 /*
203 * Create main window elements and lay them into main_box.
204 */
205 static void
create_main_window(void)206 create_main_window(void)
207 {
208 GtkWidget *left_box;
209 GtkWidget *main_hbox;
210 GtkWidget *main_box;
211 GtkWidget *playback_box;
212 GtkWidget *test_box;
213 GtkWidget *marker_box;
214 GtkWidget *meta_box;
215 GtkWidget *menu_bar;
216 main_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
217 playback_box = create_playback_box();
218 test_box = create_test_box();
219 marker_box = create_marker_box();
220 meta_box = create_metadata_box();
221 menu_bar = create_menu_bar();
222 create_status_bar();
223
224 left_box = gtk_vbox_new(FALSE, 10);
225 gtk_box_pack_start(GTK_BOX(left_box), test_box, TRUE, FALSE, 0);
226 gtk_box_pack_start(GTK_BOX(left_box), meta_box, TRUE, FALSE, 0);
227 gtk_box_pack_start(GTK_BOX(left_box), playback_box, TRUE, FALSE, 0);
228
229 main_hbox = gtk_hbox_new(FALSE, 10);
230 gtk_container_set_border_width(GTK_CONTAINER(main_hbox), 10);
231 gtk_box_pack_start(GTK_BOX(main_hbox), left_box, FALSE, TRUE, 0);
232 gtk_box_pack_start(GTK_BOX(main_hbox), marker_box, TRUE, TRUE, 0);
233
234 main_box = gtk_vbox_new(FALSE, 0);
235 gtk_box_pack_start(GTK_BOX(main_box), menu_bar, FALSE, FALSE, 0);
236 gtk_box_pack_start(GTK_BOX(main_box), main_hbox, TRUE, TRUE, 0);
237 gtk_box_pack_start(GTK_BOX(main_box), status_bar, FALSE, FALSE, 0);
238
239 gtk_window_set_title(GTK_WINDOW(main_window), "ABX Tester");
240 g_signal_connect(G_OBJECT(main_window), "destroy",
241 G_CALLBACK(destroy_event_handler), NULL);
242 gtk_container_add(GTK_CONTAINER(main_window), main_box);
243 }
244
245 /*
246 * Return a new stock-like "Play" button.
247 */
248 static GtkWidget *
create_play_button(gchar * label)249 create_play_button(gchar *label)
250 {
251 GtkWidget *aligned_box = gtk_hbox_new(TRUE, 0);
252 GtkWidget *box = gtk_hbox_new(FALSE, 3);
253 gtk_box_pack_start(GTK_BOX(box),
254 gtk_image_new_from_stock(GTK_STOCK_MEDIA_PLAY,
255 GTK_ICON_SIZE_BUTTON),
256 FALSE, FALSE, 0);
257 gtk_box_pack_start(GTK_BOX(box), gtk_label_new_with_mnemonic(label),
258 FALSE, FALSE, 0);
259 gtk_box_pack_start(GTK_BOX(aligned_box), box, TRUE, FALSE, 0);
260 return aligned_box;
261 }
262
263 /*
264 * Create playback-related widgets (time scale and control buttons),
265 * and lay them into a vbox. Return the box.
266 */
267 static GtkWidget *
create_playback_box(void)268 create_playback_box(void)
269 {
270 GtkWidget *playback_box;
271 GtkWidget *button_box;
272 GtkWidget *first_row;
273 GtkWidget *second_row;
274 adjustment = gtk_adjustment_new(0, 0, 60, 1.0, 15.0, 0);
275 scale = gtk_hscale_new(GTK_ADJUSTMENT(adjustment));
276 gtk_range_set_update_policy(GTK_RANGE(scale), GTK_UPDATE_DISCONTINUOUS);
277 is_user_seeking = 0;
278 g_signal_connect(G_OBJECT(scale), "button_press_event",
279 G_CALLBACK(scale_button_pressed_or_released), NULL);
280 g_signal_connect(G_OBJECT(scale), "button_release_event",
281 G_CALLBACK(scale_button_pressed_or_released), NULL);
282
283 first_sample = gtk_button_new();
284 gtk_container_add(GTK_CONTAINER(first_sample),
285 create_play_button("Play _A"));
286 g_signal_connect(G_OBJECT(first_sample), "clicked",
287 G_CALLBACK(play_button_clicked), NULL);
288 second_sample = gtk_button_new();
289 gtk_container_add(GTK_CONTAINER(second_sample),
290 create_play_button("Play _B"));
291 g_signal_connect(G_OBJECT(second_sample), "clicked",
292 G_CALLBACK(play_button_clicked), NULL);
293 test_sample = gtk_button_new();
294 gtk_container_add(GTK_CONTAINER(test_sample),
295 create_play_button("Play _X"));
296 g_signal_connect(G_OBJECT(test_sample), "clicked",
297 G_CALLBACK(play_button_clicked), NULL);
298 pause_button = gtk_button_new_from_stock(GTK_STOCK_MEDIA_PAUSE);
299 g_signal_connect(G_OBJECT(pause_button), "clicked",
300 G_CALLBACK(pause_button_clicked), NULL);
301 prev_marker = gtk_button_new_from_stock(GTK_STOCK_MEDIA_PREVIOUS);
302 g_signal_connect(G_OBJECT(prev_marker), "clicked",
303 G_CALLBACK(rewind_button_clicked), NULL);
304 next_marker = gtk_button_new_from_stock(GTK_STOCK_MEDIA_NEXT);
305 g_signal_connect(G_OBJECT(next_marker), "clicked",
306 G_CALLBACK(rewind_button_clicked), NULL);
307
308 button_box = gtk_vbox_new(TRUE, 5);
309 first_row = gtk_hbox_new(TRUE, 5);
310 second_row = gtk_hbox_new(TRUE, 5);
311 gtk_box_pack_start(GTK_BOX(first_row), first_sample, TRUE, TRUE, 0);
312 gtk_box_pack_start(GTK_BOX(first_row), second_sample, TRUE, TRUE, 0);
313 gtk_box_pack_start(GTK_BOX(first_row), test_sample, TRUE, TRUE, 0);
314 gtk_box_pack_start(GTK_BOX(second_row), pause_button, TRUE, TRUE, 0);
315 gtk_box_pack_start(GTK_BOX(second_row), prev_marker, TRUE, TRUE, 0);
316 gtk_box_pack_start(GTK_BOX(second_row), next_marker, TRUE, TRUE, 0);
317 gtk_box_pack_start(GTK_BOX(button_box), first_row, TRUE, TRUE, 0);
318 gtk_box_pack_start(GTK_BOX(button_box), second_row, TRUE, TRUE, 0);
319
320 playback_box = gtk_vbox_new(FALSE, 5);
321 gtk_box_pack_start(GTK_BOX(playback_box), scale, TRUE, TRUE, 0);
322 gtk_box_pack_start(GTK_BOX(playback_box), button_box, FALSE, FALSE, 0);
323 return playback_box;
324 }
325
326 /*
327 * Create marker-related widgets and lay them into a vbox. Return the
328 * box.
329 */
330 static GtkWidget *
create_marker_box(void)331 create_marker_box(void)
332 {
333 GtkWidget *scrolled_window;
334 GtkWidget *add_marker;
335 GtkWidget *remove_marker;
336 GtkWidget *clear_markers;
337 GtkWidget *upper_buttons_box;
338 GtkWidget *marker_buttons_box;
339 GtkWidget *marker_box;
340 GtkTreeViewColumn *column;
341
342 marker_list = gtk_list_store_new(1, G_TYPE_DOUBLE);
343 markers = gtk_tree_view_new_with_model(GTK_TREE_MODEL(marker_list));
344 g_signal_connect(G_OBJECT(markers), "row-activated",
345 G_CALLBACK(marker_activated), NULL);
346 column = (gtk_tree_view_column_new_with_attributes
347 ("_Markers", gtk_cell_renderer_text_new(),
348 "text", 0, NULL));
349 gtk_tree_view_append_column(GTK_TREE_VIEW(markers), column);
350
351 add_marker = gtk_button_new_from_stock(GTK_STOCK_ADD);
352 remove_marker = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
353 clear_markers = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
354 g_signal_connect(G_OBJECT(add_marker), "clicked",
355 G_CALLBACK(add_marker_clicked), NULL);
356 g_signal_connect(G_OBJECT(remove_marker), "clicked",
357 G_CALLBACK(remove_marker_clicked), NULL);
358 g_signal_connect_swapped(G_OBJECT(clear_markers), "clicked",
359 G_CALLBACK(gtk_list_store_clear),
360 G_OBJECT(marker_list));
361
362 upper_buttons_box = gtk_hbox_new(TRUE, 5);
363 gtk_box_pack_start(GTK_BOX(upper_buttons_box), add_marker,
364 TRUE, TRUE, 0);
365 gtk_box_pack_start(GTK_BOX(upper_buttons_box), remove_marker,
366 TRUE, TRUE, 0);
367 marker_buttons_box = gtk_vbox_new(TRUE, 5);
368 gtk_box_pack_start(GTK_BOX(marker_buttons_box), upper_buttons_box,
369 TRUE, TRUE, 0);
370 gtk_box_pack_start(GTK_BOX(marker_buttons_box), clear_markers,
371 TRUE, TRUE, 0);
372
373 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
374 gtk_scrolled_window_add_with_viewport
375 (GTK_SCROLLED_WINDOW(scrolled_window), markers);
376 gtk_scrolled_window_set_policy
377 (GTK_SCROLLED_WINDOW(scrolled_window),
378 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
379
380 marker_box = gtk_vbox_new(FALSE, 5);
381 gtk_box_pack_start(GTK_BOX(marker_box),
382 GTK_WIDGET(scrolled_window), TRUE, TRUE, 0);
383 gtk_box_pack_start(GTK_BOX(marker_box),
384 marker_buttons_box, FALSE, FALSE, 0);
385
386 return marker_box;
387 }
388
389 /*
390 * Create test-related widgets and lay them into test_box.
391 */
392 static GtkWidget *
create_test_box(void)393 create_test_box(void)
394 {
395 GtkWidget *test_box = gtk_hbox_new(FALSE, 5);
396 GtkWidget *decide = gtk_button_new_with_mnemonic("_Decide X");
397 g_signal_connect(G_OBJECT(decide), "clicked",
398 G_CALLBACK(decide_button_clicked), NULL);
399 trial_label = gtk_label_new("(no test in progress)");
400 gtk_box_pack_start(GTK_BOX(test_box), trial_label, FALSE, FALSE, 0);
401 gtk_box_pack_end(GTK_BOX(test_box), decide, FALSE, FALSE, 0);
402 return test_box;
403 }
404
405 /*
406 * Create metadata-related widgets and lay them into a vbox. Return
407 * the box.
408 */
409 static GtkWidget *
create_metadata_box(void)410 create_metadata_box(void)
411 {
412 GtkWidget *meta_a_hbox;
413 GtkWidget *meta_b_hbox;
414 GtkWidget *meta_vbox;
415 meta_a_label = gtk_label_new("Sample A: (none)");
416 meta_b_label = gtk_label_new("Sample B: (none)");
417 meta_a_hbox = gtk_hbox_new(FALSE, 0);
418 meta_b_hbox = gtk_hbox_new(FALSE, 0);
419 gtk_box_pack_start(GTK_BOX(meta_a_hbox), meta_a_label, FALSE, FALSE, 0);
420 gtk_box_pack_start(GTK_BOX(meta_b_hbox), meta_b_label, FALSE, FALSE, 0);
421 meta_vbox = gtk_vbox_new(TRUE, 5);
422 gtk_box_pack_start(GTK_BOX(meta_vbox), meta_a_hbox, FALSE, FALSE, 0);
423 gtk_box_pack_start(GTK_BOX(meta_vbox), meta_b_hbox, FALSE, FALSE, 0);
424 return meta_vbox;
425 }
426
427 /*
428 * Create menu bar and items. Return the menu bar.
429 */
430 static GtkWidget *
create_menu_bar(void)431 create_menu_bar(void)
432 {
433 GtkWidget *menu_bar;
434 GtkWidget *test_menu_item;
435 GtkWidget *test_menu;
436 GtkWidget *quit_menu_item;
437 GtkWidget *help_menu;
438 GtkWidget *help_menu_item;
439
440 test_menu = gtk_menu_new();
441 new_test_menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_NEW,
442 NULL);
443 quit_menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL);
444 gtk_menu_shell_append(GTK_MENU_SHELL (test_menu), new_test_menu_item);
445 gtk_menu_shell_append(GTK_MENU_SHELL (test_menu), quit_menu_item);
446 g_signal_connect(G_OBJECT (new_test_menu_item), "activate",
447 G_CALLBACK(menu_item_clicked), (gpointer) "test.new");
448 g_signal_connect_swapped(G_OBJECT(quit_menu_item), "activate",
449 G_CALLBACK(gtk_widget_destroy),
450 G_OBJECT(main_window));
451 test_menu_item = gtk_menu_item_new_with_mnemonic ("_Test");
452 gtk_menu_item_set_submenu(GTK_MENU_ITEM (test_menu_item),
453 test_menu);
454
455 help_menu = gtk_menu_new();
456 about_menu_item = gtk_image_menu_item_new_from_stock
457 (GTK_STOCK_ABOUT, NULL);
458 gtk_menu_shell_append(GTK_MENU_SHELL (help_menu), about_menu_item);
459 g_signal_connect(G_OBJECT (about_menu_item), "activate",
460 G_CALLBACK (menu_item_clicked),
461 (gpointer) "help.about");
462 help_menu_item = gtk_menu_item_new_with_mnemonic("_Help");
463 gtk_menu_item_set_submenu (GTK_MENU_ITEM (help_menu_item),
464 help_menu);
465
466 menu_bar = gtk_menu_bar_new();
467 gtk_menu_bar_append(GTK_MENU_BAR(menu_bar), test_menu_item);
468 gtk_menu_bar_append(GTK_MENU_BAR(menu_bar), help_menu_item);
469
470 return menu_bar;
471 }
472
473 /*
474 * Create the status bar.
475 */
476 static void
create_status_bar(void)477 create_status_bar(void)
478 {
479 status_bar = gtk_statusbar_new();
480 play_context = (gtk_statusbar_get_context_id
481 (GTK_STATUSBAR(status_bar), "Playback status"));
482 non_play_context = (gtk_statusbar_get_context_id
483 (GTK_STATUSBAR(status_bar),
484 "Non-playback status"));
485 gtk_statusbar_push(GTK_STATUSBAR(status_bar), non_play_context,
486 "Stopped");
487 }
488
489 /*
490 * Callback functions.
491 */
492
493 /*
494 * Quit GTK+ main loop.
495 */
496 static void
destroy_event_handler(GtkWidget * widget,gpointer data)497 destroy_event_handler(GtkWidget *widget, gpointer data)
498 {
499 gtk_main_quit();
500 }
501
502 static void
marker_activated(GtkTreeView * markers,GtkTreePath * path,GtkTreeViewColumn * column,gpointer user_data)503 marker_activated(GtkTreeView *markers, GtkTreePath *path,
504 GtkTreeViewColumn *column, gpointer user_data)
505 {
506 GtkTreeIter iter;
507 gdouble marker;
508 if (!gtk_tree_model_get_iter
509 (GTK_TREE_MODEL(marker_list), &iter, path)) {
510 g_warning("can't get iterator for marked item");
511 return;
512 }
513 gtk_tree_model_get(GTK_TREE_MODEL(marker_list),
514 &iter, 0, &marker, -1);
515 seek_playback(marker);
516 update_location();
517 }
518
519 /*
520 * Handle scale button dragging.
521 */
522 static gboolean
scale_button_pressed_or_released(GtkWidget * widget,GdkEventButton * event,gpointer data)523 scale_button_pressed_or_released(GtkWidget *widget,
524 GdkEventButton *event,
525 gpointer data)
526 {
527 if (event->type == GDK_BUTTON_PRESS) {
528 is_user_seeking = 1;
529 } else if (event->type == GDK_BUTTON_RELEASE) {
530 is_user_seeking = 0;
531 seek_playback(gtk_adjustment_get_value
532 (GTK_ADJUSTMENT(adjustment)));
533 update_location();
534 }
535 return 0;
536 }
537
538 /*
539 * Add current location as marker. If playback is in progress, use
540 * playback location. Otherwise, use slider value.
541 */
542 static void
add_marker_clicked(GtkWidget * widget,gpointer data)543 add_marker_clicked(GtkWidget *widget, gpointer data)
544 {
545 GtkTreeIter new;
546 Player_state state;
547 gtk_list_store_append(marker_list, &new);
548
549 if (get_playback_state(&state) != -1) {
550 gtk_list_store_set(marker_list, &new,
551 0, state.location, -1);
552 } else {
553 gtk_list_store_set(marker_list, &new,
554 0, gtk_adjustment_get_value
555 (GTK_ADJUSTMENT(adjustment)),
556 -1);
557 }
558 }
559
560 /*
561 * Remove the selected marker.
562 */
563 static void
remove_marker_clicked(GtkWidget * widget,gpointer data)564 remove_marker_clicked(GtkWidget *widget, gpointer data)
565 {
566 GtkTreeIter iter;
567 if (gtk_tree_selection_get_selected
568 (gtk_tree_view_get_selection(GTK_TREE_VIEW(markers)),
569 NULL, &iter)) {
570 gtk_list_store_remove(marker_list, &iter);
571 }
572 }
573
574 /*
575 * Handle menu item clicks.
576 */
577 static void
menu_item_clicked(GtkWidget * widget,gpointer data)578 menu_item_clicked(GtkWidget *widget, gpointer data)
579 {
580 static char *authors[] =
581 { "Petteri Hintsanen <petterih@iki.fi>", NULL };
582
583 /* split licence text to two parts to satisfy ISO C standard */
584 static char license_1[] =
585 "This program is free software: you can redistribute it and/or modify\n"
586 "it under the terms of the GNU General Public License as published by\n"
587 "the Free Software Foundation, either version 3 of the License, or\n"
588 "(at your option) any later version.\n\n";
589
590 static char license_2[] =
591 "This program is distributed in the hope that it will be useful,\n"
592 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
593 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
594 "GNU General Public License for more details.\n\n"
595 "You should have received a copy of the GNU General Public License\n"
596 "along with this program. If not, see <http://www.gnu.org/licenses/>.";
597
598 char license[sizeof(license_1) + sizeof(license_2)];
599 strcpy(license, license_1);
600 strcat(license, license_2);
601
602 if (widget == new_test_menu_item) {
603 show_new_test_window();
604 } else if (widget == about_menu_item) {
605 gtk_show_about_dialog
606 (NULL,
607 "program-name", "ABX Tester",
608 "title", "About ABX Tester",
609 "authors", authors,
610 "comments", "Fidelity testing program.",
611 "website", "http://iki.fi/petterih/abx.html",
612 "license", license,
613 NULL);
614 }
615 }
616
617 /*
618 * Show decide dialog.
619 */
620 static void
decide_button_clicked(GtkWidget * widget,gpointer data)621 decide_button_clicked(GtkWidget *widget, gpointer data)
622 {
623 if (current_trial >= 0) {
624 stop_playback();
625 clear_status_bar();
626 show_decide_dialog(GTK_WINDOW(main_window));
627 }
628 }
629
630 /*
631 * Start playback and update the status bar.
632 */
633 static void
play_button_clicked(GtkWidget * widget,gpointer data)634 play_button_clicked(GtkWidget *widget, gpointer data)
635 {
636 GtkTreeIter iter;
637 gint sampleid;
638 gdouble marker;
639
640 if (current_trial < 0) return;
641
642 if (widget == first_sample) {
643 sampleid = 0;
644 } else if (widget == second_sample) {
645 sampleid = 1;
646 } else {
647 sampleid = get_answer(current_trial);
648 }
649
650 /* g_debug("playing sample %d", sampleid); */
651
652 /* check for marker selection */
653 if (gtk_tree_selection_get_selected
654 (gtk_tree_view_get_selection(GTK_TREE_VIEW(markers)),
655 NULL, &iter)) {
656 gtk_tree_model_get(GTK_TREE_MODEL(marker_list),
657 &iter, 0, &marker, -1);
658 /* g_debug("playing sample %d from %f", sampleid, marker); */
659 start_playback(sampleid, marker);
660 } else {
661 Player_state state;
662 if (get_playback_state(&state) != -1
663 && state.playback == PLAYING) {
664 start_playback(sampleid, state.location);
665 } else {
666 start_playback(sampleid, gtk_adjustment_get_value
667 (GTK_ADJUSTMENT(adjustment)));
668 }
669 }
670
671 clear_status_bar();
672 if (widget == first_sample) {
673 gtk_statusbar_push(GTK_STATUSBAR(status_bar),
674 play_context, "Playing sample A");
675 } else if (widget == second_sample) {
676 gtk_statusbar_push(GTK_STATUSBAR(status_bar), play_context,
677 "Playing sample B");
678 } else {
679 gtk_statusbar_push(GTK_STATUSBAR(status_bar), play_context,
680 "Playing sample X");
681 }
682
683 update_location();
684 }
685
686 /*
687 * Pause or resume the playback.
688 */
689 static void
pause_button_clicked(GtkWidget * widget,gpointer data)690 pause_button_clicked(GtkWidget *widget, gpointer data)
691 {
692 int paused = pause_or_resume_playback();
693 switch (paused) {
694 case 0:
695 gtk_statusbar_push(GTK_STATUSBAR(status_bar), play_context, "Paused");
696 break;
697 case 1:
698 gtk_statusbar_pop(GTK_STATUSBAR(status_bar), play_context);
699 break;
700 default:
701 clear_status_bar();
702 }
703 update_location();
704 }
705
706 /*
707 * Seek to the next or previous marker.
708 */
709 static void
rewind_button_clicked(GtkWidget * widget,gpointer data)710 rewind_button_clicked(GtkWidget *widget, gpointer data)
711 {
712 GtkTreeSelection *selection;
713 GtkTreeIter iter;
714 GtkTreePath *path;
715 gdouble marker;
716
717 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(markers));
718
719 if (gtk_tree_selection_get_selected(selection, NULL, &iter)
720 == FALSE) {
721 return;
722 }
723
724 path = gtk_tree_model_get_path(GTK_TREE_MODEL(marker_list),
725 &iter);
726
727 if (widget == prev_marker && gtk_tree_path_prev(path)) {
728 gtk_tree_selection_select_path(selection, path);
729 } else if (widget == next_marker) {
730 gtk_tree_path_next (path);
731 gtk_tree_selection_select_path(selection, path);
732 }
733
734 if (gtk_tree_selection_get_selected
735 (gtk_tree_view_get_selection(GTK_TREE_VIEW(markers)),
736 NULL, &iter)) {
737 gtk_tree_model_get(GTK_TREE_MODEL(marker_list),
738 &iter, 0, &marker, -1);
739 seek_playback(marker);
740 update_location();
741 }
742
743 gtk_tree_path_free(path);
744 }
745