1 /* aylet 0.3, a .AY music file player.
2  * Copyright (C) 2001-2002 Russell Marks and Ian Collier.
3  * See main.c for licence.
4  *
5  * uigtk.c - GTK+ UI code.
6  */
7 
8 #include <stdio.h>
9 #include <string.h>
10 #include <gtk/gtk.h>
11 #include <gdk/gdkkeysyms.h>
12 #include "main.h"
13 
14 #include "ui.h"
15 
16 /* button pixmaps */
17 #ifndef GTK2_6
18 #include "button1.xpm"
19 #include "button2.xpm"
20 #include "button3.xpm"
21 #include "button4.xpm"
22 #include "button5.xpm"
23 #endif
24 
25 
26 static GtkWidget *window,*vbox,*detailstbl,*detaillabel[5];
27 static GtkWidget *label_for_status,*label_for_time;
28 static GtkWidget *highspeed_widget;
29 static GtkWidget *label_for_stopafter,*label_for_fadetime;
30 
31 static int used_ui=0,need_update=1;
32 static int retval=1;
33 
34 
action(enum cb_action_tag a)35 static void action(enum cb_action_tag a)
36 {
37 /* only set retval if we get a zero return, i.e. we need to
38  * stop current track.
39  */
40 if(!action_callback(a))
41   retval=0;
42 }
43 
44 
cb_button_prev_track(void)45 static void cb_button_prev_track(void) { action(cb_prev_track); }
cb_button_next_track(void)46 static void cb_button_next_track(void) { action(cb_next_track); }
cb_button_play(void)47 static void cb_button_play(void) { action(cb_play); }
cb_button_pause(void)48 static void cb_button_pause(void) { action(cb_pause); }
cb_button_stop(void)49 static void cb_button_stop(void) { action(cb_stop); }
cb_button_restart(void)50 static void cb_button_restart(void) { action(cb_restart); }
cb_button_prev_file(void)51 static void cb_button_prev_file(void) { action(cb_prev_file); }
cb_button_next_file(void)52 static void cb_button_next_file(void) { action(cb_next_file); }
53 
cb_toggle_highspeed(void)54 static void cb_toggle_highspeed(void)
55 {
56 action(cb_highspeed);
57 }
58 
cb_doquit(void)59 static void cb_doquit(void)
60 {
61 window=NULL;
62 action(cb_quit);
63 }
64 
65 
66 /* shouldn't really be needing this, but I had trouble
67  * with a few bits and pieces.
68  */
keypress(GtkWidget * widget,GdkEventKey * event)69 gint keypress(GtkWidget *widget,GdkEventKey *event)
70 {
71 switch(event->keyval)
72   {
73   case GDK_BackSpace: case GDK_Delete:
74     action(cb_prev_file);
75     break;
76 
77   case GDK_space:
78     action(cb_next_file);
79     break;
80 
81   case GDK_q: case GDK_Escape:
82     cb_doquit();
83     break;
84 
85   case GDK_s: case GDK_S:
86     action((event->state&GDK_SHIFT_MASK)?cb_dec_stopafter:cb_inc_stopafter);
87     break;
88 
89   case GDK_f: case GDK_F:
90     action((event->state&GDK_SHIFT_MASK)?cb_dec_fadetime:cb_inc_fadetime);
91     break;
92 
93   default:
94     return(FALSE);	/* don't stop event if not handled */
95   }
96 
97 /* if we handled it, stop anything else getting the event. */
98 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget),"key_press_event");
99 
100 return(TRUE);
101 }
102 
103 
ui_initwin(void)104 static void ui_initwin(void)
105 {
106 GtkWidget *statustbl,*buttontbl;
107 GtkWidget *sep,*label,*hbox,*button,*ebox;
108 GtkWidget *pixmap;
109 GdkBitmap *bitmap,*mask;
110 int tbl_row,width;
111 
112 #ifdef GTK2
113 #define gtk_accel_group_get_default() accel
114   GtkAccelGroup *accel;
115 #endif
116 
117 window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
118 gtk_signal_connect(GTK_OBJECT(window),"destroy",
119                    GTK_SIGNAL_FUNC(cb_doquit),NULL);
120 gtk_signal_connect(GTK_OBJECT(window),"key_press_event",
121                    GTK_SIGNAL_FUNC(keypress),NULL);
122 gtk_widget_set_events(window,GDK_KEY_PRESS_MASK);
123 gtk_window_set_title(GTK_WINDOW(window),"xaylet");
124 gtk_window_set_policy(GTK_WINDOW(window),FALSE,TRUE,FALSE);	/* XXX? */
125 
126 #ifdef GTK2
127   accel = gtk_accel_group_new ();
128   gtk_window_add_accel_group (GTK_WINDOW (window), accel);
129 #endif
130 
131 /* main vbox */
132 vbox=gtk_vbox_new(FALSE,0);
133 gtk_container_add(GTK_CONTAINER(window),vbox);
134 gtk_container_set_border_width(GTK_CONTAINER(vbox),0);
135 gtk_widget_show(vbox);
136 
137 /* add file details table */
138 /* 6 columns used to get the proportions nice - left one is for
139  * description (e.g. "Filename:"), right N for value.
140  */
141 detailstbl=gtk_table_new(5,width=6,TRUE);
142 gtk_box_pack_start(GTK_BOX(vbox),detailstbl,TRUE,TRUE,0);
143 gtk_table_set_col_spacings(GTK_TABLE(detailstbl),10);
144 gtk_table_set_row_spacings(GTK_TABLE(detailstbl),2);
145 gtk_container_set_border_width(GTK_CONTAINER(detailstbl),5);
146 gtk_widget_show(detailstbl);
147 
148 /* couldn't get gtk_label_set_justify() to work (perhaps this is
149  * only relevant to multi-line labels?), but gtk_misc_set_alignment() is ok.
150  */
151 #define DO_TBL_LEFT(table,row,start,name) \
152   label=gtk_label_new(name);				\
153   gtk_misc_set_alignment(GTK_MISC(label),1.,0.5);	\
154   gtk_table_attach_defaults(GTK_TABLE(table),label,	\
155                             (start),(start)+1, (row),(row)+1); \
156   gtk_widget_show(label)
157 
158 #define DO_TBL_RIGHT(table,row,start,end,name) \
159   ebox=gtk_event_box_new();				\
160   label=gtk_label_new(name);				\
161   gtk_misc_set_alignment(GTK_MISC(label),0.,0.5);	\
162   gtk_container_add(GTK_CONTAINER(ebox),label);		\
163   gtk_widget_set_usize(label,5,5);			\
164   gtk_table_attach_defaults(GTK_TABLE(table),ebox,	\
165                             (start),(end), (row),(row)+1); \
166   gtk_widget_show(label),gtk_widget_show(ebox)
167 
168 tbl_row=0;
169 DO_TBL_LEFT(detailstbl,tbl_row,0,"File:");
170 DO_TBL_RIGHT(detailstbl,tbl_row,1,width,"");
171 detaillabel[tbl_row]=label;
172 tbl_row++;
173 DO_TBL_LEFT(detailstbl,tbl_row,0,"Misc:");
174 DO_TBL_RIGHT(detailstbl,tbl_row,1,width,"");
175 detaillabel[tbl_row]=label;
176 tbl_row++;
177 DO_TBL_LEFT(detailstbl,tbl_row,0,"Author:");
178 DO_TBL_RIGHT(detailstbl,tbl_row,1,width,"");
179 detaillabel[tbl_row]=label;
180 tbl_row++;
181 DO_TBL_LEFT(detailstbl,tbl_row,0,"Tracks:");
182 DO_TBL_RIGHT(detailstbl,tbl_row,1,width,"");
183 detaillabel[tbl_row]=label;
184 tbl_row++;
185 DO_TBL_LEFT(detailstbl,tbl_row,0,"Playing:");
186 DO_TBL_RIGHT(detailstbl,tbl_row,1,width,"");
187 detaillabel[tbl_row]=label;
188 
189 
190 sep=gtk_hseparator_new();
191 gtk_box_pack_start(GTK_BOX(vbox),sep,TRUE,TRUE,0);
192 gtk_widget_show(sep);
193 
194 
195 statustbl=gtk_table_new(2,6,TRUE);
196 gtk_box_pack_start(GTK_BOX(vbox),statustbl,TRUE,TRUE,0);
197 gtk_table_set_col_spacings(GTK_TABLE(statustbl),10);
198 gtk_table_set_row_spacings(GTK_TABLE(statustbl),2);
199 gtk_container_set_border_width(GTK_CONTAINER(statustbl),5);
200 gtk_widget_show(statustbl);
201 
202 DO_TBL_LEFT(statustbl,0, 0,"Status:");
203 DO_TBL_RIGHT(statustbl,0, 1,2,"");
204 label_for_status=label;
205 DO_TBL_LEFT(statustbl,1, 0,"Time:");
206 DO_TBL_RIGHT(statustbl,1, 1,2,"");
207 label_for_time=label;
208 
209 #ifdef GTK2
210   highspeed_widget = gtk_check_button_new_with_mnemonic ("_High speed");
211 #else
212 highspeed_widget=gtk_check_button_new_with_label("High-speed");
213 #endif
214 /* don't allow focus, looks too weird and we have shortcut */
215 GTK_WIDGET_UNSET_FLAGS(highspeed_widget,GTK_CAN_FOCUS);
216 gtk_table_attach_defaults(GTK_TABLE(statustbl),highspeed_widget, 2,4, 0,2);
217 gtk_signal_connect(GTK_OBJECT(highspeed_widget),"clicked",
218                    GTK_SIGNAL_FUNC(cb_toggle_highspeed),NULL);
219 gtk_widget_add_accelerator(highspeed_widget,"clicked",
220                            gtk_accel_group_get_default(),
221                            GDK_h,0,0);
222 gtk_widget_show(highspeed_widget);
223 
224 
225 /* XXX these should really be spin buttons */
226 DO_TBL_LEFT(statustbl,0, 4,"Stop after:");
227 DO_TBL_RIGHT(statustbl,0, 5,6,"");
228 label_for_stopafter=label;
229 DO_TBL_LEFT(statustbl,1, 4,"Fade time:");
230 DO_TBL_RIGHT(statustbl,1, 5,6,"");
231 label_for_fadetime=label;
232 
233 
234 sep=gtk_hseparator_new();
235 gtk_box_pack_start(GTK_BOX(vbox),sep,TRUE,TRUE,0);
236 gtk_widget_show(sep);
237 
238 
239 buttontbl=gtk_table_new(2,4,TRUE);
240 gtk_box_pack_start(GTK_BOX(vbox),buttontbl,TRUE,TRUE,0);
241 gtk_table_set_col_spacings(GTK_TABLE(buttontbl),5);
242 gtk_table_set_row_spacings(GTK_TABLE(buttontbl),5);
243 gtk_container_set_border_width(GTK_CONTAINER(buttontbl),5);
244 gtk_widget_show(buttontbl);
245 
246 #ifdef GTK2_6 /* >= GTK+ 2.6 */
247 #define ADD_PIXMAP_TO_BUTTON(button,xpm,stock) \
248   pixmap = gtk_image_new_from_stock (GTK_STOCK_MEDIA_##stock, \
249 				     GTK_ICON_SIZE_BUTTON); \
250   gtk_container_add (GTK_CONTAINER ((button)), pixmap); \
251   gtk_widget_show (pixmap)
252 #else /* < GTK+ 2.6 */
253 #define ADD_PIXMAP_TO_BUTTON(button,xpm,stock) \
254   bitmap=gdk_pixmap_create_from_xpm_d(window->window,&mask,	\
255                                       &(window->style->black),	\
256                                       (xpm));			\
257   pixmap=gtk_pixmap_new(bitmap,mask);				\
258   gtk_container_add(GTK_CONTAINER(button),pixmap);		\
259   gtk_widget_show(pixmap)
260 #endif
261 
262 hbox=gtk_hbox_new(TRUE,5);
263 gtk_table_attach_defaults(GTK_TABLE(buttontbl),hbox, 0,4, 0,1);
264 gtk_container_set_border_width(GTK_CONTAINER(hbox),0);
265 gtk_widget_show(hbox);
266 
267 /* XXX this seems to be needed here, but is it ok? */
268 gtk_widget_realize(window);
269 
270 button=gtk_button_new();
271 ADD_PIXMAP_TO_BUTTON(button,button1,PREVIOUS);
272 gtk_box_pack_start(GTK_BOX(hbox),button,TRUE,TRUE,0);
273 gtk_signal_connect(GTK_OBJECT(button),"clicked",
274                    GTK_SIGNAL_FUNC(cb_button_prev_track),NULL);
275 gtk_widget_add_accelerator(button,"clicked",gtk_accel_group_get_default(),
276                            GDK_z,0,0);
277 /* since I use Space, there's no point having these focusable... */
278 GTK_WIDGET_UNSET_FLAGS(button,GTK_CAN_FOCUS);
279 gtk_widget_show(button);
280 
281 button=gtk_button_new();
282 ADD_PIXMAP_TO_BUTTON(button,button2,PLAY);
283 gtk_box_pack_start(GTK_BOX(hbox),button,TRUE,TRUE,0);
284 gtk_signal_connect(GTK_OBJECT(button),"clicked",
285                    GTK_SIGNAL_FUNC(cb_button_play),NULL);
286 gtk_widget_add_accelerator(button,"clicked",gtk_accel_group_get_default(),
287                            GDK_x,0,0);
288 GTK_WIDGET_UNSET_FLAGS(button,GTK_CAN_FOCUS);
289 gtk_widget_show(button);
290 
291 button=gtk_button_new();
292 ADD_PIXMAP_TO_BUTTON(button,button3,PAUSE);
293 gtk_box_pack_start(GTK_BOX(hbox),button,TRUE,TRUE,0);
294 gtk_signal_connect(GTK_OBJECT(button),"clicked",
295                    GTK_SIGNAL_FUNC(cb_button_pause),NULL);
296 gtk_widget_add_accelerator(button,"clicked",gtk_accel_group_get_default(),
297                            GDK_c,0,0);
298 GTK_WIDGET_UNSET_FLAGS(button,GTK_CAN_FOCUS);
299 gtk_widget_show(button);
300 
301 button=gtk_button_new();
302 ADD_PIXMAP_TO_BUTTON(button,button4,STOP);
303 gtk_box_pack_start(GTK_BOX(hbox),button,TRUE,TRUE,0);
304 gtk_signal_connect(GTK_OBJECT(button),"clicked",
305                    GTK_SIGNAL_FUNC(cb_button_stop),NULL);
306 gtk_widget_add_accelerator(button,"clicked",gtk_accel_group_get_default(),
307                            GDK_v,0,0);
308 GTK_WIDGET_UNSET_FLAGS(button,GTK_CAN_FOCUS);
309 gtk_widget_show(button);
310 
311 button=gtk_button_new();
312 ADD_PIXMAP_TO_BUTTON(button,button5,NEXT);
313 gtk_box_pack_start(GTK_BOX(hbox),button,TRUE,TRUE,0);
314 gtk_signal_connect(GTK_OBJECT(button),"clicked",
315                    GTK_SIGNAL_FUNC(cb_button_next_track),NULL);
316 gtk_widget_add_accelerator(button,"clicked",gtk_accel_group_get_default(),
317                            GDK_b,0,0);
318 GTK_WIDGET_UNSET_FLAGS(button,GTK_CAN_FOCUS);
319 gtk_widget_show(button);
320 
321 #ifdef GTK2
322   button = gtk_button_new_with_mnemonic ("_Restart");
323 #else
324 button=gtk_button_new_with_label("Restart");
325 #endif
326 gtk_box_pack_start(GTK_BOX(hbox),button,TRUE,TRUE,0);
327 gtk_signal_connect(GTK_OBJECT(button),"clicked",
328                    GTK_SIGNAL_FUNC(cb_button_restart),NULL);
329 gtk_widget_add_accelerator(button,"clicked",gtk_accel_group_get_default(),
330                            GDK_r,0,0);
331 GTK_WIDGET_UNSET_FLAGS(button,GTK_CAN_FOCUS);
332 gtk_widget_show(button);
333 
334 /* second row, these go straight on the table */
335 /* backspace/delete/space are dealt with by keypress() */
336 #ifdef GTK2
337   button = gtk_button_new_with_mnemonic ("_Previous file");
338 #else
339 button=gtk_button_new_with_label("Prev File");
340 #endif
341 gtk_table_attach_defaults(GTK_TABLE(buttontbl),button, 1,2, 1,2);
342 gtk_signal_connect(GTK_OBJECT(button),"clicked",
343                    GTK_SIGNAL_FUNC(cb_button_prev_file),NULL);
344 GTK_WIDGET_UNSET_FLAGS(button,GTK_CAN_FOCUS);
345 gtk_widget_show(button);
346 
347 #ifdef GTK2
348   button = gtk_button_new_with_mnemonic("_Next file");
349 #else
350 button=gtk_button_new_with_label("Next File");
351 #endif
352 gtk_table_attach_defaults(GTK_TABLE(buttontbl),button, 2,3, 1,2);
353 gtk_signal_connect(GTK_OBJECT(button),"clicked",
354                    GTK_SIGNAL_FUNC(cb_button_next_file),NULL);
355 GTK_WIDGET_UNSET_FLAGS(button,GTK_CAN_FOCUS);
356 gtk_widget_show(button);
357 
358 
359 gtk_widget_show(window);
360 }
361 
362 
ui_draw_status(char * filename,char * misc,char * author,int track,char * playingstr)363 static void ui_draw_status(char *filename,char *misc,
364                            char *author,int track,char *playingstr)
365 {
366 static gchar buf[256];
367 char *ptr=strrchr(filename,'/');
368 int tbl_row=0;
369 
370 if(!window) return;
371 
372 gtk_label_set_text(GTK_LABEL(detaillabel[tbl_row]),ptr?ptr+1:filename);
373 tbl_row++;
374 gtk_label_set_text(GTK_LABEL(detaillabel[tbl_row]),misc);
375 tbl_row++;
376 gtk_label_set_text(GTK_LABEL(detaillabel[tbl_row]),author);
377 tbl_row++;
378 g_snprintf(buf,sizeof(buf),"%d",aydata.num_tracks);
379 gtk_label_set_text(GTK_LABEL(detaillabel[tbl_row]),buf);
380 tbl_row++;
381 g_snprintf(buf,sizeof(buf),"%d - %s",track,playingstr);
382 gtk_label_set_text(GTK_LABEL(detaillabel[tbl_row]),buf);
383 
384 gtk_label_set_text(GTK_LABEL(label_for_status),
385                    paused?"paused ":(playing?"playing":"stopped"));
386 
387 if(!stopafter)
388   gtk_label_set_text(GTK_LABEL(label_for_stopafter),"--:--");
389 else
390   {
391   g_snprintf(buf,sizeof(buf),"%2d:%02d",stopafter/60,stopafter%60);
392   gtk_label_set_text(GTK_LABEL(label_for_stopafter),buf);
393   }
394 
395 g_snprintf(buf,sizeof(buf),"%2d sec",fadetime);
396 gtk_label_set_text(GTK_LABEL(label_for_fadetime),buf);
397 }
398 
399 
ui_draw_status_timeonly(void)400 static void ui_draw_status_timeonly(void)
401 {
402 static char buf[32];
403 
404 if(!window) return;
405 
406 g_snprintf(buf,sizeof(buf),"%2d:%02d",tunetime.min,tunetime.sec);
407 gtk_label_set_text(GTK_LABEL(label_for_time),buf);
408 }
409 
410 
411 /* called per 1/50th, this deals with the usual events.
412  * returns zero if we want to stop the current track.
413  */
ui_frame(void)414 int ui_frame(void)
415 {
416 if(!window) return(0);
417 
418 retval=1;
419 
420 if(need_update)
421   {
422   need_update=0;
423   ui_draw_status(ay_filenames[ay_file], (char *)aydata.miscstr, (char *)aydata.authorstr,
424                  ay_track+1, (char *)aydata.tracks[ay_track].namestr);
425   }
426 
427 /* update time display */
428 ui_draw_status_timeonly();
429 
430 /* this should be the only place retval gets modified by actions */
431 while(gtk_events_pending())
432   gtk_main_iteration();
433 
434 return(retval);
435 }
436 
437 
438 /* called if playback status has changed without us knowing. */
ui_change_notify(void)439 void ui_change_notify(void)
440 {
441 need_update=1;
442 }
443 
444 
ui_init(int argc,char ** argv)445 void ui_init(int argc,char **argv)
446 {
447 used_ui=1;
448 
449 if(!use_ui)
450   {
451   fprintf(stderr,"xaylet: warning: "
452           "non-UI mode not supported by GTK+ version, use `aylet'.\n");
453   use_ui=1;
454   }
455 
456 gtk_init(&argc,&argv);
457 ui_initwin();
458 }
459 
460 
ui_end(void)461 void ui_end(void)
462 {
463 if(!used_ui) return;
464 
465 /* XXX can't call gtk_exit(), ui_end() is meant to return!
466  * Oh well, it seems to be coping without... :-/
467  */
468 }
469