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