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