1 /*
2 DeaDBeeF -- the music player
3 Copyright (C) 2009-2015 Alexey Yakovenko and other contributors
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17
18 2. Altered source versions must be plainly marked as such, and must not be
19 misrepresented as being the original software.
20
21 3. This notice may not be removed or altered from any source distribution.
22 */
23
24 #include <stdlib.h>
25 #include <string.h>
26 #include <assert.h>
27 #include <math.h>
28 #include "gtkui.h"
29 #include "interface.h"
30 #include "support.h"
31 #include "widgets.h"
32 #include "ddbtabstrip.h"
33 #include "ddblistview.h"
34 #include "mainplaylist.h"
35 #include "../libparser/parser.h"
36 #include "trkproperties.h"
37 #include "coverart.h"
38 #if USE_OPENGL
39 #include "gtkuigl.h"
40 #endif
41 #include "namedicons.h"
42 #include "hotkeys.h" // for building action treeview
43 #include "../../strdupa.h"
44 #include "../../fastftoi.h"
45 #include "actions.h"
46 #include "ddbseekbar.h"
47 #include "ddbvolumebar.h"
48 #include "callbacks.h"
49
50 //#define trace(...) { fprintf(stderr, __VA_ARGS__); }
51 #define trace(fmt,...)
52
53 #define min(x,y) ((x)<(y)?(x):(y))
54 #define max(x,y) ((x)>(y)?(x):(y))
55
56
57 // utility code for parsing keyvalues
58 #define get_keyvalue(s,key,val) {\
59 s = gettoken_ext (s, key, "={}();");\
60 if (!s) {\
61 return NULL;\
62 }\
63 if (s && !strcmp (key, "{")) {\
64 break;\
65 }\
66 s = gettoken_ext (s, val, "={}();");\
67 if (!s || strcmp (val, "=")) {\
68 return NULL;\
69 }\
70 s = gettoken_ext (s, val, "={}();");\
71 if (!s) {\
72 return NULL;\
73 }\
74 }
75
76 typedef struct w_creator_s {
77 const char *type;
78 const char *title; // set to NULL to avoid exposing this widget type to user
79 uint32_t flags;
80 int compat; // when this is set to 1 -- it's a backwards compatibility creator, and must be skipped in GUI
81 ddb_gtkui_widget_t *(*create_func) (void);
82 struct w_creator_s *next;
83 } w_creator_t;
84
85 static w_creator_t *w_creators;
86
87 typedef struct {
88 ddb_gtkui_widget_t base;
89 GtkWidget *label;
90 char *text;
91 } w_dummy_t;
92
93 typedef struct {
94 ddb_gtkui_widget_t base;
95 GtkWidget *box;
96 int position;
97 int locked;
98 } w_splitter_t;
99
100 typedef struct {
101 ddb_gtkui_widget_t base;
102 } w_box_t;
103
104 typedef struct {
105 ddb_gtkui_widget_t base;
106 GtkWidget *tabstrip;
107 } w_tabstrip_t;
108
109 typedef struct {
110 ddb_gtkui_widget_t base;
111 DdbListview *list;
112 int hideheaders;
113 int width;
114 } w_playlist_t;
115
116 typedef struct {
117 w_playlist_t plt;
118 DdbTabStrip *tabstrip;
119 } w_tabbed_playlist_t;
120
121 typedef struct {
122 ddb_gtkui_widget_t base;
123 GtkWidget *drawarea;
124 } w_placeholder_t;
125
126 typedef struct {
127 ddb_gtkui_widget_t base;
128 int clicked_page;
129 int active;
130 int num_tabs;
131 char **titles;
132 } w_tabs_t;
133
134 typedef struct {
135 ddb_gtkui_widget_t base;
136 GtkWidget *tree;
137 guint refresh_timeout;
138 } w_selproperties_t;
139
140 typedef struct {
141 ddb_gtkui_widget_t base;
142 GtkWidget *drawarea;
143 int widget_height;
144 int widget_width;
145 guint load_timeout_id;
146 } w_coverart_t;
147
148 typedef struct {
149 ddb_gtkui_widget_t base;
150 GtkWidget *drawarea;
151 guint drawtimer;
152 #if USE_OPENGL
153 GdkGLContext *glcontext;
154 #endif
155 float *samples;
156 int nsamples;
157 int resized;
158 intptr_t mutex;
159 cairo_surface_t *surf;
160 } w_scope_t;
161
162 // spectrum analyzer based on cairo-spectrum from audacious
163 // Copyright (c) 2011 William Pitcock <nenolod@dereferenced.org>
164 #define MAX_BANDS DDB_FREQ_BANDS
165 #define VIS_DELAY 1
166 #define VIS_DELAY_PEAK 10
167 #define VIS_FALLOFF 1
168 #define VIS_FALLOFF_PEAK 1
169 #define BAND_WIDTH 20
170 typedef struct {
171 ddb_gtkui_widget_t base;
172 GtkWidget *drawarea;
173 guint drawtimer;
174 #if USE_OPENGL
175 GdkGLContext *glcontext;
176 #endif
177 float data[DDB_FREQ_BANDS * DDB_FREQ_MAX_CHANNELS];
178 float xscale[MAX_BANDS + 1];
179 int bars[MAX_BANDS + 1];
180 int delay[MAX_BANDS + 1];
181 int peaks[MAX_BANDS + 1];
182 int delay_peak[MAX_BANDS + 1];
183 cairo_surface_t *surf;
184 } w_spectrum_t;
185
186 typedef struct {
187 ddb_gtkui_widget_t base;
188 GtkWidget *box;
189 uint64_t expand;
190 uint64_t fill;
191 unsigned homogeneous : 1;
192 } w_hvbox_t;
193
194 typedef struct {
195 ddb_gtkui_widget_t base;
196 GtkWidget *button;
197 GtkWidget *alignment;
198 GdkColor color;
199 GdkColor textcolor;
200 char *icon;
201 char *label;
202 char *action;
203 int action_ctx;
204 unsigned use_color : 1;
205 unsigned use_textcolor : 1;
206 } w_button_t;
207
208 typedef struct {
209 ddb_gtkui_widget_t base;
210 GtkWidget *seekbar;
211 gint timer;
212 float last_songpos;
213 } w_seekbar_t;
214
215 typedef struct {
216 ddb_gtkui_widget_t base;
217 } w_playtb_t;
218
219 typedef struct {
220 ddb_gtkui_widget_t base;
221 GtkWidget *volumebar;
222 } w_volumebar_t;
223
224 typedef struct {
225 ddb_gtkui_widget_t base;
226 GtkWidget *voices[8];
227 } w_ctvoices_t;
228
229 static int design_mode;
230 static ddb_gtkui_widget_t *rootwidget;
231
232 //// common functions
233
234 void
w_init(void)235 w_init (void) {
236 rootwidget = w_create ("box");
237 }
238
239 void
w_free(void)240 w_free (void) {
241 w_creator_t *next = NULL;
242 for (w_creator_t *cr = w_creators; cr; cr = next) {
243 next = cr->next;
244 free (cr);
245 }
246 w_creators = NULL;
247 }
248
249 ddb_gtkui_widget_t *
w_get_rootwidget(void)250 w_get_rootwidget (void) {
251 return rootwidget;
252 }
253
254 void
w_set_design_mode(int active)255 w_set_design_mode (int active) {
256 design_mode = active;
257 gtk_widget_queue_draw (mainwin);
258 }
259
260 int
w_get_design_mode(void)261 w_get_design_mode (void) {
262 return design_mode;
263 }
264
265 static gboolean
w_init_cb(void * data)266 w_init_cb (void *data) {
267 ddb_gtkui_widget_t *w = data;
268 if (w->init) {
269 w->init (w);
270 }
271 return FALSE;
272 }
273
274 void
w_append(ddb_gtkui_widget_t * cont,ddb_gtkui_widget_t * child)275 w_append (ddb_gtkui_widget_t *cont, ddb_gtkui_widget_t *child) {
276 child->parent = cont;
277 if (!cont->children) {
278 cont->children = child;
279 }
280 else {
281 for (ddb_gtkui_widget_t *c = cont->children; c; c = c->next) {
282 if (!c->next) {
283 c->next = child;
284 break;
285 }
286 }
287 }
288
289 if (cont->append) {
290 cont->append (cont, child);
291 }
292 if (child->init) {
293 child->init (child);
294 }
295 }
296
297 void
w_remove(ddb_gtkui_widget_t * cont,ddb_gtkui_widget_t * child)298 w_remove (ddb_gtkui_widget_t *cont, ddb_gtkui_widget_t *child) {
299 // recurse
300 while (child->children) {
301 ddb_gtkui_widget_t *c = child->children;
302 w_remove (child, c);
303 w_destroy (c);
304 }
305
306 if (cont->remove) {
307 cont->remove (cont, child);
308 }
309 child->widget = NULL;
310 ddb_gtkui_widget_t *prev = NULL;
311 for (ddb_gtkui_widget_t *c = cont->children; c; c = c->next) {
312 if (c == child) {
313 if (prev) {
314 prev->next = c->next;
315 }
316 else {
317 cont->children = c->next;
318 }
319 break;
320 }
321 prev = c;
322 }
323 child->parent = NULL;
324 }
325
326 GtkWidget *
w_get_container(ddb_gtkui_widget_t * w)327 w_get_container (ddb_gtkui_widget_t *w) {
328 if (w->get_container) {
329 return w->get_container (w);
330 }
331 return w->widget;
332 }
333
334 void
w_replace(ddb_gtkui_widget_t * w,ddb_gtkui_widget_t * from,ddb_gtkui_widget_t * to)335 w_replace (ddb_gtkui_widget_t *w, ddb_gtkui_widget_t *from, ddb_gtkui_widget_t *to) {
336 if (w->replace) {
337 w->replace (w, from, to);
338 if (to->init) {
339 g_idle_add (w_init_cb, to);
340 }
341 }
342 else {
343 w_remove (w, from);
344 w_destroy (from);
345 w_append (w, to);
346 // we don't call init here, because w_append does it automatically
347 }
348 }
349
350 // unknown widget wrapper
351 typedef struct {
352 ddb_gtkui_widget_t base;
353 GtkWidget *drawarea;
354 char *expected_type;
355 char *parms;
356 char *children;
357 } w_unknown_t;
358
359 const char *
w_unknown_load(struct ddb_gtkui_widget_s * base,const char * type,const char * s)360 w_unknown_load (struct ddb_gtkui_widget_s *base, const char *type, const char *s) {
361 w_unknown_t *w = (w_unknown_t *)base;
362 char buffer_parms[4000];
363 char buffer_children[4000];
364
365 const char *p = s;
366 while (*p && *p != '{') {
367 p++;
368 }
369
370 if (!(*p)) {
371 fprintf (stderr, "reached EOL before expected { while trying to load unknown widget %s\n", w->expected_type);
372 return NULL;
373 }
374
375 if (p - s + 1 > sizeof (buffer_parms)) {
376 fprintf (stderr, "buffer to small to load unknown widget %s\n", w->expected_type);
377 return NULL;
378 }
379
380 memcpy (buffer_parms, s, p-s);
381 buffer_parms[p-s] = 0;
382
383 p++;
384 s = p;
385
386 int nb = 1;
387 while (*p) {
388 if (*p == '{') {
389 nb++;
390 }
391 if (*p == '}') {
392 nb--;
393 if (nb == 0) {
394 break;
395 }
396 }
397 p++;
398 }
399 if (!(*p)) {
400 fprintf (stderr, "reached EOL before expected } while trying to load unknown widget %s\n", w->expected_type);
401 return NULL;
402 }
403 if (p - s + 1 > sizeof (buffer_parms)) {
404 fprintf (stderr, "buffer to small to load unknown widget %s\n", w->expected_type);
405 return NULL;
406 }
407 memcpy (buffer_children, s, p-s);
408 buffer_children[p-s] = 0;
409
410 w->parms = strdup (buffer_parms);
411 w->children = strdup (buffer_children);
412
413 // caller expects 's' to point to '}'
414 return p;
415 }
416
417 static void
w_unknown_save(struct ddb_gtkui_widget_s * base,char * s,int sz)418 w_unknown_save (struct ddb_gtkui_widget_s *base, char *s, int sz) {
419 w_unknown_t *w = (w_unknown_t *)base;
420 int l = strlen (s);
421 s += l;
422 sz -= l;
423 snprintf (s, sz, "%s%s {%s}", w->expected_type, w->parms, w->children);
424 }
425
w_unknown_destroy(ddb_gtkui_widget_t * _w)426 void w_unknown_destroy (ddb_gtkui_widget_t *_w) {
427 w_unknown_t *w = (w_unknown_t *)_w;
428 if (w->expected_type) {
429 free (w->expected_type);
430 w->expected_type = NULL;
431 }
432 if (w->parms) {
433 free (w->parms);
434 w->parms = NULL;
435 }
436 if (w->children) {
437 free (w->children);
438 w->children = NULL;
439 }
440 }
441
442 static gboolean
unknown_draw(GtkWidget * widget,cairo_t * cr,gpointer user_data)443 unknown_draw (GtkWidget *widget, cairo_t *cr, gpointer user_data) {
444 ddb_gtkui_widget_t *w = user_data;
445 cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);
446
447 cairo_set_font_size(cr, 16);
448
449 cairo_move_to(cr, 20, 30);
450
451 char s[1000];
452 snprintf (s, sizeof (s), _("Widget \"%s\" is not available"), ((w_unknown_t *)w)->expected_type);
453
454 cairo_show_text(cr, s);
455 return TRUE;
456 }
457
458 static gboolean
unknown_expose_event(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)459 unknown_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data) {
460 cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget));
461 gboolean res = unknown_draw (widget, cr, user_data);
462 cairo_destroy (cr);
463 return res;
464 }
465
466 ddb_gtkui_widget_t *
w_unknown_create(const char * type)467 w_unknown_create (const char *type) {
468 w_unknown_t *w = malloc (sizeof (w_unknown_t));
469 memset (w, 0, sizeof (w_unknown_t));
470 w->base.type = "unknown";
471 w->base.load = w_unknown_load;
472 w->base.save = w_unknown_save;
473 w->base.destroy = w_unknown_destroy;
474 w->expected_type = strdup (type);
475
476 w->base.widget = gtk_event_box_new ();
477 w->drawarea = gtk_drawing_area_new ();
478 gtk_widget_show (w->drawarea);
479 gtk_container_add (GTK_CONTAINER (w->base.widget), w->drawarea);
480 #if !GTK_CHECK_VERSION(3,0,0)
481 g_signal_connect_after ((gpointer) w->drawarea, "expose_event", G_CALLBACK (unknown_expose_event), w);
482 #else
483 g_signal_connect_after ((gpointer) w->drawarea, "draw", G_CALLBACK (unknown_draw), w);
484 #endif
485 w_override_signals (w->base.widget, w);
486 return (ddb_gtkui_widget_t *)w;
487 }
488
489 const char *
w_create_from_string(const char * s,ddb_gtkui_widget_t ** parent)490 w_create_from_string (const char *s, ddb_gtkui_widget_t **parent) {
491 char t[MAX_TOKEN];
492 s = gettoken (s, t);
493 if (!s) {
494 return NULL;
495 }
496 char *type = strdupa (t);
497 ddb_gtkui_widget_t *w = w_create (type);
498 if (!w) {
499 w = w_unknown_create (type);
500 }
501 // nuke all default children
502 while (w->children) {
503 w_remove (w, w->children);
504 }
505
506 // load widget params
507 if (w->load) {
508 s = w->load (w, type, s);
509 if (!s) {
510 w_destroy (w);
511 return NULL;
512 }
513 }
514 else {
515 // skip all params (if any)
516 for (;;) {
517 s = gettoken_ext (s, t, "={}();");
518 if (!s) {
519 w_destroy (w);
520 return NULL;
521 }
522 if (!strcmp (t, "{")) {
523 break;
524 }
525 // match '='
526 char eq[MAX_TOKEN];
527 s = gettoken_ext (s, eq, "={}();");
528 if (!s || strcmp (eq, "=")) {
529 w_destroy (w);
530 return NULL;
531 }
532 s = gettoken_ext (s, eq, "={}();");
533 if (!s) {
534 w_destroy (w);
535 return NULL;
536 }
537 }
538 }
539
540 // we don't need to match '{' here, it's already done above
541 const char *back = s;
542 s = gettoken (s, t);
543 if (!s) {
544 w_destroy (w);
545 return NULL;
546 }
547 for (;;) {
548 if (!strcmp (t, "}")) {
549 break;
550 }
551
552 s = w_create_from_string (back, &w);
553 if (!s) {
554 w_destroy (w);
555 return NULL;
556 }
557
558 back = s;
559 s = gettoken (s, t);
560 if (!s) {
561 w_destroy (w);
562 return NULL;
563 }
564 }
565
566 if (*parent) {
567 w_append (*parent, w);
568 }
569 else {
570 *parent = w;
571 }
572 return s;
573 }
574
575 static ddb_gtkui_widget_t *current_widget;
576 static int hidden = 0;
577
578 static gboolean
w_draw_event(GtkWidget * widget,cairo_t * cr,gpointer user_data)579 w_draw_event (GtkWidget *widget, cairo_t *cr, gpointer user_data) {
580 if (hidden && user_data == current_widget) {
581 GtkAllocation allocation;
582 gtk_widget_get_allocation (widget, &allocation);
583 cairo_set_source_rgb (cr, 0.17f, 0, 0.83f);
584
585 if (!gtk_widget_get_has_window (widget)) {
586 #if GTK_CHECK_VERSION(3,0,0)
587 cairo_translate (cr, -allocation.x, -allocation.y);
588 #endif
589 cairo_reset_clip (cr);
590 cairo_rectangle (cr, allocation.x, allocation.y, allocation.width, allocation.height);
591 }
592 else {
593 cairo_reset_clip (cr);
594 cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
595 }
596 cairo_fill (cr);
597 }
598 return FALSE;
599 }
600
601 gboolean
w_expose_event(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)602 w_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data) {
603 cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget));
604 gboolean res = w_draw_event (widget, cr, user_data);
605 cairo_destroy (cr);
606 return res;
607 }
608
609 static char paste_buffer[20000];
610
611 void
save_widget_to_string(char * str,int sz,ddb_gtkui_widget_t * w)612 save_widget_to_string (char *str, int sz, ddb_gtkui_widget_t *w) {
613 // uknown is special case
614 if (!strcmp (w->type, "unknown")) {
615 w->save (w, str, sz);
616 return;
617 }
618
619 strcat (str, w->type);
620 if (w->save) {
621 w->save (w, str, sz);
622 }
623
624 strcat (str, " {");
625 for (ddb_gtkui_widget_t *c = w->children; c; c = c->next) {
626 save_widget_to_string (str, sz, c);
627 }
628 strcat (str, "} ");
629 }
630
631 void
w_save(void)632 w_save (void) {
633 char buf[20000] = "";
634 save_widget_to_string (buf, sizeof (buf), rootwidget->children);
635 deadbeef->conf_set_str (DDB_GTKUI_CONF_LAYOUT, buf);
636 deadbeef->conf_save ();
637 }
638
639 static void
on_replace_activate(GtkMenuItem * menuitem,gpointer user_data)640 on_replace_activate (GtkMenuItem *menuitem, gpointer user_data) {
641 for (w_creator_t *cr = w_creators; cr; cr = cr->next) {
642 if (cr->type == user_data) {
643 // hack for single-instance
644 // first replace with a placeholder, so that the original widget is destroyed
645 // then do the real replacement
646 ddb_gtkui_widget_t *w = w_create ("placeholder");
647 w_replace (current_widget->parent, current_widget, w);
648 current_widget = w;
649 w = w_create (user_data);
650 w_replace (current_widget->parent, current_widget, w);
651 current_widget = w;
652 }
653 }
654 w_save ();
655 }
656
657 static void
on_delete_activate(GtkMenuItem * menuitem,gpointer user_data)658 on_delete_activate (GtkMenuItem *menuitem, gpointer user_data) {
659 ddb_gtkui_widget_t *parent = current_widget->parent;
660 if (!strcmp (current_widget->type, "placeholder")) {
661 return;
662 }
663 if (parent->replace) {
664 parent->replace (parent, current_widget, w_create ("placeholder"));
665 }
666 else {
667 w_remove (parent, current_widget);
668 w_destroy (current_widget);
669 current_widget = w_create ("placeholder");
670 w_append (parent, current_widget);
671 }
672 w_save ();
673 }
674
675 static void
on_cut_activate(GtkMenuItem * menuitem,gpointer user_data)676 on_cut_activate (GtkMenuItem *menuitem, gpointer user_data) {
677 ddb_gtkui_widget_t *parent = current_widget->parent;
678 if (!strcmp (current_widget->type, "placeholder")) {
679 return;
680 }
681 // save hierarchy to string
682 // FIXME: use real clipboard
683 paste_buffer[0] = 0;
684 save_widget_to_string (paste_buffer, sizeof (paste_buffer), current_widget);
685
686 if (parent->replace) {
687 parent->replace (parent, current_widget, w_create ("placeholder"));
688 }
689 else {
690 w_remove (parent, current_widget);
691 w_destroy (current_widget);
692 current_widget = w_create ("placeholder");
693 w_append (parent, current_widget);
694 }
695 w_save ();
696 }
697
698 static void
on_copy_activate(GtkMenuItem * menuitem,gpointer user_data)699 on_copy_activate (GtkMenuItem *menuitem, gpointer user_data) {
700 ddb_gtkui_widget_t *parent = current_widget->parent;
701 if (!strcmp (current_widget->type, "placeholder")) {
702 return;
703 }
704 // save hierarchy to string
705 // FIXME: use real clipboard
706 paste_buffer[0] = 0;
707 save_widget_to_string (paste_buffer, sizeof (paste_buffer), current_widget);
708 }
709
710 static void
on_paste_activate(GtkMenuItem * menuitem,gpointer user_data)711 on_paste_activate (GtkMenuItem *menuitem, gpointer user_data) {
712 ddb_gtkui_widget_t *parent = current_widget->parent;
713 if (!paste_buffer[0]) {
714 return;
715 }
716
717 ddb_gtkui_widget_t *w = w_create ("placeholder");
718 w_replace (current_widget->parent, current_widget, w);
719 current_widget = w;
720
721 w = NULL;
722 w_create_from_string (paste_buffer, &w);
723 w_replace (parent, current_widget, w);
724 w_save ();
725 current_widget = w;
726 }
727
728 void
hide_widget(GtkWidget * widget,gpointer data)729 hide_widget (GtkWidget *widget, gpointer data) {
730 if (data) {
731 GtkAllocation *a = data;
732 gtk_widget_get_allocation (widget, a);
733 }
734 gtk_widget_hide (widget);
735 }
736
737 void
show_widget(GtkWidget * widget,gpointer data)738 show_widget (GtkWidget *widget, gpointer data) {
739 gtk_widget_show (widget);
740 }
741
742 static GtkRequisition prev_req;
743
744 void
w_menu_deactivate(GtkMenuShell * menushell,gpointer user_data)745 w_menu_deactivate (GtkMenuShell *menushell, gpointer user_data) {
746 hidden = 0;
747 ddb_gtkui_widget_t *w = user_data;
748 if (GTK_IS_CONTAINER (w->widget)) {
749 gtk_container_foreach (GTK_CONTAINER (w->widget), show_widget, NULL);
750 gtk_widget_set_size_request (w->widget, prev_req.width, prev_req.height);
751 }
752 gtk_widget_set_app_paintable (w->widget, FALSE);
753 gtk_widget_queue_draw (w->widget);
754 }
755
756 gboolean
w_button_press_event(GtkWidget * widget,GdkEventButton * event,gpointer user_data)757 w_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer user_data) {
758 if (!design_mode || !TEST_RIGHT_CLICK(event)) {
759 return FALSE;
760 }
761
762 current_widget = user_data;
763 widget = current_widget->widget;
764 hidden = 1;
765
766 if (GTK_IS_CONTAINER (widget)) {
767 // hide all children
768 #if !GTK_CHECK_VERSION(3,0,0)
769 gtk_widget_size_request (widget, &prev_req);
770 #else
771 gtk_widget_get_preferred_size (widget, NULL, &prev_req);
772 #endif
773 gtk_container_foreach (GTK_CONTAINER (widget), hide_widget, NULL);
774 gtk_widget_set_size_request (widget, prev_req.width, prev_req.height);
775 }
776
777 gtk_widget_set_app_paintable (widget, TRUE);
778 gtk_widget_queue_draw (((ddb_gtkui_widget_t *)user_data)->widget);
779 GtkWidget *menu;
780 GtkWidget *submenu;
781 GtkWidget *item;
782 menu = gtk_menu_new ();
783 if (strcmp (current_widget->type, "placeholder")) {
784 item = gtk_menu_item_new_with_mnemonic (_("Replace with..."));
785 gtk_widget_show (item);
786 gtk_container_add (GTK_CONTAINER (menu), item);
787 }
788 else {
789 item = gtk_menu_item_new_with_mnemonic (_("Insert..."));
790 gtk_widget_show (item);
791 gtk_container_add (GTK_CONTAINER (menu), item);
792 }
793
794 submenu = gtk_menu_new ();
795 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
796
797 for (w_creator_t *cr = w_creators; cr; cr = cr->next) {
798 if (cr->title) {
799 item = gtk_menu_item_new_with_mnemonic (cr->title);
800 gtk_widget_show (item);
801 gtk_container_add (GTK_CONTAINER (submenu), item);
802 g_signal_connect ((gpointer) item, "activate",
803 G_CALLBACK (on_replace_activate),
804 (void *)cr->type);
805 }
806 }
807
808 if (strcmp (current_widget->type, "placeholder")) {
809 item = gtk_menu_item_new_with_mnemonic (_("Delete"));
810 gtk_widget_show (item);
811 gtk_container_add (GTK_CONTAINER (menu), item);
812 g_signal_connect ((gpointer) item, "activate",
813 G_CALLBACK (on_delete_activate),
814 NULL);
815
816 item = gtk_menu_item_new_with_mnemonic (_("Cut"));
817 gtk_widget_show (item);
818 gtk_container_add (GTK_CONTAINER (menu), item);
819 g_signal_connect ((gpointer) item, "activate",
820 G_CALLBACK (on_cut_activate),
821 NULL);
822
823 item = gtk_menu_item_new_with_mnemonic (_("Copy"));
824 gtk_widget_show (item);
825 gtk_container_add (GTK_CONTAINER (menu), item);
826 g_signal_connect ((gpointer) item, "activate",
827 G_CALLBACK (on_copy_activate),
828 NULL);
829 }
830 item = gtk_menu_item_new_with_mnemonic (_("Paste"));
831 gtk_widget_show (item);
832 gtk_container_add (GTK_CONTAINER (menu), item);
833 g_signal_connect ((gpointer) item, "activate",
834 G_CALLBACK (on_paste_activate),
835 NULL);
836
837 if (current_widget->initmenu) {
838 current_widget->initmenu (current_widget, menu);
839 }
840 if (current_widget->parent && current_widget->parent->initchildmenu) {
841 current_widget->parent->initchildmenu (current_widget, menu);
842 }
843
844 g_signal_connect ((gpointer) menu, "deactivate", G_CALLBACK (w_menu_deactivate), user_data);
845 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, widget, 0, gtk_get_current_event_time());
846 return TRUE;
847 }
848
849 void
w_override_signals(GtkWidget * widget,gpointer user_data)850 w_override_signals (GtkWidget *widget, gpointer user_data) {
851 g_signal_connect ((gpointer) widget, "button_press_event", G_CALLBACK (w_button_press_event), user_data);
852 #if !GTK_CHECK_VERSION(3,0,0)
853 g_signal_connect ((gpointer) widget, "expose_event", G_CALLBACK (w_expose_event), user_data);
854 #else
855 g_signal_connect ((gpointer) widget, "draw", G_CALLBACK (w_draw_event), user_data);
856 #endif
857 if (GTK_IS_CONTAINER (widget)) {
858 gtk_container_forall (GTK_CONTAINER (widget), w_override_signals, user_data);
859 }
860 }
861
862 void
w_reg_widget(const char * title,uint32_t flags,ddb_gtkui_widget_t * (* create_func)(void),...)863 w_reg_widget (const char *title, uint32_t flags, ddb_gtkui_widget_t *(*create_func) (void), ...) {
864 int compat = 0;
865
866 va_list vl;
867 va_start (vl, create_func);
868 for (;;) {
869 const char *type = va_arg(vl, const char *);
870 if (!type) {
871 break;
872 }
873 w_creator_t *c;
874 for (c = w_creators; c; c = c->next) {
875 if (!strcmp (c->type, type)) {
876 fprintf (stderr, "gtkui w_reg_widget: widget type %s already registered\n", type);
877 return;
878 }
879 }
880 c = malloc (sizeof (w_creator_t));
881 memset (c, 0, sizeof (w_creator_t));
882 c->type = type;
883 c->title = title;
884 c->flags = flags;
885 c->compat = compat;
886 c->create_func = create_func;
887 c->next = w_creators;
888 w_creators = c;
889 compat = 1;
890 }
891 va_end(vl);
892 }
893
894 void
w_unreg_widget(const char * type)895 w_unreg_widget (const char *type) {
896 w_creator_t *c, *prev = NULL;
897 for (c = w_creators; c; c = c->next) {
898 if (!strcmp (c->type, type)) {
899 if (prev) {
900 prev->next = c->next;
901 }
902 else {
903 w_creators = c->next;
904 }
905 free (c);
906 return;
907 }
908 prev = c;
909 }
910 trace ("gtkui w_unreg_widget: widget type %s is not registered\n", type);
911 }
912
913 int
w_is_registered(const char * type)914 w_is_registered (const char *type) {
915 for (w_creator_t *c = w_creators; c; c = c->next) {
916 if (!strcmp (c->type, type)) {
917 return 1;
918 }
919 }
920 return 0;
921 }
922
923 static int
get_num_widgets(ddb_gtkui_widget_t * w,const char * type)924 get_num_widgets (ddb_gtkui_widget_t *w, const char *type) {
925 int num = 0;
926 if (!strcmp (w->type, type)) {
927 num++;
928 }
929 for (w = w->children; w; w = w->next) {
930 num += get_num_widgets (w, type);
931 }
932 return num;
933 }
934
935
936 ddb_gtkui_widget_t *
w_create(const char * type)937 w_create (const char *type) {
938 for (w_creator_t *c = w_creators; c; c = c->next) {
939 if (!strcmp (c->type, type)) {
940 if (c->flags & DDB_WF_SINGLE_INSTANCE) {
941 int num = get_num_widgets (rootwidget, c->type);
942 // HACK: playlist and tabbed playlist are essentially the same
943 // widgets with single-instance limit
944
945 if (!strcmp (c->type, "tabbed_playlist")) {
946 num += get_num_widgets (rootwidget, "playlist");
947 }
948 else if (!strcmp (c->type, "playlist")) {
949 num += get_num_widgets (rootwidget, "tabbed_playlist");
950 }
951 if (num) {
952 // create dummy
953 w_dummy_t *w = (w_dummy_t *)w_create ("dummy");
954 w->text = strdup (_("Multiple widgets of this type are not supported"));
955 return (ddb_gtkui_widget_t *)w;
956
957 }
958 }
959 ddb_gtkui_widget_t *w = c->create_func ();
960 w->type = c->type;
961
962 return w;
963 }
964 }
965 return NULL;
966 }
967
968 void
w_destroy(ddb_gtkui_widget_t * w)969 w_destroy (ddb_gtkui_widget_t *w) {
970 if (w->destroy) {
971 w->destroy (w);
972 }
973 if (w->widget) {
974 gtk_widget_destroy (w->widget);
975 }
976 free (w);
977 }
978
979 ///// gtk_container convenience functions
980 void
w_container_add(ddb_gtkui_widget_t * cont,ddb_gtkui_widget_t * child)981 w_container_add (ddb_gtkui_widget_t *cont, ddb_gtkui_widget_t *child) {
982 GtkWidget *container = w_get_container (cont);
983 gtk_container_add (GTK_CONTAINER (container), child->widget);
984 gtk_widget_show (child->widget);
985 }
986
987 void
w_container_remove(ddb_gtkui_widget_t * cont,ddb_gtkui_widget_t * child)988 w_container_remove (ddb_gtkui_widget_t *cont, ddb_gtkui_widget_t *child) {
989 GtkWidget *container = NULL;
990 container = cont->widget;
991 gtk_container_remove (GTK_CONTAINER (container), child->widget);
992
993 }
994
995 ////// placeholder widget
996 gboolean
w_placeholder_draw(GtkWidget * widget,cairo_t * cr,gpointer user_data)997 w_placeholder_draw (GtkWidget *widget, cairo_t *cr, gpointer user_data) {
998 if (!design_mode) {
999 return FALSE;
1000 }
1001 cairo_set_source_rgb (cr, 255, 0, 0);
1002 cairo_surface_t *checker;
1003 cairo_t *cr2;
1004
1005 checker = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 12, 12);
1006 cr2 = cairo_create (checker);
1007
1008 cairo_set_source_rgb (cr2, 0.5, 0.5 ,0.5);
1009 cairo_paint (cr2);
1010 cairo_set_source_rgb (cr2, 0, 0, 0);
1011 cairo_move_to (cr2, 0, 0);
1012 cairo_line_to (cr2, 12, 12);
1013 cairo_move_to (cr2, 1, 12);
1014 cairo_line_to (cr2, 12, 1);
1015 cairo_set_line_width (cr2, 1);
1016 cairo_set_antialias (cr2, CAIRO_ANTIALIAS_NONE);
1017 cairo_stroke (cr2);
1018 cairo_fill (cr2);
1019 cairo_destroy (cr2);
1020
1021 cairo_set_source_surface (cr, checker, 0, 0);
1022 cairo_pattern_t *pt = cairo_get_source(cr);
1023 cairo_pattern_set_extend (pt, CAIRO_EXTEND_REPEAT);
1024 GtkAllocation a;
1025 gtk_widget_get_allocation (widget, &a);
1026 cairo_rectangle (cr, 0, 0, a.width, a.height);
1027 cairo_paint (cr);
1028 cairo_surface_destroy (checker);
1029 return FALSE;
1030 }
1031
1032 gboolean
w_placeholder_expose_event(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)1033 w_placeholder_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data) {
1034 cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget));
1035 gboolean res = w_placeholder_draw (widget, cr, user_data);
1036 cairo_destroy (cr);
1037 return res;
1038 }
1039
1040 ddb_gtkui_widget_t *
w_placeholder_create(void)1041 w_placeholder_create (void) {
1042 w_placeholder_t *w = malloc (sizeof (w_placeholder_t));
1043 memset (w, 0, sizeof (w_placeholder_t));
1044
1045 w->base.widget = gtk_event_box_new ();
1046 w->drawarea = gtk_drawing_area_new ();
1047 gtk_widget_set_size_request (w->base.widget, 20, 20);
1048 gtk_widget_show (w->drawarea);
1049 gtk_container_add (GTK_CONTAINER (w->base.widget), w->drawarea);
1050
1051 #if !GTK_CHECK_VERSION(3,0,0)
1052 g_signal_connect_after ((gpointer) w->drawarea, "expose_event", G_CALLBACK (w_placeholder_expose_event), w);
1053 #else
1054 g_signal_connect_after ((gpointer) w->drawarea, "draw", G_CALLBACK (w_placeholder_draw), w);
1055 #endif
1056 w_override_signals (w->base.widget, w);
1057 return (ddb_gtkui_widget_t*)w;
1058 }
1059
1060 // dummy widget
1061 static const char *
w_dummy_load(struct ddb_gtkui_widget_s * w,const char * type,const char * s)1062 w_dummy_load (struct ddb_gtkui_widget_s *w, const char *type, const char *s) {
1063 w_dummy_t *b = (w_dummy_t *)w;
1064 char key[MAX_TOKEN], val[MAX_TOKEN];
1065 for (;;) {
1066 get_keyvalue (s, key, val);
1067 if (!strcmp (key, "text")) {
1068 b->text = val[0] ? strdup (val) : NULL;
1069 }
1070 }
1071
1072 return s;
1073 }
1074
1075 static void
w_dummy_save(struct ddb_gtkui_widget_s * w,char * s,int sz)1076 w_dummy_save (struct ddb_gtkui_widget_s *w, char *s, int sz) {
1077 char save[1000] = "";
1078 char *pp = save;
1079 int ss = sizeof (save);
1080 int n;
1081
1082 w_dummy_t *b = (w_dummy_t *)w;
1083 if (b->text) {
1084 n = snprintf (pp, ss, " text=\"%s\"", b->text);
1085 ss -= n;
1086 pp += n;
1087 }
1088
1089 strncat (s, save, sz);
1090 }
1091
1092 void
w_dummy_init(ddb_gtkui_widget_t * wbase)1093 w_dummy_init (ddb_gtkui_widget_t *wbase) {
1094 w_dummy_t *w = (w_dummy_t *)wbase;
1095 if (w->label) {
1096 gtk_widget_destroy (w->label);
1097 w->label = NULL;
1098 }
1099 if (w->text) {
1100 w->label = gtk_label_new_with_mnemonic (w->text);
1101 gtk_widget_show (w->label);
1102 gtk_container_add (GTK_CONTAINER (w->base.widget), w->label);
1103 }
1104 }
1105
1106 static void
w_dummy_destroy(ddb_gtkui_widget_t * wbase)1107 w_dummy_destroy (ddb_gtkui_widget_t *wbase) {
1108 w_dummy_t *w = (w_dummy_t *)wbase;
1109 if (w->text) {
1110 free (w->text);
1111 w->text = NULL;
1112 }
1113 }
1114
1115 ddb_gtkui_widget_t *
w_dummy_create(void)1116 w_dummy_create (void) {
1117 w_dummy_t *w = (w_dummy_t *)malloc (sizeof (w_dummy_t));
1118 memset (w, 0, sizeof (w_dummy_t));
1119 w->base.widget = gtk_event_box_new ();
1120 w->base.init = w_dummy_init;
1121 w->base.destroy = w_dummy_destroy;
1122 w->base.load = w_dummy_load;
1123 w->base.save = w_dummy_save;
1124 w_override_signals (w->base.widget, w);
1125 return (ddb_gtkui_widget_t *)w;
1126 }
1127
1128 // common splitter funcs
1129 const char *
w_splitter_load(struct ddb_gtkui_widget_s * w,const char * type,const char * s)1130 w_splitter_load (struct ddb_gtkui_widget_s *w, const char *type, const char *s) {
1131 if (strcmp (type, "vsplitter") && strcmp (type, "hsplitter")) {
1132 return NULL;
1133 }
1134 char key[MAX_TOKEN], val[MAX_TOKEN];
1135 for (;;) {
1136 get_keyvalue (s,key,val);
1137
1138 if (!strcmp (key, "pos")) {
1139 ((w_splitter_t *)w)->position = atoi (val);
1140 }
1141 else if (!strcmp (key, "locked")) {
1142 ((w_splitter_t *)w)->locked = atoi (val);
1143 }
1144 }
1145
1146 return s;
1147 }
1148
1149 void
w_splitter_save(struct ddb_gtkui_widget_s * w,char * s,int sz)1150 w_splitter_save (struct ddb_gtkui_widget_s *w, char *s, int sz) {
1151 int pos = ((w_splitter_t *)w)->locked ? ((w_splitter_t *)w)->position : gtk_paned_get_position (GTK_PANED(((w_splitter_t *)w)->box));
1152 char spos[100];
1153 snprintf (spos, sizeof (spos), " pos=%d locked=%d", pos, ((w_splitter_t *)w)->locked);
1154 strncat (s, spos, sz);
1155 }
1156
1157 void
w_splitter_add(ddb_gtkui_widget_t * w,ddb_gtkui_widget_t * child)1158 w_splitter_add (ddb_gtkui_widget_t *w, ddb_gtkui_widget_t *child) {
1159 w_container_add (w, child);
1160 if (((w_splitter_t *)w)->locked) {
1161 if (child == w->children) {
1162 if (GTK_IS_VBOX (((w_splitter_t *)w)->box)) {
1163 gtk_widget_set_size_request (child->widget, -1, ((w_splitter_t *)w)->position);
1164 }
1165 else {
1166 gtk_widget_set_size_request (child->widget, ((w_splitter_t *)w)->position, -1);
1167 }
1168 }
1169 }
1170 else {
1171 gtk_paned_set_position (GTK_PANED(((w_splitter_t *)w)->box), ((w_splitter_t *)w)->position);
1172 }
1173 }
1174
1175 GtkWidget *
w_splitter_get_container(struct ddb_gtkui_widget_s * b)1176 w_splitter_get_container (struct ddb_gtkui_widget_s *b) {
1177 return ((w_splitter_t *)b)->box;
1178 }
1179
1180 void
w_splitter_lock(w_splitter_t * w)1181 w_splitter_lock (w_splitter_t *w) {
1182 // we can't change GtkPaned behavior, so convert to vbox for now
1183 if (w->locked) {
1184 return;
1185 }
1186 w->locked = 1;
1187
1188 int vert = w->base.type == "vsplitter";
1189
1190 GtkAllocation a;
1191 gtk_widget_get_allocation (w->base.widget, &a);
1192
1193 GtkWidget *box = vert ? gtk_vbox_new (FALSE, 3) : gtk_hbox_new (FALSE, 3);
1194 gtk_widget_show (box);
1195
1196 w->position = gtk_paned_get_position (GTK_PANED (w->box));
1197
1198 GtkWidget *c1 = gtk_paned_get_child1 (GTK_PANED (w->box));
1199 g_object_ref (c1);
1200 GtkWidget *c2 = gtk_paned_get_child2 (GTK_PANED (w->box));
1201 g_object_ref (c2);
1202 gtk_container_remove (GTK_CONTAINER (w->box), c1);
1203 gtk_container_remove (GTK_CONTAINER (w->box), c2);
1204
1205 gtk_box_pack_start (GTK_BOX (box), c1, FALSE, FALSE, 0);
1206 gtk_widget_set_size_request (c1, vert ? -1 : w->position, vert ? w->position : -1);
1207 gtk_box_pack_end (GTK_BOX (box), c2, TRUE, TRUE, 0);
1208
1209 gtk_container_remove (GTK_CONTAINER (w->base.widget), w->box);
1210 gtk_container_add (GTK_CONTAINER (w->base.widget), box);
1211 w->box = box;
1212 }
1213
1214 void
w_splitter_unlock(w_splitter_t * w)1215 w_splitter_unlock (w_splitter_t *w) {
1216 if (!w->locked) {
1217 return;
1218 }
1219 w->locked = 0;
1220
1221 int vert = w->base.type == "vsplitter";
1222 // convert back to vpaned
1223 GtkWidget *paned = vert ? gtk_vpaned_new () : gtk_hpaned_new ();
1224 gtk_widget_set_can_focus (paned, FALSE);
1225 gtk_widget_show (paned);
1226
1227 GList *lst = gtk_container_get_children (GTK_CONTAINER (w->box));
1228
1229 GtkWidget *c1 = lst->data;
1230 g_object_ref (c1);
1231 GtkWidget *c2 = lst->next->data;
1232 g_object_ref (c2);
1233 gtk_container_remove (GTK_CONTAINER (w->box), c1);
1234 gtk_container_remove (GTK_CONTAINER (w->box), c2);
1235
1236 gtk_container_add (GTK_CONTAINER (paned), c1);
1237 gtk_container_add (GTK_CONTAINER (paned), c2);
1238 gtk_paned_set_position (GTK_PANED (paned), w->position);
1239
1240 gtk_container_remove (GTK_CONTAINER (w->base.widget), w->box);
1241 gtk_container_add (GTK_CONTAINER (w->base.widget), paned);
1242 w->box = paned;
1243 }
1244
1245 void
on_splitter_lock_movement_toggled(GtkCheckMenuItem * checkmenuitem,gpointer user_data)1246 on_splitter_lock_movement_toggled (GtkCheckMenuItem *checkmenuitem, gpointer user_data) {
1247 if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (checkmenuitem))) {
1248 w_splitter_lock (user_data);
1249 }
1250 else {
1251 w_splitter_unlock (user_data);
1252 }
1253 }
1254
1255 void
w_splitter_initmenu(struct ddb_gtkui_widget_s * w,GtkWidget * menu)1256 w_splitter_initmenu (struct ddb_gtkui_widget_s *w, GtkWidget *menu) {
1257 GtkWidget *item;
1258 item = gtk_check_menu_item_new_with_mnemonic (_("Lock movement"));
1259 gtk_widget_show (item);
1260 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), ((w_splitter_t *)w)->locked);
1261 gtk_container_add (GTK_CONTAINER (menu), item);
1262 g_signal_connect ((gpointer) item, "toggled",
1263 G_CALLBACK (on_splitter_lock_movement_toggled),
1264 w);
1265 }
1266
1267 void
w_splitter_replace(ddb_gtkui_widget_t * cont,ddb_gtkui_widget_t * child,ddb_gtkui_widget_t * newchild)1268 w_splitter_replace (ddb_gtkui_widget_t *cont, ddb_gtkui_widget_t *child, ddb_gtkui_widget_t *newchild) {
1269 int ntab = 0;
1270 ddb_gtkui_widget_t *prev = NULL;
1271 for (ddb_gtkui_widget_t *c = cont->children; c; c = c->next, ntab++) {
1272 if (c == child) {
1273 newchild->next = c->next;
1274 if (prev) {
1275 prev->next = newchild;
1276 }
1277 else {
1278 cont->children = newchild;
1279 }
1280 newchild->parent = cont;
1281 w_remove (cont, child);
1282 w_destroy (child);
1283 GtkWidget *container = ((w_splitter_t *)cont)->box;
1284 gtk_widget_show (newchild->widget);
1285 if (((w_splitter_t *)cont)->locked) {
1286 if (ntab == 0) {
1287 gtk_box_pack_start (GTK_BOX (container), newchild->widget, TRUE, TRUE, 0);
1288 }
1289 else {
1290 gtk_box_pack_end (GTK_BOX (container), newchild->widget, TRUE, TRUE, 0);
1291 }
1292 }
1293 else {
1294 if (ntab == 0) {
1295 gtk_paned_add1 (GTK_PANED (container), newchild->widget);
1296 }
1297 else {
1298 gtk_paned_add2 (GTK_PANED (container), newchild->widget);
1299 }
1300 }
1301 break;
1302 }
1303 prev = c;
1304 }
1305 }
1306
1307 void
w_splitter_remove(ddb_gtkui_widget_t * cont,ddb_gtkui_widget_t * child)1308 w_splitter_remove (ddb_gtkui_widget_t *cont, ddb_gtkui_widget_t *child) {
1309 GtkWidget *container = w_get_container (cont);
1310 gtk_container_remove (GTK_CONTAINER (container), child->widget);
1311 }
1312
1313 ////// vsplitter widget
1314 void
w_vsplitter_init(ddb_gtkui_widget_t * base)1315 w_vsplitter_init (ddb_gtkui_widget_t *base) {
1316 w_splitter_t *w = (w_splitter_t *)base;
1317 int pos = ((w_splitter_t *)w)->position; // prevent lock/unlock from overwriting position
1318 if (w->locked && !GTK_IS_BOX(w->box)) {
1319 w->locked = 0;
1320 w_splitter_lock (w);
1321 }
1322 if (!w->locked && GTK_IS_BOX(w->box)) {
1323 w->locked = 1;
1324 w_splitter_unlock (w);
1325 }
1326 if (pos == -1) {
1327 GtkAllocation a;
1328 gtk_widget_get_allocation (w->base.widget, &a);
1329 pos = a.height/2;
1330 }
1331 w->position = pos;
1332 if (!w->locked) {
1333 gtk_widget_set_size_request (w->base.children->widget, -1, -1);
1334 gtk_paned_set_position (GTK_PANED(w->box), pos);
1335 }
1336 else {
1337 gtk_widget_set_size_request (w->base.children->widget, -1, w->position);
1338 }
1339 }
1340
1341 ddb_gtkui_widget_t *
w_vsplitter_create(void)1342 w_vsplitter_create (void) {
1343 w_splitter_t *w = malloc (sizeof (w_splitter_t));
1344 memset (w, 0, sizeof (w_splitter_t));
1345 w->position = -1;
1346 w->base.append = w_splitter_add;
1347 w->base.remove = w_splitter_remove;
1348 w->base.replace = w_splitter_replace;
1349 w->base.get_container = w_splitter_get_container;
1350 w->base.load = w_splitter_load;
1351 w->base.save = w_splitter_save;
1352 w->base.init = w_vsplitter_init;
1353 w->base.initmenu = w_splitter_initmenu;
1354
1355 w->base.widget = gtk_event_box_new ();
1356 w->box = gtk_vpaned_new ();
1357 gtk_widget_show (w->box);
1358 gtk_container_add (GTK_CONTAINER (w->base.widget), w->box);
1359 w_override_signals (w->base.widget, w);
1360
1361 ddb_gtkui_widget_t *ph1, *ph2;
1362 ph1 = w_create ("placeholder");
1363 ph2 = w_create ("placeholder");
1364
1365 w_append ((ddb_gtkui_widget_t*)w, ph1);
1366 w_append ((ddb_gtkui_widget_t*)w, ph2);
1367
1368 return (ddb_gtkui_widget_t*)w;
1369 }
1370
1371 ////// hsplitter widget
1372 void
w_hsplitter_init(ddb_gtkui_widget_t * base)1373 w_hsplitter_init (ddb_gtkui_widget_t *base) {
1374 w_splitter_t *w = (w_splitter_t *)base;
1375 int pos = ((w_splitter_t *)w)->position; // prevent lock/unlock from overwriting position
1376 if (w->locked && !GTK_IS_BOX(w->box)) {
1377 w->locked = 0;
1378 w_splitter_lock (w);
1379 }
1380 else if (!w->locked && GTK_IS_BOX(w->box)) {
1381 w->locked = 1;
1382 w_splitter_unlock (w);
1383 }
1384 if (pos == -1) {
1385 GtkAllocation a;
1386 gtk_widget_get_allocation (w->base.widget, &a);
1387 pos = a.width/2;
1388 }
1389 w->position = pos;
1390 if (!w->locked) {
1391 gtk_widget_set_size_request (w->base.children->widget, -1, -1);
1392 gtk_paned_set_position (GTK_PANED(w->box), pos);
1393 }
1394 else {
1395 gtk_widget_set_size_request (w->base.children->widget, w->position, -1);
1396 }
1397 }
1398
1399 ddb_gtkui_widget_t *
w_hsplitter_create(void)1400 w_hsplitter_create (void) {
1401 w_splitter_t *w = malloc (sizeof (w_splitter_t));
1402 memset (w, 0, sizeof (w_splitter_t));
1403 w->position = -1;
1404 w->base.append = w_splitter_add;
1405 w->base.remove = w_splitter_remove;
1406 w->base.replace = w_splitter_replace;
1407 w->base.get_container = w_splitter_get_container;
1408 w->base.load = w_splitter_load;
1409 w->base.save = w_splitter_save;
1410 w->base.init = w_hsplitter_init;
1411 w->base.initmenu = w_splitter_initmenu;
1412
1413 w->base.widget = gtk_event_box_new ();
1414 w->box = gtk_hpaned_new ();
1415 gtk_widget_show (w->box);
1416 gtk_container_add (GTK_CONTAINER (w->base.widget), w->box);
1417 w_override_signals (w->base.widget, w);
1418
1419 ddb_gtkui_widget_t *ph1, *ph2;
1420 ph1 = w_create ("placeholder");
1421 ph2 = w_create ("placeholder");
1422
1423 w_append ((ddb_gtkui_widget_t*)w, ph1);
1424 w_append ((ddb_gtkui_widget_t*)w, ph2);
1425
1426 return (ddb_gtkui_widget_t*)w;
1427 }
1428
1429 ///// tabs widget
1430
1431 static void
w_tabs_destroy(ddb_gtkui_widget_t * w)1432 w_tabs_destroy (ddb_gtkui_widget_t *w) {
1433 w_tabs_t *s = (w_tabs_t *)w;
1434 if (s->titles) {
1435 for (int i = 0; i < s->num_tabs; i++) {
1436 if (s->titles[i]) {
1437 free (s->titles[i]);
1438 }
1439 }
1440 free (s->titles);
1441 }
1442 }
1443
1444 const char *
w_tabs_load(struct ddb_gtkui_widget_s * widget,const char * type,const char * s)1445 w_tabs_load (struct ddb_gtkui_widget_s *widget, const char *type, const char *s) {
1446 w_tabs_t *w = (w_tabs_t *)widget;
1447 if (strcmp (type, "tabs")) {
1448 return NULL;
1449 }
1450
1451 char key[MAX_TOKEN], val[MAX_TOKEN];
1452 for (;;) {
1453 get_keyvalue (s,key,val);
1454
1455 if (!strcmp (key, "active")) {
1456 w->active = atoi (val);
1457 continue;
1458 }
1459 if (!strcmp (key, "num_tabs")) {
1460 w->num_tabs = atoi (val);
1461 w->titles = malloc (w->num_tabs * sizeof (char *));
1462 continue;
1463 }
1464 for (int i = 0; i < w->num_tabs; i++) {
1465 char tab_name[100];
1466 snprintf (tab_name, sizeof (tab_name), "tab%03d", i);
1467 if (!strcmp (key, tab_name)) {
1468 w->titles[i] = strdup (val);
1469 continue;
1470 }
1471 }
1472 }
1473
1474 return s;
1475 }
1476
1477 void
w_tabs_save(struct ddb_gtkui_widget_s * widget,char * s,int sz)1478 w_tabs_save (struct ddb_gtkui_widget_s *widget, char *s, int sz) {
1479 w_tabs_t *w = (w_tabs_t *)widget;
1480 int active = gtk_notebook_get_current_page (GTK_NOTEBOOK (w->base.widget));
1481 int num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (w->base.widget));
1482 char spos[1000];
1483 char *pp = spos;
1484 int ss = sizeof (spos);
1485 int n;
1486
1487 n = snprintf (spos, sizeof (spos), " active=%d num_tabs=%d", active, num_pages);
1488 ss -= n;
1489 pp += n;
1490 for (int i = 0; i < num_pages; i++) {
1491 GtkWidget *child = gtk_notebook_get_nth_page (GTK_NOTEBOOK (w->base.widget), i);
1492 const char *text = gtk_notebook_get_tab_label_text (GTK_NOTEBOOK (w->base.widget), child);
1493 char *esctext = parser_escape_string (text);
1494 n = snprintf (pp, ss, " tab%03d=\"%s\"", i, esctext);
1495 free (esctext);
1496 ss -= n;
1497 pp += n;
1498 }
1499 strncat (s, spos, sz);
1500 }
1501
1502 static void
tabs_add_tab(gpointer user_data)1503 tabs_add_tab (gpointer user_data)
1504 {
1505 w_tabs_t *w = user_data;
1506
1507 ddb_gtkui_widget_t *ph;
1508 ph = w_create ("placeholder");
1509 w_append ((ddb_gtkui_widget_t*)w, ph);
1510
1511 int i = 0;
1512 for (ddb_gtkui_widget_t *c = w->base.children; c; c = c->next, i++);
1513 w->clicked_page = i-1;
1514 gtk_notebook_set_current_page (GTK_NOTEBOOK (w->base.widget), w->clicked_page);
1515
1516 }
1517
1518 static void
tabs_remove_tab(gpointer user_data,int tab)1519 tabs_remove_tab (gpointer user_data, int tab)
1520 {
1521 w_tabs_t *w = user_data;
1522 int i = 0;
1523 int num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (w->base.widget));
1524 for (ddb_gtkui_widget_t *c = w->base.children; c; c = c->next, i++) {
1525 if (i == tab) {
1526 w_remove ((ddb_gtkui_widget_t *)w, c);
1527 w_destroy (c);
1528 if (num_pages == 1) {
1529 // if last tab was deleted add a new placeholder tab
1530 tabs_add_tab (w);
1531 }
1532 return;
1533 }
1534 }
1535 }
1536
1537 static gboolean
1538 tab_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer user_data);
1539
1540 static void
on_rename_tab_activate(GtkMenuItem * menuitem,gpointer user_data)1541 on_rename_tab_activate (GtkMenuItem *menuitem, gpointer user_data) {
1542 w_tabs_t *w = user_data;
1543
1544 GtkWidget *dlg = create_entrydialog ();
1545 gtk_dialog_set_default_response (GTK_DIALOG (dlg), GTK_RESPONSE_OK);
1546 gtk_window_set_title (GTK_WINDOW (dlg), _("Rename tab"));
1547 GtkWidget *e;
1548 e = lookup_widget (dlg, "title_label");
1549 gtk_label_set_text (GTK_LABEL(e), _("Title:"));
1550 e = lookup_widget (dlg, "title");
1551 int active = gtk_notebook_get_current_page (GTK_NOTEBOOK (w->base.widget));
1552 GtkWidget *child = gtk_notebook_get_nth_page (GTK_NOTEBOOK (w->base.widget), active);
1553 gtk_entry_set_text (GTK_ENTRY (e), gtk_notebook_get_tab_label_text (GTK_NOTEBOOK (w->base.widget), child));
1554 int res = gtk_dialog_run (GTK_DIALOG (dlg));
1555 if (res == GTK_RESPONSE_OK) {
1556 gtk_notebook_set_tab_label_text (GTK_NOTEBOOK (w->base.widget), child, gtk_entry_get_text (GTK_ENTRY (e)));
1557 }
1558 gtk_widget_destroy (dlg);
1559 }
1560
1561 static void
on_remove_tab_activate(GtkMenuItem * menuitem,gpointer user_data)1562 on_remove_tab_activate (GtkMenuItem *menuitem, gpointer user_data) {
1563 w_tabs_t *w = user_data;
1564 tabs_remove_tab (w, w->clicked_page);
1565 }
1566
1567 static void
on_add_tab_activate(GtkMenuItem * menuitem,gpointer user_data)1568 on_add_tab_activate (GtkMenuItem *menuitem, gpointer user_data) {
1569 tabs_add_tab (user_data);
1570 }
1571
1572 static void
on_move_tab_left_activate(GtkMenuItem * menuitem,gpointer user_data)1573 on_move_tab_left_activate (GtkMenuItem *menuitem, gpointer user_data) {
1574 w_tabs_t *w = user_data;
1575 if (w->clicked_page <= 0) {
1576 return;
1577 }
1578 // remove and save widget
1579 int i = 0;
1580 ddb_gtkui_widget_t *newchild = NULL;
1581 ddb_gtkui_widget_t *prev = NULL;
1582 char *title;
1583 for (ddb_gtkui_widget_t *c = w->base.children; c; c = c->next, i++) {
1584 if (i == w->clicked_page) {
1585 char buf[20000] = "";
1586 save_widget_to_string (buf, sizeof (buf), c);
1587 GtkWidget *child = gtk_notebook_get_nth_page (GTK_NOTEBOOK (w->base.widget), i);
1588 title = strdup (gtk_notebook_get_tab_label_text (GTK_NOTEBOOK (w->base.widget), child));
1589 w_remove ((ddb_gtkui_widget_t *)w, c);
1590 w_destroy (c);
1591 w_create_from_string (buf, &newchild);
1592 break;
1593 }
1594 }
1595 if (!newchild) {
1596 goto out;
1597 }
1598
1599 // add new child at new position
1600 i = 0;
1601 prev = NULL;
1602 for (ddb_gtkui_widget_t *c = w->base.children; c; c = c->next, i++) {
1603 if (i == w->clicked_page-1) {
1604 if (prev) {
1605 newchild->next = prev->next;
1606 prev->next = newchild;
1607 }
1608 else {
1609 newchild->next = w->base.children;
1610 w->base.children = newchild;
1611 }
1612 GtkWidget *label = gtk_label_new (title);
1613 gtk_widget_show (label);
1614 gtk_widget_show (newchild->widget);
1615
1616 gtk_notebook_insert_page (GTK_NOTEBOOK (w->base.widget), newchild->widget, label, w->clicked_page-1);
1617 #if GTK_CHECK_VERSION(3,0,0)
1618 gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
1619 gtk_misc_set_padding (GTK_MISC (label), 0, 0);
1620 gtk_container_child_set (GTK_CONTAINER (w->base.widget),
1621 newchild->widget,
1622 "tab-expand", TRUE,
1623 "tab-fill", TRUE,
1624 NULL);
1625 #endif
1626 gtk_notebook_set_current_page (GTK_NOTEBOOK (w->base.widget), w->clicked_page-1);
1627 w->clicked_page--;
1628 break;
1629 }
1630 prev = c;
1631 }
1632
1633 out:
1634 if (title) {
1635 free (title);
1636 }
1637 }
1638
1639 static void
on_move_tab_right_activate(GtkMenuItem * menuitem,gpointer user_data)1640 on_move_tab_right_activate (GtkMenuItem *menuitem, gpointer user_data) {
1641 w_tabs_t *w = user_data;
1642
1643 int num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (w->base.widget));
1644 if (w->clicked_page == num_pages - 1) {
1645 return;
1646 }
1647
1648 gtk_notebook_set_current_page (GTK_NOTEBOOK (w->base.widget), ++w->clicked_page);
1649 on_move_tab_left_activate (menuitem, user_data);
1650 gtk_notebook_set_current_page (GTK_NOTEBOOK (w->base.widget), ++w->clicked_page);
1651 }
1652
1653 static void
on_tab_popup_menu(GtkWidget * widget,gpointer user_data)1654 on_tab_popup_menu (GtkWidget *widget, gpointer user_data)
1655 {
1656 w_tabs_t *w = user_data;
1657 GtkWidget *menu;
1658 GtkWidget *item;
1659 menu = gtk_menu_new ();
1660
1661 item = gtk_menu_item_new_with_mnemonic (_("Add new tab"));
1662 gtk_widget_show (item);
1663 gtk_container_add (GTK_CONTAINER (menu), item);
1664 g_signal_connect ((gpointer) item, "activate",
1665 G_CALLBACK (on_add_tab_activate),
1666 w);
1667
1668 item = gtk_menu_item_new_with_mnemonic (_("Rename tab"));
1669 gtk_widget_show (item);
1670 gtk_container_add (GTK_CONTAINER (menu), item);
1671 g_signal_connect ((gpointer) item, "activate",
1672 G_CALLBACK (on_rename_tab_activate),
1673 w);
1674
1675 item = gtk_menu_item_new_with_mnemonic (_("Move tab left"));
1676 gtk_widget_show (item);
1677 gtk_container_add (GTK_CONTAINER (menu), item);
1678 g_signal_connect ((gpointer) item, "activate",
1679 G_CALLBACK (on_move_tab_left_activate),
1680 w);
1681
1682 item = gtk_menu_item_new_with_mnemonic (_("Move tab right"));
1683 gtk_widget_show (item);
1684 gtk_container_add (GTK_CONTAINER (menu), item);
1685 g_signal_connect ((gpointer) item, "activate",
1686 G_CALLBACK (on_move_tab_right_activate),
1687 w);
1688
1689 item = gtk_menu_item_new_with_mnemonic (_("Remove tab"));
1690 gtk_widget_show (item);
1691 gtk_container_add (GTK_CONTAINER (menu), item);
1692 g_signal_connect ((gpointer) item, "activate",
1693 G_CALLBACK (on_remove_tab_activate),
1694 w);
1695
1696 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, w, 0, gtk_get_current_event_time());
1697 }
1698
1699 static void
w_tabs_add(ddb_gtkui_widget_t * cont,ddb_gtkui_widget_t * child)1700 w_tabs_add (ddb_gtkui_widget_t *cont, ddb_gtkui_widget_t *child) {
1701 GtkWidget *label = gtk_label_new (child->type);
1702 gtk_widget_show (label);
1703 gtk_widget_show (child->widget);
1704 gtk_notebook_append_page (GTK_NOTEBOOK (cont->widget), child->widget, label);
1705 #if GTK_CHECK_VERSION(3,0,0)
1706 gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
1707 gtk_misc_set_padding (GTK_MISC (label), 0, 0);
1708 gtk_container_child_set (GTK_CONTAINER (cont->widget),
1709 child->widget,
1710 "tab-expand", TRUE,
1711 "tab-fill", TRUE,
1712 NULL);
1713 #endif
1714 }
1715
1716 static void
w_tabs_replace(ddb_gtkui_widget_t * cont,ddb_gtkui_widget_t * child,ddb_gtkui_widget_t * newchild)1717 w_tabs_replace (ddb_gtkui_widget_t *cont, ddb_gtkui_widget_t *child, ddb_gtkui_widget_t *newchild) {
1718 int ntab = 0;
1719 ddb_gtkui_widget_t *prev = NULL;
1720 for (ddb_gtkui_widget_t *c = cont->children; c; prev = c, c = c->next, ntab++) {
1721 if (c == child) {
1722 newchild->next = c->next;
1723 if (prev) {
1724 prev->next = newchild;
1725 }
1726 else {
1727 cont->children = newchild;
1728 }
1729 newchild->parent = cont;
1730 gtk_notebook_remove_page (GTK_NOTEBOOK(cont->widget), ntab);
1731 c->widget = NULL;
1732 w_destroy (c);
1733 GtkWidget *label = gtk_label_new (newchild->type);
1734 gtk_widget_show (label);
1735 gtk_widget_show (newchild->widget);
1736 int pos = gtk_notebook_insert_page (GTK_NOTEBOOK (cont->widget), newchild->widget, label, ntab);
1737 #if GTK_CHECK_VERSION(3,0,0)
1738 gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
1739 gtk_misc_set_padding (GTK_MISC (label), 0, 0);
1740 gtk_container_child_set (GTK_CONTAINER (cont->widget),
1741 newchild->widget,
1742 "tab-expand", TRUE,
1743 "tab-fill", TRUE,
1744 NULL);
1745 #endif
1746 gtk_notebook_set_current_page (GTK_NOTEBOOK (cont->widget), pos);
1747 break;
1748 }
1749 }
1750 }
1751
1752 static gboolean
get_event_coordinates_in_widget(GtkWidget * widget,GdkEventButton * event,gint * x,gint * y)1753 get_event_coordinates_in_widget (GtkWidget *widget,
1754 GdkEventButton *event,
1755 gint *x,
1756 gint *y)
1757 {
1758 GdkWindow *window = event->window;
1759 gdouble tx, ty;
1760 tx = event->x;
1761 ty = event->y;
1762
1763 while (window && window != gtk_widget_get_window (widget)) {
1764 gint window_x, window_y;
1765 gdk_window_get_position (window, &window_x, &window_y);
1766 tx += window_x;
1767 ty += window_y;
1768 window = gdk_window_get_parent (window);
1769 }
1770
1771 if (window) {
1772 *x = tx;
1773 *y = ty;
1774 return TRUE;
1775 }
1776 else {
1777 return FALSE;
1778 }
1779 }
1780
1781 static gboolean
on_tabs_button_press_event(GtkWidget * notebook,GdkEventButton * event,gpointer user_data)1782 on_tabs_button_press_event (GtkWidget *notebook,
1783 GdkEventButton *event,
1784 gpointer user_data)
1785 {
1786 w_tabs_t *w = user_data;
1787
1788 int page_num = 0;
1789 GtkWidget *page;
1790 GtkWidget *label_box;
1791 GtkAllocation alloc;
1792 gboolean close_tab;
1793
1794 int event_x, event_y;
1795 if (!get_event_coordinates_in_widget (notebook, event, &event_x, &event_y)) {
1796 // clicked outside the tabstrip (e.g. in one of its children)
1797 return FALSE;
1798 }
1799
1800 /* lookup the clicked tab */
1801 while ((page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), page_num)) != NULL)
1802 {
1803 label_box = gtk_notebook_get_tab_label (GTK_NOTEBOOK (notebook), page);
1804 gtk_widget_get_allocation (label_box, &alloc);
1805
1806 if (event_x >= alloc.x && event_x < alloc.x + alloc.width
1807 && event_y >= alloc.y && event_y < alloc.y + alloc.height)
1808 break;
1809
1810 page_num++;
1811 }
1812 w->clicked_page = page_num;
1813
1814 if (event->type == GDK_BUTTON_PRESS) {
1815 /* leave if no tab could be found */
1816 if (page == NULL) {
1817 return FALSE;
1818 }
1819
1820 if (event->button == 2) {
1821 /* check if we should close the tab */
1822 if (design_mode) {
1823 tabs_remove_tab (w, page_num);
1824 }
1825 }
1826 else if (event->button == 3) {
1827 /* update the current tab before we show the menu */
1828 gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), page_num);
1829
1830 /* show the tab menu */
1831 on_tab_popup_menu (notebook, user_data);
1832 return TRUE;
1833 }
1834 }
1835 else if (event->type == GDK_2BUTTON_PRESS) {
1836 if (event->button == 1 && page == NULL && design_mode) {
1837 // open new tab
1838 tabs_add_tab (w);
1839 }
1840 return TRUE;
1841 }
1842 return FALSE;
1843 }
1844
1845 static void
w_tabs_initmenu(struct ddb_gtkui_widget_s * w,GtkWidget * menu)1846 w_tabs_initmenu (struct ddb_gtkui_widget_s *w, GtkWidget *menu) {
1847 GtkWidget *item;
1848 item = gtk_menu_item_new_with_mnemonic (_("Add new tab"));
1849 gtk_widget_show (item);
1850 gtk_container_add (GTK_CONTAINER (menu), item);
1851 g_signal_connect ((gpointer) item, "activate",
1852 G_CALLBACK (on_add_tab_activate),
1853 w);
1854 }
1855
1856 static void
w_tabs_init(ddb_gtkui_widget_t * base)1857 w_tabs_init (ddb_gtkui_widget_t *base) {
1858 w_tabs_t *w = (w_tabs_t *)base;
1859 gtk_notebook_set_current_page (GTK_NOTEBOOK (w->base.widget), w->active);
1860 if (w->titles) {
1861 int page = 0;
1862 while (page < w->num_tabs) {
1863 GtkWidget *child = gtk_notebook_get_nth_page (GTK_NOTEBOOK (w->base.widget), page);
1864 if (w->titles[page]) {
1865 gtk_notebook_set_tab_label_text (GTK_NOTEBOOK (w->base.widget), child, w->titles[page]);
1866 #if GTK_CHECK_VERSION(3,0,0)
1867 GtkLabel *label = GTK_LABEL(gtk_notebook_get_tab_label (GTK_NOTEBOOK (w->base.widget), child));
1868 gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
1869 gtk_misc_set_padding (GTK_MISC (label), 0, 0);
1870 #endif
1871 }
1872 page++;
1873 }
1874 }
1875 }
1876
1877 ddb_gtkui_widget_t *
w_tabs_create(void)1878 w_tabs_create (void) {
1879 w_tabs_t *w = malloc (sizeof (w_tabs_t));
1880 memset (w, 0, sizeof (w_tabs_t));
1881 w->base.widget = gtk_notebook_new ();
1882 w->base.append = w_tabs_add;
1883 w->base.remove = w_container_remove;
1884 w->base.replace = w_tabs_replace;
1885 w->base.initmenu = w_tabs_initmenu;
1886 w->base.save = w_tabs_save;
1887 w->base.load = w_tabs_load;
1888 w->base.init = w_tabs_init;
1889 w->base.destroy = w_tabs_destroy;
1890
1891 ddb_gtkui_widget_t *ph1, *ph2, *ph3;
1892 ph1 = w_create ("placeholder");
1893 ph2 = w_create ("placeholder");
1894 ph3 = w_create ("placeholder");
1895
1896 gtk_notebook_set_scrollable (GTK_NOTEBOOK (w->base.widget), TRUE);
1897
1898 #if !GTK_CHECK_VERSION(3,0,0)
1899 g_signal_connect ((gpointer) w->base.widget, "expose_event", G_CALLBACK (w_expose_event), w);
1900 #else
1901 g_signal_connect ((gpointer) w->base.widget, "draw", G_CALLBACK (w_draw_event), w);
1902 #endif
1903 g_signal_connect ((gpointer) w->base.widget, "button_press_event", G_CALLBACK (on_tabs_button_press_event), w);
1904
1905 w_append ((ddb_gtkui_widget_t*)w, ph1);
1906 w_append ((ddb_gtkui_widget_t*)w, ph2);
1907 w_append ((ddb_gtkui_widget_t*)w, ph3);
1908
1909 w_override_signals (w->base.widget, w);
1910 return (ddb_gtkui_widget_t*)w;
1911 }
1912
1913 //// box widget
1914 //// this widget should not be exposed to user, it is used as a top level
1915 //// container (rootwidget)
1916
1917 ddb_gtkui_widget_t *
w_box_create(void)1918 w_box_create (void) {
1919 w_box_t *w = malloc (sizeof (w_box_t));
1920 memset (w, 0, sizeof (w_box_t));
1921 w->base.widget = gtk_vbox_new (FALSE, 0);
1922 w->base.append = w_container_add;
1923 w->base.remove = w_container_remove;
1924
1925 return (ddb_gtkui_widget_t*)w;
1926 }
1927
1928 //// tabstrip widget
1929 static gboolean
tabstrip_refresh_cb(void * ctx)1930 tabstrip_refresh_cb (void *ctx) {
1931 w_tabstrip_t *w = ctx;
1932 ddb_tabstrip_refresh (DDB_TABSTRIP (w->tabstrip));
1933 return FALSE;
1934 }
1935 static int
w_tabstrip_message(ddb_gtkui_widget_t * w,uint32_t id,uintptr_t ctx,uint32_t p1,uint32_t p2)1936 w_tabstrip_message (ddb_gtkui_widget_t *w, uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2) {
1937 switch (id) {
1938 case DB_EV_PLAYLISTCHANGED:
1939 if (p1 == DDB_PLAYLIST_CHANGE_TITLE
1940 || p1 == DDB_PLAYLIST_CHANGE_POSITION
1941 || p1 == DDB_PLAYLIST_CHANGE_DELETED
1942 || p1 == DDB_PLAYLIST_CHANGE_CREATED) {
1943 g_idle_add (tabstrip_refresh_cb, w);
1944 }
1945 break;
1946 case DB_EV_PLAYLISTSWITCHED:
1947 case DB_EV_TRACKINFOCHANGED:
1948 g_idle_add (tabstrip_refresh_cb, w);
1949 break;
1950 }
1951 return 0;
1952 }
1953
1954 ddb_gtkui_widget_t *
w_tabstrip_create(void)1955 w_tabstrip_create (void) {
1956 w_tabstrip_t *w = malloc (sizeof (w_tabstrip_t));
1957 memset (w, 0, sizeof (w_tabstrip_t));
1958 w->base.flags = DDB_GTKUI_WIDGET_FLAG_NON_EXPANDABLE;
1959 w->base.widget = gtk_event_box_new ();
1960 w->base.message = w_tabstrip_message;
1961 GtkWidget *ts = ddb_tabstrip_new ();
1962 gtk_widget_show (ts);
1963 gtk_container_add (GTK_CONTAINER (w->base.widget), ts);
1964 w->tabstrip = ts;
1965 w_override_signals (w->base.widget, w);
1966 return (ddb_gtkui_widget_t*)w;
1967 }
1968
1969 //// tabbed playlist widget
1970
1971 typedef struct {
1972 ddb_gtkui_widget_t *w;
1973 DB_playItem_t *trk;
1974 } w_trackdata_t;
1975
1976 static gboolean
tabbed_trackinfochanged_cb(gpointer p)1977 tabbed_trackinfochanged_cb (gpointer p) {
1978 w_trackdata_t *d = p;
1979 w_playlist_t *tp = (w_playlist_t *)d->w;
1980 if (!strcmp (tp->base.type, "tabbed_playlist")) {
1981 ddb_tabstrip_refresh (((w_tabbed_playlist_t *)tp)->tabstrip);
1982 }
1983 ddb_playlist_t *plt = deadbeef->plt_get_curr ();
1984 if (plt) {
1985 int idx = deadbeef->plt_get_item_idx (plt, (DB_playItem_t *)d->trk, PL_MAIN);
1986 if (idx != -1) {
1987 ddb_listview_draw_row (tp->list, idx, (DdbListviewIter)d->trk);
1988 }
1989 deadbeef->plt_unref (plt);
1990 }
1991 if (d->trk) {
1992 deadbeef->pl_item_unref (d->trk);
1993 }
1994 free (d);
1995 return FALSE;
1996 }
1997
1998 static gboolean
trackinfochanged_cb(gpointer data)1999 trackinfochanged_cb (gpointer data) {
2000 w_trackdata_t *d = data;
2001 w_playlist_t *p = (w_playlist_t *)d->w;
2002 ddb_playlist_t *plt = deadbeef->plt_get_curr ();
2003 if (plt) {
2004 int idx = deadbeef->plt_get_item_idx (plt, (DB_playItem_t *)d->trk, PL_MAIN);
2005 if (idx != -1) {
2006 ddb_listview_draw_row (DDB_LISTVIEW (p->list), idx, (DdbListviewIter)d->trk);
2007 }
2008 deadbeef->plt_unref (plt);
2009 }
2010 if (d->trk) {
2011 deadbeef->pl_item_unref (d->trk);
2012 }
2013 free (d);
2014 return FALSE;
2015 }
2016
2017 static gboolean
tabbed_paused_cb(gpointer p)2018 tabbed_paused_cb (gpointer p) {
2019 w_playlist_t *tp = (w_playlist_t *)p;
2020 DB_playItem_t *curr = deadbeef->streamer_get_playing_track ();
2021 if (curr) {
2022 int idx = deadbeef->pl_get_idx_of (curr);
2023 ddb_listview_draw_row (tp->list, idx, (DdbListviewIter)curr);
2024 deadbeef->pl_item_unref (curr);
2025 }
2026 return FALSE;
2027 }
2028
2029 static gboolean
paused_cb(gpointer data)2030 paused_cb (gpointer data) {
2031 w_playlist_t *p = (w_playlist_t *)data;
2032 DB_playItem_t *curr = deadbeef->streamer_get_playing_track ();
2033 if (curr) {
2034 int idx = deadbeef->pl_get_idx_of (curr);
2035 ddb_listview_draw_row (p->list, idx, (DdbListviewIter)curr);
2036 deadbeef->pl_item_unref (curr);
2037 }
2038 return FALSE;
2039 }
2040
2041 static gboolean
config_changed_cb(gpointer data)2042 config_changed_cb (gpointer data) {
2043 DdbListview *p = DDB_LISTVIEW (data);
2044 ddb_listview_update_fonts (p);
2045 ddb_listview_header_update_fonts (p);
2046 ddb_listview_lock_columns (p, 0);
2047 ddb_listview_clear_sort (p);
2048 ddb_listview_refresh (DDB_LISTVIEW (p), DDB_REFRESH_LIST | DDB_REFRESH_VSCROLL);
2049 return FALSE;
2050 }
2051
2052 static gboolean
refresh_cb(gpointer data)2053 refresh_cb (gpointer data) {
2054 DdbListview *p = DDB_LISTVIEW (data);
2055 ddb_listview_lock_columns (p, 0);
2056 ddb_listview_clear_sort (p);
2057 ddb_listview_refresh (DDB_LISTVIEW (p), DDB_REFRESH_LIST | DDB_REFRESH_VSCROLL);
2058 return FALSE;
2059 }
2060
2061 static gboolean
playlistchanged_cb(gpointer p)2062 playlistchanged_cb (gpointer p) {
2063 w_playlist_t *tp = (w_playlist_t *)p;
2064 if (!strcmp (tp->base.type, "tabbed_playlist")) {
2065 ddb_tabstrip_refresh (((w_tabbed_playlist_t *)tp)->tabstrip);
2066 }
2067 return FALSE;
2068 }
2069
2070 static gboolean
playlistswitch_cb(gpointer p)2071 playlistswitch_cb (gpointer p) {
2072 w_playlist_t *tp = (w_playlist_t *)p;
2073 if (!strcmp (tp->base.type, "tabbed_playlist")) {
2074 ddb_tabstrip_refresh (((w_tabbed_playlist_t *)tp)->tabstrip);
2075 }
2076 ddb_playlist_t *plt = deadbeef->plt_get_curr ();
2077 if (plt) {
2078 int cursor = deadbeef->plt_get_cursor (plt, PL_MAIN);
2079 int scroll = deadbeef->plt_get_scroll (plt);
2080 if (cursor != -1) {
2081 DB_playItem_t *it = deadbeef->pl_get_for_idx_and_iter (cursor, PL_MAIN);
2082 if (it) {
2083 deadbeef->pl_set_selected (it, 1);
2084 deadbeef->pl_item_unref (it);
2085 }
2086 }
2087 deadbeef->plt_unref (plt);
2088
2089 ddb_listview_refresh (tp->list, DDB_LIST_CHANGED | DDB_REFRESH_LIST | DDB_REFRESH_VSCROLL);
2090 ddb_listview_set_vscroll (tp->list, scroll);
2091 }
2092 return FALSE;
2093 }
2094
2095 struct fromto_t {
2096 ddb_gtkui_widget_t *w;
2097 DB_playItem_t *from;
2098 DB_playItem_t *to;
2099 };
2100
2101 static gboolean
songchanged_cb(gpointer p)2102 songchanged_cb (gpointer p) {
2103 struct fromto_t *ft = p;
2104 DB_playItem_t *from = ft->from;
2105 DB_playItem_t *to = ft->to;
2106 w_playlist_t *tp = (w_playlist_t *)ft->w;
2107 int to_idx = -1;
2108 if (!ddb_listview_is_scrolling (tp->list) && to) {
2109 int cursor_follows_playback = deadbeef->conf_get_int ("playlist.scroll.cursorfollowplayback", 1);
2110 int scroll_follows_playback = deadbeef->conf_get_int ("playlist.scroll.followplayback", 1);
2111 int plt = deadbeef->streamer_get_current_playlist ();
2112 if (plt != -1) {
2113 if (plt != deadbeef->plt_get_curr_idx ()) {
2114 ddb_playlist_t *p = deadbeef->plt_get_for_idx (plt);
2115 if (p) {
2116 to_idx = deadbeef->plt_get_item_idx (p, to, PL_MAIN);
2117 if (cursor_follows_playback) {
2118 deadbeef->plt_deselect_all (p);
2119 deadbeef->pl_set_selected (to, 1);
2120 deadbeef->plt_set_cursor (p, PL_MAIN, to_idx);
2121 }
2122 deadbeef->plt_unref (p);
2123 }
2124 goto end;
2125 }
2126 to_idx = deadbeef->pl_get_idx_of (to);
2127 if (to_idx != -1) {
2128 if (cursor_follows_playback) {
2129 ddb_listview_set_cursor_noscroll (tp->list, to_idx);
2130 }
2131 if (scroll_follows_playback && plt == deadbeef->plt_get_curr_idx ()) {
2132 ddb_listview_scroll_to (tp->list, to_idx);
2133 }
2134 }
2135 }
2136 }
2137 end:
2138 if (from) {
2139 int idx = deadbeef->pl_get_idx_of (from);
2140 if (idx != -1) {
2141 ddb_listview_draw_row (tp->list, idx, from);
2142 }
2143 }
2144 if (to && to_idx != -1) {
2145 ddb_listview_draw_row (tp->list, to_idx, to);
2146 }
2147 if (ft->from) {
2148 deadbeef->pl_item_unref (ft->from);
2149 }
2150 if (ft->to) {
2151 deadbeef->pl_item_unref (ft->to);
2152 }
2153 free (ft);
2154 return FALSE;
2155 }
2156
2157 static gboolean
trackfocus_cb(gpointer p)2158 trackfocus_cb (gpointer p) {
2159 w_playlist_t *tp = p;
2160 deadbeef->pl_lock ();
2161 DB_playItem_t *it = deadbeef->streamer_get_playing_track ();
2162 if (it) {
2163 ddb_playlist_t *plt = deadbeef->pl_get_playlist (it);
2164 if (plt) {
2165 deadbeef->plt_set_curr (plt);
2166 int idx = deadbeef->pl_get_idx_of (it);
2167 if (idx != -1) {
2168 ddb_listview_scroll_to (tp->list, idx);
2169 ddb_listview_set_cursor (tp->list, idx);
2170 }
2171 deadbeef->plt_unref (plt);
2172 }
2173 deadbeef->pl_item_unref (it);
2174 }
2175 deadbeef->pl_unlock ();
2176
2177 return FALSE;
2178 }
2179
2180 static gboolean
selectionfocus_cb(gpointer p)2181 selectionfocus_cb (gpointer p) {
2182 w_playlist_t *tp = p;
2183 deadbeef->pl_lock ();
2184 ddb_playlist_t *plt = deadbeef->plt_get_curr ();
2185 if (plt) {
2186 DB_playItem_t *it = deadbeef->plt_get_first (plt, PL_MAIN);
2187 while (it) {
2188 if (deadbeef->pl_is_selected (it)) {
2189 break;
2190 }
2191 DB_playItem_t *next = deadbeef->pl_get_next (it, PL_MAIN);
2192 deadbeef->pl_item_unref (it);
2193 it = next;
2194 }
2195 if (it) {
2196 int idx = deadbeef->pl_get_idx_of (it);
2197 if (idx != -1) {
2198 deadbeef->plt_set_cursor (p, PL_MAIN, idx);
2199 ddb_listview_scroll_to (tp->list, idx);
2200 }
2201 deadbeef->pl_item_unref (it);
2202 }
2203
2204 deadbeef->plt_unref (plt);
2205 }
2206 deadbeef->pl_unlock ();
2207
2208 return FALSE;
2209 }
2210 static int
w_tabbed_playlist_message(ddb_gtkui_widget_t * w,uint32_t id,uintptr_t ctx,uint32_t p1,uint32_t p2)2211 w_tabbed_playlist_message (ddb_gtkui_widget_t *w, uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2) {
2212 w_playlist_t *tp = (w_playlist_t *)w;
2213 switch (id) {
2214 case DB_EV_SONGCHANGED:
2215 g_idle_add (redraw_queued_tracks_cb, tp->list);
2216 ddb_event_trackchange_t *ev = (ddb_event_trackchange_t *)ctx;
2217 struct fromto_t *ft = malloc (sizeof (struct fromto_t));
2218 ft->from = ev->from;
2219 ft->to = ev->to;
2220 if (ft->from) {
2221 deadbeef->pl_item_ref (ft->from);
2222 }
2223 if (ft->to) {
2224 deadbeef->pl_item_ref (ft->to);
2225 }
2226 ft->w = w;
2227 g_idle_add (songchanged_cb, ft);
2228 break;
2229 case DB_EV_TRACKINFOCHANGED:
2230 {
2231 ddb_event_track_t *ev = (ddb_event_track_t *)ctx;
2232 if (ev->track) {
2233 deadbeef->pl_item_ref (ev->track);
2234 }
2235 w_trackdata_t *d = malloc (sizeof (w_trackdata_t));
2236 memset (d, 0, sizeof (w_trackdata_t));
2237 d->w = w;
2238 d->trk = ev->track;
2239 g_idle_add (tabbed_trackinfochanged_cb, d);
2240 }
2241 break;
2242 case DB_EV_PAUSED:
2243 g_idle_add (tabbed_paused_cb, w);
2244 break;
2245 case DB_EV_PLAYLISTCHANGED:
2246 g_idle_add (refresh_cb, tp->list);
2247 if (p1 == DDB_PLAYLIST_CHANGE_TITLE
2248 || p1 == DDB_PLAYLIST_CHANGE_POSITION
2249 || p1 == DDB_PLAYLIST_CHANGE_DELETED
2250 || p1 == DDB_PLAYLIST_CHANGE_CREATED) {
2251 g_idle_add (playlistchanged_cb, w);
2252 }
2253 break;
2254 case DB_EV_PLAYLISTSWITCHED:
2255 g_idle_add (playlistswitch_cb, w);
2256 break;
2257 case DB_EV_TRACKFOCUSCURRENT:
2258 g_idle_add (trackfocus_cb, w);
2259 break;
2260 case DB_EV_FOCUS_SELECTION:
2261 g_idle_add (selectionfocus_cb, w);
2262 break;
2263 case DB_EV_CONFIGCHANGED:
2264 g_idle_add (config_changed_cb, tp->list);
2265 break;
2266 case DB_EV_SELCHANGED:
2267 if (ctx != (uintptr_t)tp->list || p2 == PL_SEARCH) {
2268 g_idle_add (refresh_cb, tp->list);
2269 }
2270 break;
2271 }
2272 return 0;
2273 }
2274
2275 static int
w_playlist_message(ddb_gtkui_widget_t * w,uint32_t id,uintptr_t ctx,uint32_t p1,uint32_t p2)2276 w_playlist_message (ddb_gtkui_widget_t *w, uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2) {
2277 w_playlist_t *p = (w_playlist_t *)w;
2278 switch (id) {
2279 case DB_EV_SONGCHANGED:
2280 g_idle_add (redraw_queued_tracks_cb, p->list);
2281 ddb_event_trackchange_t *ev = (ddb_event_trackchange_t *)ctx;
2282 struct fromto_t *ft = malloc (sizeof (struct fromto_t));
2283 ft->from = ev->from;
2284 ft->to = ev->to;
2285 if (ft->from) {
2286 deadbeef->pl_item_ref (ft->from);
2287 }
2288 if (ft->to) {
2289 deadbeef->pl_item_ref (ft->to);
2290 }
2291 ft->w = w;
2292 g_idle_add (songchanged_cb, ft);
2293 break;
2294 case DB_EV_TRACKINFOCHANGED:
2295 {
2296 ddb_event_track_t *ev = (ddb_event_track_t *)ctx;
2297 if (ev->track) {
2298 deadbeef->pl_item_ref (ev->track);
2299 }
2300 w_trackdata_t *d = malloc (sizeof (w_trackdata_t));
2301 memset (d, 0, sizeof (w_trackdata_t));
2302 d->w = w;
2303 d->trk = ev->track;
2304 g_idle_add (trackinfochanged_cb, d);
2305 }
2306 break;
2307 case DB_EV_PAUSED:
2308 g_idle_add (paused_cb, w);
2309 break;
2310 case DB_EV_PLAYLISTCHANGED:
2311 g_idle_add (refresh_cb, p->list);
2312 break;
2313 case DB_EV_PLAYLISTSWITCHED:
2314 g_idle_add (playlistswitch_cb, w);
2315 break;
2316 case DB_EV_TRACKFOCUSCURRENT:
2317 g_idle_add (trackfocus_cb, w);
2318 break;
2319 case DB_EV_FOCUS_SELECTION:
2320 g_idle_add (selectionfocus_cb, w);
2321 break;
2322 case DB_EV_CONFIGCHANGED:
2323 g_idle_add (config_changed_cb, p->list);
2324 break;
2325 case DB_EV_SELCHANGED:
2326 if (ctx != (uintptr_t)p->list || p2 == PL_SEARCH) {
2327 g_idle_add (refresh_cb, p->list);
2328 }
2329 break;
2330 }
2331 return 0;
2332 }
2333
2334 static const char *
w_playlist_load(struct ddb_gtkui_widget_s * w,const char * type,const char * s)2335 w_playlist_load (struct ddb_gtkui_widget_s *w, const char *type, const char *s) {
2336 if (strcmp (type, "playlist") && strcmp (type, "tabbed_playlist")) {
2337 return NULL;
2338 }
2339 char key[MAX_TOKEN], val[MAX_TOKEN];
2340 for (;;) {
2341 get_keyvalue (s, key, val);
2342 if (!strcmp (key, "hideheaders")) {
2343 ((w_playlist_t *)w)->hideheaders = atoi (val);
2344 }
2345 if (!strcmp (key, "width")) {
2346 ((w_playlist_t *)w)->width = atoi (val);
2347 }
2348 }
2349
2350 return s;
2351 }
2352
2353 static void
w_playlist_save(struct ddb_gtkui_widget_s * w,char * s,int sz)2354 w_playlist_save (struct ddb_gtkui_widget_s *w, char *s, int sz) {
2355 w_playlist_t *ww = (w_playlist_t *)w;
2356
2357 GtkAllocation a;
2358 gtk_widget_get_allocation(ww->base.widget, &a);
2359 int width = a.width;
2360
2361 char save[100];
2362 snprintf (save, sizeof (save), " hideheaders=%d width=%d", ww->hideheaders, width);
2363 strncat (s, save, sz);
2364 }
2365
2366 static void
w_playlist_init(ddb_gtkui_widget_t * base)2367 w_playlist_init (ddb_gtkui_widget_t *base) {
2368 w_playlist_t *w = (w_playlist_t *)base;
2369
2370 ddb_listview_show_header (w->list, !w->hideheaders);
2371 ddb_listview_init_autoresize (w->list, w->width);
2372
2373 g_idle_add (playlistswitch_cb, w);
2374 g_idle_add (refresh_cb, w->list);
2375 }
2376
2377 static void
on_playlist_showheaders_toggled(GtkCheckMenuItem * checkmenuitem,gpointer user_data)2378 on_playlist_showheaders_toggled (GtkCheckMenuItem *checkmenuitem, gpointer user_data) {
2379 w_playlist_t *w = user_data;
2380 w->hideheaders = !gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (checkmenuitem));
2381 ddb_listview_show_header (DDB_LISTVIEW (w->list), !w->hideheaders);
2382 }
2383
2384 static void
w_playlist_initmenu(struct ddb_gtkui_widget_s * w,GtkWidget * menu)2385 w_playlist_initmenu (struct ddb_gtkui_widget_s *w, GtkWidget *menu) {
2386 GtkWidget *item;
2387 item = gtk_check_menu_item_new_with_mnemonic (_("Show Column Headers"));
2388 gtk_widget_show (item);
2389 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), !((w_playlist_t *)w)->hideheaders);
2390 gtk_container_add (GTK_CONTAINER (menu), item);
2391 g_signal_connect ((gpointer) item, "toggled",
2392 G_CALLBACK (on_playlist_showheaders_toggled),
2393 w);
2394 }
2395
2396 ddb_gtkui_widget_t *
w_tabbed_playlist_create(void)2397 w_tabbed_playlist_create (void) {
2398 w_tabbed_playlist_t *w = malloc (sizeof (w_tabbed_playlist_t));
2399 memset (w, 0, sizeof (w_tabbed_playlist_t));
2400
2401 GtkWidget *vbox = gtk_vbox_new (FALSE, 0);
2402 w->plt.base.widget = vbox;
2403 w->plt.base.save = w_playlist_save;
2404 w->plt.base.load = w_playlist_load;
2405 w->plt.base.init = w_playlist_init;
2406 w->plt.base.initmenu = w_playlist_initmenu;
2407 gtk_widget_show (vbox);
2408
2409 GtkWidget *tabstrip = ddb_tabstrip_new ();
2410 w->tabstrip = DDB_TABSTRIP (tabstrip);
2411 gtk_widget_show (tabstrip);
2412 GtkWidget *list = ddb_listview_new ();
2413 gtk_widget_set_size_request (vbox, 100, 100);
2414 w->plt.list = (DdbListview *)list;
2415 gtk_widget_show (list);
2416
2417 gtk_box_pack_start (GTK_BOX (vbox), tabstrip, FALSE, TRUE, 0);
2418 gtk_widget_set_can_focus (tabstrip, FALSE);
2419 gtk_widget_set_can_default (tabstrip, FALSE);
2420
2421 gtk_box_pack_start (GTK_BOX (vbox), list, TRUE, TRUE, 0);
2422
2423 main_playlist_init (list);
2424
2425 w_override_signals (w->plt.base.widget, w);
2426
2427 w->plt.base.message = w_tabbed_playlist_message;
2428 return (ddb_gtkui_widget_t*)w;
2429 }
2430
2431 ///// playlist widget
2432
2433 ddb_gtkui_widget_t *
w_playlist_create(void)2434 w_playlist_create (void) {
2435 w_playlist_t *w = malloc (sizeof (w_playlist_t));
2436 memset (w, 0, sizeof (w_playlist_t));
2437
2438 w->base.widget = gtk_event_box_new ();
2439 w->list = DDB_LISTVIEW (ddb_listview_new ());
2440 gtk_widget_set_size_request (GTK_WIDGET (w->base.widget), 100, 100);
2441 w->base.save = w_playlist_save;
2442 w->base.load = w_playlist_load;
2443 w->base.init = w_playlist_init;
2444 w->base.initmenu = w_playlist_initmenu;
2445 gtk_widget_show (GTK_WIDGET (w->list));
2446 main_playlist_init (GTK_WIDGET (w->list));
2447
2448 if (deadbeef->conf_get_int ("gtkui.headers.visible", 1)) {
2449 ddb_listview_show_header (DDB_LISTVIEW (w->list), 1);
2450 }
2451 else {
2452 ddb_listview_show_header (DDB_LISTVIEW (w->list), 0);
2453 }
2454
2455 gtk_container_add (GTK_CONTAINER (w->base.widget), GTK_WIDGET (w->list));
2456 w_override_signals (w->base.widget, w);
2457 w->base.message = w_playlist_message;
2458 return (ddb_gtkui_widget_t*)w;
2459 }
2460
2461 ////// selection properties widget
2462
2463 gboolean
fill_selproperties_cb(gpointer data)2464 fill_selproperties_cb (gpointer data) {
2465 w_selproperties_t *w = data;
2466 DB_playItem_t **tracks = NULL;
2467 if (w->refresh_timeout) {
2468 g_source_remove (w->refresh_timeout);
2469 w->refresh_timeout = 0;
2470 }
2471 int numtracks = 0;
2472 deadbeef->pl_lock ();
2473 int nsel = deadbeef->pl_getselcount ();
2474 if (0 < nsel) {
2475 tracks = malloc (sizeof (DB_playItem_t *) * nsel);
2476 if (tracks) {
2477 int n = 0;
2478 DB_playItem_t *it = deadbeef->pl_get_first (PL_MAIN);
2479 while (it) {
2480 if (deadbeef->pl_is_selected (it)) {
2481 assert (n < nsel);
2482 deadbeef->pl_item_ref (it);
2483 tracks[n++] = it;
2484 }
2485 DB_playItem_t *next = deadbeef->pl_get_next (it, PL_MAIN);
2486 deadbeef->pl_item_unref (it);
2487 it = next;
2488 }
2489 numtracks = nsel;
2490 }
2491 else {
2492 deadbeef->pl_unlock ();
2493 return FALSE;
2494 }
2495 }
2496 GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (w->tree)));
2497 trkproperties_fill_meta (store, tracks, numtracks);
2498 if (tracks) {
2499 for (int i = 0; i < numtracks; i++) {
2500 deadbeef->pl_item_unref (tracks[i]);
2501 }
2502 free (tracks);
2503 tracks = NULL;
2504 numtracks = 0;
2505 }
2506 deadbeef->pl_unlock ();
2507 return FALSE;
2508 }
2509
2510 static void
selproperties_selection_changed(gpointer user_data)2511 selproperties_selection_changed (gpointer user_data)
2512 {
2513 w_selproperties_t *selprop_w = user_data;
2514 if (selprop_w->refresh_timeout) {
2515 g_source_remove (selprop_w->refresh_timeout);
2516 selprop_w->refresh_timeout = 0;
2517 }
2518 selprop_w->refresh_timeout = g_timeout_add (100, fill_selproperties_cb, user_data);
2519 }
2520
2521 static int
selproperties_message(ddb_gtkui_widget_t * w,uint32_t id,uintptr_t ctx,uint32_t p1,uint32_t p2)2522 selproperties_message (ddb_gtkui_widget_t *w, uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2) {
2523 w_selproperties_t *selprop_w = (w_selproperties_t *)w;
2524 switch (id) {
2525 case DB_EV_PLAYLISTCHANGED:
2526 if (p1 == DDB_PLAYLIST_CHANGE_CONTENT
2527 || p1 == DDB_PLAYLIST_CHANGE_SELECTION) {
2528 selproperties_selection_changed (w);
2529 }
2530 break;
2531 case DB_EV_PLAYLISTSWITCHED:
2532 case DB_EV_SELCHANGED:
2533 selproperties_selection_changed (w);
2534 break;
2535 }
2536 return 0;
2537 }
2538
2539 static void
w_selproperties_init(struct ddb_gtkui_widget_s * widget)2540 w_selproperties_init (struct ddb_gtkui_widget_s *widget) {
2541 w_selproperties_t *w = (w_selproperties_t *)widget;
2542 w->refresh_timeout = 0;
2543 fill_selproperties_cb (widget);
2544 }
2545
2546 static void
on_selproperties_showheaders_toggled(GtkCheckMenuItem * checkmenuitem,gpointer user_data)2547 on_selproperties_showheaders_toggled (GtkCheckMenuItem *checkmenuitem, gpointer user_data) {
2548 w_selproperties_t *w = user_data;
2549 int showheaders = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (checkmenuitem));
2550 deadbeef->conf_set_int ("gtkui.selection_properties.show_headers", showheaders);
2551 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (w->tree), showheaders);
2552 }
2553
2554 static void
w_selproperties_initmenu(struct ddb_gtkui_widget_s * w,GtkWidget * menu)2555 w_selproperties_initmenu (struct ddb_gtkui_widget_s *w, GtkWidget *menu) {
2556 GtkWidget *item;
2557 item = gtk_check_menu_item_new_with_mnemonic (_("Show Column Headers"));
2558 gtk_widget_show (item);
2559 int showheaders = deadbeef->conf_get_int ("gtkui.selection_properties.show_headers", 1);
2560 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), showheaders);
2561 gtk_container_add (GTK_CONTAINER (menu), item);
2562 g_signal_connect ((gpointer) item, "toggled",
2563 G_CALLBACK (on_selproperties_showheaders_toggled),
2564 w);
2565 }
2566
2567 ddb_gtkui_widget_t *
w_selproperties_create(void)2568 w_selproperties_create (void) {
2569 w_selproperties_t *w = malloc (sizeof (w_selproperties_t));
2570 memset (w, 0, sizeof (w_selproperties_t));
2571
2572 w->base.widget = gtk_event_box_new ();
2573 w->base.init = w_selproperties_init;
2574 w->base.message = selproperties_message;
2575 w->base.initmenu = w_selproperties_initmenu;
2576
2577 gtk_widget_set_can_focus (w->base.widget, FALSE);
2578
2579 GtkWidget *scroll = gtk_scrolled_window_new (NULL, NULL);
2580 gtk_widget_set_can_focus (scroll, FALSE);
2581 gtk_widget_show (scroll);
2582 gtk_container_add (GTK_CONTAINER (w->base.widget), scroll);
2583
2584 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2585 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll), GTK_SHADOW_ETCHED_IN);
2586 w->tree = gtk_tree_view_new ();
2587 gtk_widget_show (w->tree);
2588 gtk_tree_view_set_enable_search (GTK_TREE_VIEW (w->tree), FALSE);
2589 gtk_container_add (GTK_CONTAINER (scroll), w->tree);
2590
2591 GtkListStore *store = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT);
2592 gtk_tree_view_set_model (GTK_TREE_VIEW (w->tree), GTK_TREE_MODEL (store));
2593 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (w->tree), TRUE);
2594
2595 GtkCellRenderer *rend1 = gtk_cell_renderer_text_new ();
2596 GtkCellRenderer *rend2 = gtk_cell_renderer_text_new ();
2597 GtkTreeViewColumn *col1 = gtk_tree_view_column_new_with_attributes (_("Key"), rend1, "text", 0, NULL);
2598 gtk_tree_view_column_set_sizing (col1, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
2599 GtkTreeViewColumn *col2 = gtk_tree_view_column_new_with_attributes (_("Value"), rend2, "text", 1, NULL);
2600 gtk_tree_view_column_set_sizing (col2, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
2601 gtk_tree_view_append_column (GTK_TREE_VIEW (w->tree), col1);
2602 gtk_tree_view_append_column (GTK_TREE_VIEW (w->tree), col2);
2603 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW (w->tree), TRUE);
2604
2605 int showheaders = deadbeef->conf_get_int ("gtkui.selection_properties.show_headers", 1);
2606 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (w->tree), showheaders);
2607
2608 w_override_signals (w->base.widget, w);
2609
2610 return (ddb_gtkui_widget_t *)w;
2611 }
2612
2613 ///// cover art display
2614 static GdkPixbuf *
get_cover_art(const int width,const int height,void (* callback)(void *),void * user_data)2615 get_cover_art(const int width, const int height, void (*callback)(void *), void *user_data) {
2616 DB_playItem_t *it = deadbeef->streamer_get_playing_track();
2617 if (!it) {
2618 return NULL;
2619 }
2620
2621 deadbeef->pl_lock();
2622 const char *uri = deadbeef->pl_find_meta(it, ":URI");
2623 const char *album = deadbeef->pl_find_meta(it, "album");
2624 const char *artist = deadbeef->pl_find_meta(it, "artist");
2625 if (!album || !*album) {
2626 album = deadbeef->pl_find_meta(it, "title");
2627 }
2628 GdkPixbuf *pixbuf = get_cover_art_primary_by_size(uri, artist, album, width, height, callback, user_data);
2629 deadbeef->pl_unlock();
2630 deadbeef->pl_item_unref(it);
2631 return pixbuf;
2632 }
2633
2634 static gboolean
coverart_invalidate_cb(void * user_data)2635 coverart_invalidate_cb (void *user_data) {
2636 w_coverart_t *w = user_data;
2637 gtk_widget_queue_draw(w->drawarea);
2638 return FALSE;
2639 }
2640
2641 void
coverart_invalidate(void * user_data)2642 coverart_invalidate (void *user_data) {
2643 g_idle_add(coverart_invalidate_cb, user_data);
2644 }
2645
2646 static gboolean
coverart_load(void * user_data)2647 coverart_load (void *user_data) {
2648 w_coverart_t *w = user_data;
2649 w->load_timeout_id = 0;
2650 GdkPixbuf *pixbuf = get_cover_art(w->widget_width, w->widget_height, coverart_invalidate, user_data);
2651 if (pixbuf) {
2652 coverart_invalidate(user_data);
2653 g_object_unref(pixbuf);
2654 }
2655 return FALSE;
2656 }
2657
2658 static void
coverart_draw_cairo(GdkPixbuf * pixbuf,GtkAllocation * a,cairo_t * cr,const int filter)2659 coverart_draw_cairo (GdkPixbuf *pixbuf, GtkAllocation *a, cairo_t *cr, const int filter) {
2660 const int pw = gdk_pixbuf_get_width(pixbuf);
2661 const int ph = gdk_pixbuf_get_height(pixbuf);
2662 cairo_rectangle(cr, 0, 0, a->width, a->height);
2663 if (pw > a->width || ph > a->height || pw < a->width && ph < a->height) {
2664 const double scale = min(a->width/(double)pw, a->height/(double)ph);
2665 cairo_translate(cr, (a->width - a->width*scale)/2., (a->height - a->height*scale)/2.);
2666 cairo_scale(cr, scale, scale);
2667 cairo_pattern_set_filter(cairo_get_source(cr), filter);
2668 }
2669 gdk_cairo_set_source_pixbuf(cr, pixbuf, (a->width - pw)/2., (a->height - ph)/2.);
2670 cairo_fill(cr);
2671 }
2672
2673 static void
coverart_draw_anything(GtkAllocation * a,cairo_t * cr)2674 coverart_draw_anything (GtkAllocation *a, cairo_t *cr) {
2675 GdkPixbuf *pixbuf = get_cover_art(-1, -1, NULL, NULL);
2676 if (pixbuf) {
2677 coverart_draw_cairo(pixbuf, a, cr, CAIRO_FILTER_FAST);
2678 g_object_unref(pixbuf);
2679 }
2680 }
2681
2682 static void
coverart_draw_exact(GtkAllocation * a,cairo_t * cr,void * user_data)2683 coverart_draw_exact (GtkAllocation *a, cairo_t *cr, void *user_data) {
2684 GdkPixbuf *pixbuf = get_cover_art(a->width, a->height, coverart_invalidate, user_data);
2685 if (pixbuf) {
2686 coverart_draw_cairo(pixbuf, a, cr, CAIRO_FILTER_BEST);
2687 g_object_unref(pixbuf);
2688 }
2689 else {
2690 coverart_draw_anything(a, cr);
2691 }
2692 }
2693
2694 static gboolean
coverart_draw(GtkWidget * widget,cairo_t * cr,gpointer user_data)2695 coverart_draw (GtkWidget *widget, cairo_t *cr, gpointer user_data) {
2696 GtkAllocation a;
2697 gtk_widget_get_allocation(widget, &a);
2698 if (a.width < 8 || a.height < 8) {
2699 return TRUE;
2700 }
2701
2702 w_coverart_t *w = user_data;
2703 if (w->widget_height == a.height && w->widget_width == a.width) {
2704 coverart_draw_exact(&a, cr, user_data);
2705 }
2706 else {
2707 coverart_draw_anything(&a, cr);
2708 w->widget_height = a.height;
2709 w->widget_width = a.width;
2710 if (w->load_timeout_id) {
2711 g_source_remove(w->load_timeout_id);
2712 }
2713 w->load_timeout_id = g_timeout_add(1000, coverart_load, user_data);
2714 }
2715
2716 return TRUE;
2717 }
2718
2719 static gboolean
coverart_expose_event(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)2720 coverart_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data) {
2721 cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget));
2722 gboolean res = coverart_draw (widget, cr, user_data);
2723 cairo_destroy (cr);
2724 return res;
2725 }
2726
2727 static int
coverart_message(ddb_gtkui_widget_t * w,uint32_t id,uintptr_t ctx,uint32_t p1,uint32_t p2)2728 coverart_message (ddb_gtkui_widget_t *w, uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2) {
2729 switch (id) {
2730 case DB_EV_PLAYLIST_REFRESH:
2731 coverart_invalidate(w);
2732 break;
2733 case DB_EV_SONGSTARTED:
2734 coverart_invalidate(w);
2735 break;
2736 case DB_EV_TRACKINFOCHANGED:
2737 {
2738 ddb_event_track_t *ev = (ddb_event_track_t *)ctx;
2739 DB_playItem_t *it = deadbeef->streamer_get_playing_track ();
2740 if (it == ev->track) {
2741 coverart_invalidate(w);
2742 }
2743 if (it) {
2744 deadbeef->pl_item_unref (it);
2745 }
2746 }
2747 break;
2748 }
2749 return 0;
2750 }
2751
2752 ddb_gtkui_widget_t *
w_coverart_create(void)2753 w_coverart_create (void) {
2754 w_coverart_t *w = malloc (sizeof (w_coverart_t));
2755 memset (w, 0, sizeof (w_coverart_t));
2756
2757 w->base.widget = gtk_event_box_new ();
2758 w->base.message = coverart_message;
2759 w->drawarea = gtk_drawing_area_new ();
2760 w->widget_height = -1;
2761 w->widget_width = -1;
2762 gtk_widget_show (w->drawarea);
2763 gtk_container_add (GTK_CONTAINER (w->base.widget), w->drawarea);
2764 #if !GTK_CHECK_VERSION(3,0,0)
2765 g_signal_connect_after ((gpointer) w->drawarea, "expose_event", G_CALLBACK (coverart_expose_event), w);
2766 #else
2767 g_signal_connect_after ((gpointer) w->drawarea, "draw", G_CALLBACK (coverart_draw), w);
2768 #endif
2769 w_override_signals (w->base.widget, w);
2770 return (ddb_gtkui_widget_t *)w;
2771 }
2772
2773 ///// scope vis
2774 void
w_scope_destroy(ddb_gtkui_widget_t * w)2775 w_scope_destroy (ddb_gtkui_widget_t *w) {
2776 w_scope_t *s = (w_scope_t *)w;
2777 deadbeef->vis_waveform_unlisten (w);
2778 if (s->drawtimer) {
2779 g_source_remove (s->drawtimer);
2780 s->drawtimer = 0;
2781 }
2782 #if USE_OPENGL
2783 if (s->glcontext) {
2784 gdk_gl_context_destroy (s->glcontext);
2785 s->glcontext = NULL;
2786 }
2787 #endif
2788 if (s->surf) {
2789 cairo_surface_destroy (s->surf);
2790 s->surf = NULL;
2791 }
2792 if (s->samples) {
2793 free (s->samples);
2794 s->samples = NULL;
2795 }
2796 if (s->mutex) {
2797 deadbeef->mutex_free (s->mutex);
2798 s->mutex = 0;
2799 }
2800 }
2801
2802 gboolean
w_scope_draw_cb(void * data)2803 w_scope_draw_cb (void *data) {
2804 w_scope_t *s = data;
2805 gtk_widget_queue_draw (s->drawarea);
2806 return TRUE;
2807 }
2808
2809 static void
scope_wavedata_listener(void * ctx,ddb_audio_data_t * data)2810 scope_wavedata_listener (void *ctx, ddb_audio_data_t *data) {
2811 w_scope_t *w = ctx;
2812 if (w->nsamples != w->resized) {
2813 deadbeef->mutex_lock (w->mutex);
2814 float *oldsamples = w->samples;
2815 int oldnsamples = w->nsamples;
2816 w->samples = NULL;
2817 w->nsamples = w->resized;
2818 if (w->nsamples > 0) {
2819 w->samples = malloc (sizeof (float) * w->nsamples);
2820 memset (w->samples, 0, sizeof (float) * w->nsamples);
2821 if (oldsamples) {
2822 int n = min (oldnsamples, w->nsamples);
2823 memcpy (w->samples + w->nsamples - n, oldsamples + oldnsamples - n, n * sizeof (float));
2824 }
2825 }
2826 if (oldnsamples) {
2827 free (oldsamples);
2828 }
2829 deadbeef->mutex_unlock (w->mutex);
2830 }
2831
2832 if (w->samples) {
2833 // append
2834 int nsamples = data->nframes / data->fmt->channels;
2835 float ratio = data->fmt->samplerate / 44100.f;
2836 int size = nsamples / ratio;
2837
2838 int sz = min (w->nsamples, size);
2839 int n = w->nsamples-sz;
2840
2841 memmove (w->samples, w->samples + sz, n * sizeof (float));
2842 float pos = 0;
2843 for (int i = 0; i < sz && pos < nsamples; i++, pos += ratio) {
2844 w->samples[n + i] = data->data[ftoi(pos * data->fmt->channels) * data->fmt->channels + 0];
2845 for (int j = 1; j < data->fmt->channels; j++) {
2846 w->samples[n + i] += data->data[ftoi(pos * data->fmt->channels) * data->fmt->channels + j];
2847 }
2848 w->samples[n+i] /= data->fmt->channels;
2849 }
2850 }
2851 }
2852
2853 #if 0
2854 // Bresenham's line drawing from http://rosettacode.org
2855 static inline void
2856 _draw_line (uint8_t *data, int stride, int x0, int y0, int x1, int y1) {
2857 int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1;
2858 int dy = abs(y1-y0), sy = y0<y1 ? 1 : -1;
2859 int err = (dx>dy ? dx : -dy)/2, e2;
2860
2861 for(;;){
2862 uint32_t *ptr = (uint32_t*)&data[y0*stride+x0*4];
2863 *ptr = 0xffffffff;
2864 if (x0==x1 && y0==y1) break;
2865 e2 = err;
2866 if (e2 >-dx) { err -= dy; x0 += sx; }
2867 if (e2 < dy) { err += dx; y0 += sy; }
2868 }
2869 }
2870 #endif
2871
2872 static inline void
_draw_vline(uint8_t * data,int stride,int x0,int y0,int y1)2873 _draw_vline (uint8_t *data, int stride, int x0, int y0, int y1) {
2874 if (y0 > y1) {
2875 int tmp = y0;
2876 y0 = y1;
2877 y1 = tmp;
2878 y1--;
2879 }
2880 else if (y0 < y1) {
2881 y0++;
2882 }
2883 while (y0 <= y1) {
2884 uint32_t *ptr = (uint32_t*)&data[y0*stride+x0*4];
2885 *ptr = 0xffffffff;
2886 y0++;
2887 }
2888 }
2889
2890 static inline void
_draw_bar(uint8_t * data,int stride,int x0,int y0,int w,int h,uint32_t color)2891 _draw_bar (uint8_t *data, int stride, int x0, int y0, int w, int h, uint32_t color) {
2892 int y1 = y0+h-1;
2893 int x1 = x0+w-1;
2894 uint32_t *ptr = (uint32_t*)&data[y0*stride+x0*4];
2895 while (y0 <= y1) {
2896 int x = x0;
2897 while (x++ <= x1) {
2898 *ptr++ = color;
2899 }
2900 y0++;
2901 ptr += stride/4-w;
2902 }
2903 }
2904
2905 gboolean
scope_draw_cairo(GtkWidget * widget,cairo_t * cr,gpointer user_data)2906 scope_draw_cairo (GtkWidget *widget, cairo_t *cr, gpointer user_data) {
2907 GtkAllocation a;
2908 gtk_widget_get_allocation (widget, &a);
2909
2910 w_scope_t *w = user_data;
2911
2912 if (!w->surf || cairo_image_surface_get_width (w->surf) != a.width || cairo_image_surface_get_height (w->surf) != a.height) {
2913 if (w->surf) {
2914 cairo_surface_destroy (w->surf);
2915 w->surf = NULL;
2916 }
2917 w->surf = cairo_image_surface_create (CAIRO_FORMAT_RGB24, a.width, a.height);
2918 }
2919
2920 int nsamples = a.width;
2921 if (w->nsamples != nsamples) {
2922 w->resized = nsamples;
2923 }
2924 cairo_surface_flush (w->surf);
2925 unsigned char *data = cairo_image_surface_get_data (w->surf);
2926 if (!data) {
2927 return FALSE;
2928 }
2929 int stride = cairo_image_surface_get_stride (w->surf);
2930 memset (data, 0, a.height * stride);
2931 if (w->samples && a.height > 2) {
2932 deadbeef->mutex_lock (w->mutex);
2933 float incr = a.width / (float)w->nsamples;
2934 float h = a.height;
2935 if (h > 50) {
2936 h -= 20;
2937 }
2938 if (h > 100) {
2939 h -= 40;
2940 }
2941 h /= 2;
2942 float hh = a.height/2.f;
2943
2944 int prev_y = w->samples[0] * h + hh;
2945
2946 int n = min (w->nsamples, a.width);
2947 for (int i = 1; i < n; i++) {
2948 int y = ftoi (w->samples[i] * h + hh);
2949 if (y < 0) {
2950 y = 0;
2951 }
2952 if (y >= a.height) {
2953 y = a.height-1;
2954 }
2955 _draw_vline (data, stride, i, prev_y, y);
2956 prev_y = y;
2957 }
2958 if (n < a.width) {
2959 memset (data + a.height / 2 * stride + n * 4, 0xff, (a.width-n)*4);
2960 }
2961 deadbeef->mutex_unlock (w->mutex);
2962 }
2963 else if (a.height > 0) {
2964 memset (data + a.height / 2 *stride, 0xff, stride);
2965 }
2966 cairo_surface_mark_dirty (w->surf);
2967 cairo_save (cr);
2968 cairo_set_source_surface (cr, w->surf, 0, 0);
2969 cairo_rectangle (cr, 0, 0, a.width, a.height);
2970 cairo_fill (cr);
2971 cairo_restore (cr);
2972
2973 return FALSE;
2974 }
2975
2976 gboolean
scope_draw(GtkWidget * widget,cairo_t * cr,gpointer user_data)2977 scope_draw (GtkWidget *widget, cairo_t *cr, gpointer user_data) {
2978 #if USE_OPENGL
2979 GtkAllocation a;
2980 gtk_widget_get_allocation (widget, &a);
2981 w_scope_t *w = user_data;
2982 int nsamples = a.width;
2983 if (w->nsamples != nsamples) {
2984 w->resized = nsamples;
2985 return FALSE;
2986 }
2987 float incr = a.width / (float)w->nsamples;
2988 float h = a.height;
2989 if (h > 50) {
2990 h -= 20;
2991 }
2992 if (h > 100) {
2993 h -= 40;
2994 }
2995 h /= 2;
2996 float hh = a.height/2.f;
2997
2998 GdkGLDrawable *d = gtk_widget_get_gl_drawable (widget);
2999 gdk_gl_drawable_gl_begin (d, w->glcontext);
3000
3001 glClear (GL_COLOR_BUFFER_BIT);
3002 glMatrixMode (GL_PROJECTION);
3003 glLoadIdentity ();
3004 gluOrtho2D(0,a.width,a.height,0);
3005 glMatrixMode (GL_MODELVIEW);
3006 glViewport (0, 0, a.width, a.height);
3007
3008 glBegin (GL_LINE_STRIP);
3009
3010 for (int i = 0; i < w->nsamples; i++) {
3011 float y = w->samples[i] * h + hh;
3012 glVertex2f (i, y);
3013 }
3014
3015 glEnd();
3016 gdk_gl_drawable_swap_buffers (d);
3017
3018 gdk_gl_drawable_gl_end (d);
3019
3020 #else
3021 gboolean res = scope_draw_cairo (widget, cr, user_data);
3022 #endif
3023 return FALSE;
3024 }
3025
3026 gboolean
scope_expose_event(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)3027 scope_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data) {
3028 cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget));
3029 gboolean res = scope_draw (widget, cr, user_data);
3030 cairo_destroy (cr);
3031 return res;
3032 }
3033
3034 void
w_scope_init(ddb_gtkui_widget_t * w)3035 w_scope_init (ddb_gtkui_widget_t *w) {
3036 w_scope_t *s = (w_scope_t *)w;
3037 if (s->drawtimer) {
3038 g_source_remove (s->drawtimer);
3039 s->drawtimer = 0;
3040 }
3041 #if USE_OPENGL
3042 if (!gtkui_gl_init ()) {
3043 s->drawtimer = g_timeout_add (33, w_scope_draw_cb, w);
3044 }
3045 #else
3046 s->drawtimer = g_timeout_add (33, w_scope_draw_cb, w);
3047 #endif
3048 }
3049
3050 void
scope_realize(GtkWidget * widget,gpointer data)3051 scope_realize (GtkWidget *widget, gpointer data) {
3052 w_scope_t *w = data;
3053 #if USE_OPENGL
3054 w->glcontext = gtk_widget_create_gl_context (w->drawarea, NULL, TRUE, GDK_GL_RGBA_TYPE);
3055 #endif
3056 }
3057
3058 ddb_gtkui_widget_t *
w_scope_create(void)3059 w_scope_create (void) {
3060 w_scope_t *w = malloc (sizeof (w_scope_t));
3061 memset (w, 0, sizeof (w_scope_t));
3062
3063 w->base.widget = gtk_event_box_new ();
3064 w->base.init = w_scope_init;
3065 w->base.destroy = w_scope_destroy;
3066 w->drawarea = gtk_drawing_area_new ();
3067 #if USE_OPENGL
3068 int attrlist[] = {GDK_GL_ATTRIB_LIST_NONE};
3069 GdkGLConfig *conf = gdk_gl_config_new_by_mode ((GdkGLConfigMode)(GDK_GL_MODE_RGB |
3070 GDK_GL_MODE_DOUBLE));
3071 gboolean cap = gtk_widget_set_gl_capability (w->drawarea, conf, NULL, TRUE, GDK_GL_RGBA_TYPE);
3072 #endif
3073
3074 w->mutex = deadbeef->mutex_create ();
3075 gtk_widget_show (w->drawarea);
3076 gtk_container_add (GTK_CONTAINER (w->base.widget), w->drawarea);
3077 #if !GTK_CHECK_VERSION(3,0,0)
3078 g_signal_connect_after ((gpointer) w->drawarea, "expose_event", G_CALLBACK (scope_expose_event), w);
3079 #else
3080 g_signal_connect_after ((gpointer) w->drawarea, "draw", G_CALLBACK (scope_draw), w);
3081 #endif
3082 g_signal_connect_after (G_OBJECT (w->drawarea), "realize", G_CALLBACK (scope_realize), w);
3083 w_override_signals (w->base.widget, w);
3084 deadbeef->vis_waveform_listen(w, scope_wavedata_listener);
3085 return (ddb_gtkui_widget_t *)w;
3086 }
3087
3088 ///// spectrum vis
3089 void
w_spectrum_destroy(ddb_gtkui_widget_t * w)3090 w_spectrum_destroy (ddb_gtkui_widget_t *w) {
3091 w_spectrum_t *s = (w_spectrum_t *)w;
3092 deadbeef->vis_spectrum_unlisten (w);
3093 if (s->drawtimer) {
3094 g_source_remove (s->drawtimer);
3095 s->drawtimer = 0;
3096 }
3097 if (s->surf) {
3098 cairo_surface_destroy (s->surf);
3099 s->surf = NULL;
3100 }
3101 #if USE_OPENGL
3102 if (s->glcontext) {
3103 gdk_gl_context_destroy (s->glcontext);
3104 s->glcontext = NULL;
3105 }
3106 #endif
3107 }
3108
3109 gboolean
w_spectrum_draw_cb(void * data)3110 w_spectrum_draw_cb (void *data) {
3111 w_spectrum_t *s = data;
3112 gtk_widget_queue_draw (s->drawarea);
3113 return TRUE;
3114 }
3115
3116
calculate_bands(w_spectrum_t * w,int bands)3117 static void calculate_bands(w_spectrum_t *w, int bands)
3118 {
3119 int i;
3120
3121 for (i = 0; i <= bands; i++)
3122 w->xscale[i] = powf((float)(MAX_BANDS+1), ((float) i / (float) bands)) - 1;
3123 }
3124
3125 static void
spectrum_audio_listener(void * ctx,ddb_audio_data_t * data)3126 spectrum_audio_listener (void *ctx, ddb_audio_data_t *data) {
3127 w_spectrum_t *w = ctx;
3128 memcpy (w->data, data->data, DDB_FREQ_BANDS * sizeof (float));
3129 }
3130
3131 static gboolean
spectrum_draw(GtkWidget * widget,cairo_t * cr,gpointer user_data)3132 spectrum_draw (GtkWidget *widget, cairo_t *cr, gpointer user_data) {
3133 w_spectrum_t *w = user_data;
3134 float *freq = w->data;
3135
3136 GtkAllocation a;
3137 gtk_widget_get_allocation (widget, &a);
3138
3139 int width, height, bands;
3140 bands = a.width/BAND_WIDTH;
3141 bands = CLAMP(bands, 4, MAX_BANDS);
3142 width = a.width;
3143 height = a.height;
3144 calculate_bands(w, bands);
3145
3146 for (int i = 0; i <= bands; i ++)
3147 {
3148 int a = ceil (w->xscale[i]);
3149 int b = floor (w->xscale[i + 1]);
3150 float n = 0;
3151
3152 if (b < a)
3153 n += freq[b] * (w->xscale[i + 1] - w->xscale[i]);
3154 else
3155 {
3156 if (a > 0)
3157 n += freq[a - 1] * (a - w->xscale[i]);
3158 for (; a < b; a ++)
3159 n += freq[a];
3160 if (b < MAX_BANDS)
3161 n += freq[b] * (w->xscale[i + 1] - b);
3162 }
3163
3164 /* 40 dB range */
3165 int x = 20 * log10 (n * 200);
3166 x = CLAMP (x, 0, 40);
3167
3168 w->bars[i] -= MAX (0, VIS_FALLOFF - w->delay[i]);
3169 w->peaks[i] -= MAX (0, VIS_FALLOFF_PEAK - w->delay_peak[i]);;
3170
3171 if (w->delay[i])
3172 w->delay[i]--;
3173 if (w->delay_peak[i])
3174 w->delay_peak[i]--;
3175
3176 if (x > w->bars[i])
3177 {
3178 w->bars[i] = x;
3179 w->delay[i] = VIS_DELAY;
3180 }
3181 if (x > w->peaks[i]) {
3182 w->peaks[i] = x;
3183 w->delay_peak[i] = VIS_DELAY_PEAK;
3184 }
3185 if (w->peaks[i] < w->bars[i]) {
3186 w->peaks[i] = w->bars[i];
3187 }
3188 }
3189
3190 #if USE_OPENGL
3191 GdkGLDrawable *d = gtk_widget_get_gl_drawable (widget);
3192 gdk_gl_drawable_gl_begin (d, w->glcontext);
3193
3194 glClear (GL_COLOR_BUFFER_BIT);
3195 glMatrixMode (GL_PROJECTION);
3196 glLoadIdentity ();
3197 gluOrtho2D(0,a.width,a.height,0);
3198 glMatrixMode (GL_MODELVIEW);
3199 glViewport (0, 0, a.width, a.height);
3200
3201 glBegin (GL_QUADS);
3202 float base_s = (height / 40.f);
3203
3204 for (gint i = 0; i <= bands; i++)
3205 {
3206 gint x = ((width / bands) * i) + 2;
3207 int y = a.height - w->bars[i] * base_s;
3208 glColor3f (0, 0.5, 1);
3209 glVertex2f (x + 1, y);
3210 glVertex2f (x + 1 + (width / bands) - 1, y);
3211 glVertex2f (x + 1 + (width / bands) - 1, a.height);
3212 glVertex2f (x + 1, a.height);
3213
3214 // peak
3215 glColor3f (1, 1, 1);
3216 y = a.height - w->peaks[i] * base_s;
3217 glVertex2f (x + 1, y);
3218 glVertex2f (x + 1 + (width / bands) - 1, y);
3219 glVertex2f (x + 1 + (width / bands) - 1, y+1);
3220 glVertex2f (x + 1, y+1);
3221 }
3222 glEnd();
3223 gdk_gl_drawable_swap_buffers (d);
3224
3225 gdk_gl_drawable_gl_end (d);
3226 #else
3227 if (!w->surf || cairo_image_surface_get_width (w->surf) != a.width || cairo_image_surface_get_height (w->surf) != a.height) {
3228 if (w->surf) {
3229 cairo_surface_destroy (w->surf);
3230 w->surf = NULL;
3231 }
3232 w->surf = cairo_image_surface_create (CAIRO_FORMAT_RGB24, a.width, a.height);
3233 }
3234 float base_s = (height / 40.f);
3235
3236 cairo_surface_flush (w->surf);
3237 unsigned char *data = cairo_image_surface_get_data (w->surf);
3238 if (!data) {
3239 return FALSE;
3240 }
3241 int stride = cairo_image_surface_get_stride (w->surf);
3242 memset (data, 0, a.height * stride);
3243
3244 int barw = width / bands;
3245 for (gint i = 0; i <= bands; i++)
3246 {
3247 int x = barw * i;
3248 int y = a.height - w->bars[i] * base_s;
3249 if (y < 0) {
3250 y = 0;
3251 }
3252 int bw = barw-1;
3253 if (x + bw >= a.width) {
3254 bw = a.width-x-1;
3255 }
3256 _draw_bar (data, stride, x+1, y, bw, a.height-y, 0xff007fff);
3257 y = a.height - w->peaks[i] * base_s;
3258 if (y < a.height-1) {
3259 _draw_bar (data, stride, x + 1, y, bw, 1, 0xffffffff);
3260 }
3261 }
3262 cairo_surface_mark_dirty (w->surf);
3263 cairo_save (cr);
3264 cairo_set_source_surface (cr, w->surf, 0, 0);
3265 cairo_rectangle (cr, 0, 0, a.width, a.height);
3266 cairo_fill (cr);
3267 cairo_restore (cr);
3268
3269 #endif
3270 return FALSE;
3271 }
3272
3273
3274 gboolean
spectrum_expose_event(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)3275 spectrum_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data) {
3276 cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget));
3277 gboolean res = spectrum_draw (widget, cr, user_data);
3278 cairo_destroy (cr);
3279 return res;
3280 }
3281 void
w_spectrum_init(ddb_gtkui_widget_t * w)3282 w_spectrum_init (ddb_gtkui_widget_t *w) {
3283 w_spectrum_t *s = (w_spectrum_t *)w;
3284 if (s->drawtimer) {
3285 g_source_remove (s->drawtimer);
3286 s->drawtimer = 0;
3287 }
3288 #if USE_OPENGL
3289 if (!gtkui_gl_init ()) {
3290 s->drawtimer = g_timeout_add (33, w_spectrum_draw_cb, w);
3291 }
3292 #else
3293 s->drawtimer = g_timeout_add (33, w_spectrum_draw_cb, w);
3294 #endif
3295 }
3296
3297 void
spectrum_realize(GtkWidget * widget,gpointer data)3298 spectrum_realize (GtkWidget *widget, gpointer data) {
3299 w_spectrum_t *w = data;
3300 #if USE_OPENGL
3301 w->glcontext = gtk_widget_create_gl_context (w->drawarea, NULL, TRUE, GDK_GL_RGBA_TYPE);
3302 #endif
3303 }
3304
3305 ddb_gtkui_widget_t *
w_spectrum_create(void)3306 w_spectrum_create (void) {
3307 w_spectrum_t *w = malloc (sizeof (w_spectrum_t));
3308 memset (w, 0, sizeof (w_spectrum_t));
3309
3310 w->base.widget = gtk_event_box_new ();
3311 w->base.init = w_spectrum_init;
3312 w->base.destroy = w_spectrum_destroy;
3313 w->drawarea = gtk_drawing_area_new ();
3314 #if USE_OPENGL
3315 int attrlist[] = {GDK_GL_ATTRIB_LIST_NONE};
3316 GdkGLConfig *conf = gdk_gl_config_new_by_mode ((GdkGLConfigMode)(GDK_GL_MODE_RGB |
3317 GDK_GL_MODE_DOUBLE));
3318 gboolean cap = gtk_widget_set_gl_capability (w->drawarea, conf, NULL, TRUE, GDK_GL_RGBA_TYPE);
3319 #endif
3320 gtk_widget_show (w->drawarea);
3321 gtk_container_add (GTK_CONTAINER (w->base.widget), w->drawarea);
3322 #if !GTK_CHECK_VERSION(3,0,0)
3323 g_signal_connect_after ((gpointer) w->drawarea, "expose_event", G_CALLBACK (spectrum_expose_event), w);
3324 #else
3325 g_signal_connect_after ((gpointer) w->drawarea, "draw", G_CALLBACK (spectrum_draw), w);
3326 #endif
3327 g_signal_connect_after (G_OBJECT (w->drawarea), "realize", G_CALLBACK (spectrum_realize), w);
3328 w_override_signals (w->base.widget, w);
3329 deadbeef->vis_spectrum_listen (w, spectrum_audio_listener);
3330 return (ddb_gtkui_widget_t *)w;
3331 }
3332
3333 // hbox and vbox
3334 static const char *
w_hvbox_load(struct ddb_gtkui_widget_s * w,const char * type,const char * s)3335 w_hvbox_load (struct ddb_gtkui_widget_s *w, const char *type, const char *s) {
3336 if (strcmp (type, "hbox") && strcmp (type, "vbox")) {
3337 return NULL;
3338 }
3339 w_hvbox_t *hvbox = (w_hvbox_t *)w;
3340
3341 char key[MAX_TOKEN], val[MAX_TOKEN];
3342 for (;;) {
3343 get_keyvalue (s,key,val);
3344
3345 if (!strcmp (key, "expand")) {
3346 const char *s = val;
3347 int n = 0;
3348 hvbox->expand = 0;
3349 char t[MAX_TOKEN];
3350 while (n < 64) {
3351 s = gettoken (s, t);
3352 if (!s) {
3353 break;
3354 }
3355 if (atoi (t)) {
3356 hvbox->expand |= (1ULL << n);
3357 }
3358 n++;
3359 }
3360 }
3361 else if (!strcmp (key, "fill")) {
3362 const char *s = val;
3363 int n = 0;
3364 hvbox->fill = 0;
3365 char t[MAX_TOKEN];
3366 while (n < 64) {
3367 s = gettoken (s, t);
3368 if (!s) {
3369 break;
3370 }
3371 if (atoi (t)) {
3372 hvbox->fill |= (1ULL << n);
3373 }
3374 n++;
3375 }
3376 }
3377 else if (!strcmp (key, "homogeneous")) {
3378 hvbox->homogeneous = atoi (val) ? 1 : 0;
3379 }
3380 }
3381
3382 return s;
3383 }
3384
3385 typedef struct {
3386 GtkWidget *hvbox;
3387 char expand[150];
3388 char fill[150];
3389 } w_hvbox_save_info_t;
3390
3391 static void
save_hvbox_packing(GtkWidget * child,gpointer user_data)3392 save_hvbox_packing (GtkWidget *child, gpointer user_data) {
3393 w_hvbox_save_info_t *info = user_data;
3394 gboolean expand;
3395 gboolean fill;
3396 guint padding;
3397 GtkPackType pack_type;
3398 gtk_box_query_child_packing (GTK_BOX (info->hvbox), child, &expand, &fill, &padding, &pack_type);
3399 char s[10];
3400 snprintf (s, sizeof (s), info->expand[0] ? " %d" : "%d", expand);
3401 strncat (info->expand, s, sizeof (info->expand) - strlen (info->expand));
3402
3403 snprintf (s, sizeof (s), info->fill[0] ? " %d" : "%d", fill);
3404 strncat (info->fill, s, sizeof (info->fill) - strlen (info->fill));
3405 }
3406
3407 static void
w_hvbox_save(struct ddb_gtkui_widget_s * w,char * s,int sz)3408 w_hvbox_save (struct ddb_gtkui_widget_s *w, char *s, int sz) {
3409 char save[300];
3410
3411 w_hvbox_save_info_t info;
3412 memset (&info, 0, sizeof (info));
3413 info.hvbox = ((w_hvbox_t *)w)->box;
3414 gtk_container_foreach (GTK_CONTAINER (((w_hvbox_t *)w)->box), save_hvbox_packing, &info);
3415 gboolean homogeneous = gtk_box_get_homogeneous (GTK_BOX (((w_hvbox_t *)w)->box));
3416
3417 snprintf (save, sizeof (save), " expand=\"%s\" fill=\"%s\" homogeneous=%d", info.expand, info.fill, homogeneous);
3418 strncat (s, save, sz);
3419 }
3420
3421 typedef struct {
3422 w_hvbox_t *w;
3423 int n;
3424 } hwbox_init_info_t;
3425
3426 static void
hvbox_init_child(GtkWidget * child,void * user_data)3427 hvbox_init_child (GtkWidget *child, void *user_data) {
3428 hwbox_init_info_t *info = user_data;
3429 gboolean expand;
3430 gboolean fill;
3431 guint padding;
3432 GtkPackType pack_type;
3433 gtk_box_query_child_packing (GTK_BOX (info->w->box), child, &expand, &fill, &padding, &pack_type);
3434 expand = (info->w->expand & (1<<info->n)) ? TRUE : FALSE;
3435 fill = (info->w->fill & (1<<info->n)) ? TRUE : FALSE;
3436 gtk_box_set_child_packing (GTK_BOX (info->w->box), child, expand, fill, padding, pack_type);
3437 info->n++;
3438 }
3439
3440 static void
w_hvbox_init(struct ddb_gtkui_widget_s * w)3441 w_hvbox_init (struct ddb_gtkui_widget_s *w) {
3442 w_hvbox_t *hvbox = (w_hvbox_t *)w;
3443 hwbox_init_info_t info;
3444 info.w = hvbox;
3445 info.n = 0;
3446 gtk_container_foreach (GTK_CONTAINER (hvbox->box), hvbox_init_child, &info);
3447 gtk_box_set_homogeneous (GTK_BOX (hvbox->box), hvbox->homogeneous ? TRUE : FALSE);
3448 }
3449
3450 static void
w_hvbox_append(struct ddb_gtkui_widget_s * container,struct ddb_gtkui_widget_s * child)3451 w_hvbox_append (struct ddb_gtkui_widget_s *container, struct ddb_gtkui_widget_s *child) {
3452 w_hvbox_t *b = (w_hvbox_t *)container;
3453 gtk_box_pack_start (GTK_BOX (b->box), child->widget, (child->flags & DDB_GTKUI_WIDGET_FLAG_NON_EXPANDABLE) ? FALSE : TRUE, TRUE, 0);
3454 gtk_widget_show (child->widget);
3455 }
3456
3457 static void
w_hvbox_remove(struct ddb_gtkui_widget_s * container,struct ddb_gtkui_widget_s * child)3458 w_hvbox_remove (struct ddb_gtkui_widget_s *container, struct ddb_gtkui_widget_s *child) {
3459 w_hvbox_t *b = (w_hvbox_t *)container;
3460 gtk_container_remove (GTK_CONTAINER (b->box), child->widget);
3461 }
3462
3463 static void
w_hvbox_replace(struct ddb_gtkui_widget_s * container,struct ddb_gtkui_widget_s * child,struct ddb_gtkui_widget_s * newchild)3464 w_hvbox_replace (struct ddb_gtkui_widget_s *container, struct ddb_gtkui_widget_s *child, struct ddb_gtkui_widget_s *newchild) {
3465 w_hvbox_t *b = (w_hvbox_t *)container;
3466 ddb_gtkui_widget_t *c;
3467 ddb_gtkui_widget_t *prev = NULL;
3468 int n = 0;
3469 for (c = container->children; c; prev = c, c = c->next, n++) {
3470 if (c == child) {
3471 break;
3472 }
3473 }
3474
3475 if (!c) {
3476 return;
3477 }
3478
3479 if (prev) {
3480 prev->next = newchild;
3481 }
3482 else {
3483 container->children = newchild;
3484 }
3485 newchild->next = c->next;
3486 newchild->parent = container;
3487 w_remove (container, c);
3488 w_destroy (c);
3489
3490 gtk_box_pack_start (GTK_BOX (b->box), newchild->widget, TRUE, TRUE, 0);
3491 gtk_widget_show (newchild->widget);
3492 gtk_box_reorder_child (GTK_BOX (b->box), newchild->widget, n);
3493
3494 }
3495
3496 static void
on_hvbox_expand(GtkMenuItem * menuitem,gpointer user_data)3497 on_hvbox_expand (GtkMenuItem *menuitem, gpointer user_data) {
3498 w_append ((ddb_gtkui_widget_t*)user_data, w_create ("placeholder"));
3499 }
3500
3501 static void
on_hvbox_shrink(GtkMenuItem * menuitem,gpointer user_data)3502 on_hvbox_shrink (GtkMenuItem *menuitem, gpointer user_data) {
3503 ddb_gtkui_widget_t *w = (ddb_gtkui_widget_t *)user_data;
3504 ddb_gtkui_widget_t *c;
3505 for (c = w->children; c && c->next; c = c->next);
3506 if (c) {
3507 w_remove (w, c);
3508 }
3509 if (!w->children) {
3510 w_append (w, w_create ("placeholder"));
3511 }
3512 }
3513
3514 static void
on_hvbox_toggle_homogeneous(GtkCheckMenuItem * checkmenuitem,gpointer user_data)3515 on_hvbox_toggle_homogeneous (GtkCheckMenuItem *checkmenuitem, gpointer user_data) {
3516 w_hvbox_t *w = user_data;
3517 gboolean hmg = gtk_box_get_homogeneous (GTK_BOX (((w_hvbox_t *)w)->box));
3518 gtk_box_set_homogeneous (GTK_BOX (((w_hvbox_t *)w->box)), hmg ? FALSE : TRUE);
3519 }
3520
3521
3522 static void
w_hvbox_initmenu(struct ddb_gtkui_widget_s * w,GtkWidget * menu)3523 w_hvbox_initmenu (struct ddb_gtkui_widget_s *w, GtkWidget *menu) {
3524 GtkWidget *item;
3525 item = gtk_menu_item_new_with_mnemonic (_("Expand the box by 1 item"));
3526 gtk_widget_show (item);
3527 gtk_container_add (GTK_CONTAINER (menu), item);
3528 g_signal_connect ((gpointer) item, "activate", G_CALLBACK (on_hvbox_expand), w);
3529
3530 item = gtk_menu_item_new_with_mnemonic (_("Shrink the box by 1 item"));
3531 gtk_widget_show (item);
3532 gtk_container_add (GTK_CONTAINER (menu), item);
3533 g_signal_connect ((gpointer) item, "activate", G_CALLBACK (on_hvbox_shrink), w);
3534
3535 item = gtk_check_menu_item_new_with_mnemonic (_("Homogeneous"));
3536 gtk_widget_show (item);
3537 gtk_container_add (GTK_CONTAINER (menu), item);
3538 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), gtk_box_get_homogeneous (GTK_BOX (((w_hvbox_t *)w)->box)));
3539 g_signal_connect ((gpointer) item, "toggled", G_CALLBACK (on_hvbox_toggle_homogeneous), w);
3540 }
3541
3542 static void
on_hvbox_child_toggle_expand(GtkCheckMenuItem * checkmenuitem,gpointer user_data)3543 on_hvbox_child_toggle_expand (GtkCheckMenuItem *checkmenuitem, gpointer user_data) {
3544 ddb_gtkui_widget_t *w = user_data;
3545 w_hvbox_t *box = (w_hvbox_t *)w->parent;
3546 gboolean expand, fill;
3547 guint padding;
3548 GtkPackType packtype;
3549 gtk_box_query_child_packing (GTK_BOX (box->box), w->widget, &expand, &fill, &padding, &packtype);
3550 gtk_box_set_child_packing (GTK_BOX (box->box), w->widget, !expand, fill, padding, packtype);
3551 }
3552
3553 static void
on_hvbox_child_toggle_fill(GtkCheckMenuItem * checkmenuitem,gpointer user_data)3554 on_hvbox_child_toggle_fill (GtkCheckMenuItem *checkmenuitem, gpointer user_data) {
3555 ddb_gtkui_widget_t *w = user_data;
3556 w_hvbox_t *box = (w_hvbox_t *)w->parent;
3557 gboolean expand, fill;
3558 guint padding;
3559 GtkPackType packtype;
3560 gtk_box_query_child_packing (GTK_BOX (box->box), w->widget, &expand, &fill, &padding, &packtype);
3561 gtk_box_set_child_packing (GTK_BOX (box->box), w->widget, expand, !fill, padding, packtype);
3562 }
3563
3564 static void
w_hvbox_initchildmenu(struct ddb_gtkui_widget_s * w,GtkWidget * menu)3565 w_hvbox_initchildmenu (struct ddb_gtkui_widget_s *w, GtkWidget *menu) {
3566 w_hvbox_t *box = (w_hvbox_t *)w->parent;
3567
3568 gboolean expand, fill;
3569 guint padding;
3570 GtkPackType packtype;
3571 gtk_box_query_child_packing (GTK_BOX (box->box), w->widget, &expand, &fill, &padding, &packtype);
3572
3573 GtkWidget *item;
3574
3575 item = gtk_check_menu_item_new_with_mnemonic (_("Expand"));
3576 gtk_widget_show (item);
3577 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), expand);
3578 gtk_container_add (GTK_CONTAINER (menu), item);
3579 g_signal_connect ((gpointer) item, "toggled",
3580 G_CALLBACK (on_hvbox_child_toggle_expand),
3581 w);
3582
3583 item = gtk_check_menu_item_new_with_mnemonic (_("Fill"));
3584 gtk_widget_show (item);
3585 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), fill);
3586 gtk_container_add (GTK_CONTAINER (menu), item);
3587 g_signal_connect ((gpointer) item, "toggled",
3588 G_CALLBACK (on_hvbox_child_toggle_fill),
3589 w);
3590 }
3591
3592 GtkWidget *
w_hvbox_get_container(struct ddb_gtkui_widget_s * b)3593 w_hvbox_get_container (struct ddb_gtkui_widget_s *b) {
3594 return ((w_hvbox_t *)b)->box;
3595 }
3596
3597 ddb_gtkui_widget_t *
w_hbox_create(void)3598 w_hbox_create (void) {
3599 w_hvbox_t *w = malloc (sizeof (w_hvbox_t));
3600 memset (w, 0, sizeof (w_hvbox_t));
3601 w->base.widget = gtk_event_box_new ();
3602 w->base.append = w_hvbox_append;
3603 w->base.remove = w_hvbox_remove;
3604 w->base.replace = w_hvbox_replace;
3605 w->base.initmenu = w_hvbox_initmenu;
3606 w->base.initchildmenu = w_hvbox_initchildmenu;
3607 w->base.load = w_hvbox_load;
3608 w->base.save = w_hvbox_save;
3609 w->base.init = w_hvbox_init;
3610 w->base.get_container = w_hvbox_get_container;
3611 w->box = gtk_hbox_new (TRUE, 3);
3612 w->homogeneous = 1;
3613 w->expand = -1;
3614 w->fill = -1;
3615 gtk_widget_show (w->box);
3616 gtk_container_add (GTK_CONTAINER (w->base.widget), w->box);
3617
3618 w_append ((ddb_gtkui_widget_t*)w, w_create ("placeholder"));
3619 w_append ((ddb_gtkui_widget_t*)w, w_create ("placeholder"));
3620 w_append ((ddb_gtkui_widget_t*)w, w_create ("placeholder"));
3621
3622 w_override_signals (w->base.widget, w);
3623 return (ddb_gtkui_widget_t *)w;
3624 }
3625
3626 ddb_gtkui_widget_t *
w_vbox_create(void)3627 w_vbox_create (void) {
3628 w_hvbox_t *w = malloc (sizeof (w_hvbox_t));
3629 memset (w, 0, sizeof (w_hvbox_t));
3630 w->base.widget = gtk_event_box_new ();
3631 w->base.append = w_hvbox_append;
3632 w->base.remove = w_hvbox_remove;
3633 w->base.replace = w_hvbox_replace;
3634 w->base.get_container = w_hvbox_get_container;
3635 w->base.initmenu = w_hvbox_initmenu;
3636 w->base.initchildmenu = w_hvbox_initchildmenu;
3637 w->base.load = w_hvbox_load;
3638 w->base.save = w_hvbox_save;
3639 w->base.init = w_hvbox_init;
3640 w->box = gtk_vbox_new (TRUE, 3);
3641 w->homogeneous = 1;
3642 w->expand = -1;
3643 w->fill = -1;
3644 gtk_widget_show (w->box);
3645 gtk_container_add (GTK_CONTAINER (w->base.widget), w->box);
3646
3647 w_append ((ddb_gtkui_widget_t*)w, w_create ("placeholder"));
3648 w_append ((ddb_gtkui_widget_t*)w, w_create ("placeholder"));
3649 w_append ((ddb_gtkui_widget_t*)w, w_create ("placeholder"));
3650
3651 w_override_signals (w->base.widget, w);
3652 return (ddb_gtkui_widget_t *)w;
3653 }
3654
3655 // button widget
3656 static const char *
w_button_load(struct ddb_gtkui_widget_s * w,const char * type,const char * s)3657 w_button_load (struct ddb_gtkui_widget_s *w, const char *type, const char *s) {
3658 if (strcmp (type, "button")) {
3659 return NULL;
3660 }
3661 w_button_t *b = (w_button_t *)w;
3662 char key[MAX_TOKEN], val[MAX_TOKEN];
3663 for (;;) {
3664 get_keyvalue (s, key, val);
3665 if (!strcmp (key, "color")) {
3666 int red, green, blue;
3667 if (3 == sscanf (val, "#%02x%02x%02x", &red, &green, &blue)) {
3668 b->color.red = red << 8;
3669 b->color.green = green << 8;
3670 b->color.blue = blue << 8;
3671 }
3672 }
3673 else if (!strcmp (key, "textcolor")) {
3674 int red, green, blue;
3675 if (3 == sscanf (val, "#%02x%02x%02x", &red, &green, &blue)) {
3676 b->textcolor.red = red << 8;
3677 b->textcolor.green = green << 8;
3678 b->textcolor.blue = blue << 8;
3679 }
3680 }
3681 else if (!strcmp (key, "icon")) {
3682 b->icon = val[0] ? strdup (val) : NULL;
3683 }
3684 else if (!strcmp (key, "label")) {
3685 b->label = val[0] ? strdup (val) : NULL;
3686 }
3687 else if (!strcmp (key, "action")) {
3688 b->action = val[0] ? strdup (val) : NULL;
3689 }
3690 else if (!strcmp (key, "action_ctx")) {
3691 b->action_ctx = atoi (val);
3692 }
3693 else if (!strcmp (key, "use_color")) {
3694 b->use_color = atoi (val);
3695 }
3696 else if (!strcmp (key, "use_textcolor")) {
3697 b->use_textcolor = atoi (val);
3698 }
3699 }
3700
3701 return s;
3702 }
3703
3704 static void
w_button_save(struct ddb_gtkui_widget_s * w,char * s,int sz)3705 w_button_save (struct ddb_gtkui_widget_s *w, char *s, int sz) {
3706 char save[1000] = "";
3707 char *pp = save;
3708 int ss = sizeof (save);
3709 int n;
3710
3711 w_button_t *b = (w_button_t *)w;
3712 n = snprintf (pp, ss, " color=\"#%02x%02x%02x\"", b->color.red>>8, b->color.green>>8, b->color.blue>>8);
3713 ss -= n;
3714 pp += n;
3715 n = snprintf (pp, ss, " textcolor=\"#%02x%02x%02x\"", b->textcolor.red>>8, b->textcolor.green>>8, b->textcolor.blue>>8);
3716 ss -= n;
3717 pp += n;
3718 if (b->icon) {
3719 n = snprintf (pp, ss, " icon=\"%s\"", b->icon);
3720 ss -= n;
3721 pp += n;
3722 }
3723 if (b->label) {
3724 n = snprintf (pp, ss, " label=\"%s\"", b->label);
3725 ss -= n;
3726 pp += n;
3727 }
3728 if (b->action) {
3729 n = snprintf (pp, ss, " action=\"%s\"", b->action);
3730 ss -= n;
3731 pp += n;
3732 }
3733 if (b->action_ctx) {
3734 n = snprintf (pp, ss, " action_ctx=%d", (int)b->action_ctx);
3735 ss -= n;
3736 pp += n;
3737 }
3738
3739 n = snprintf (pp, ss, " use_color=%d", (int)b->use_color);
3740 ss -= n;
3741 pp += n;
3742
3743 n = snprintf (pp, ss, " use_textcolor=%d", (int)b->use_textcolor);
3744 ss -= n;
3745 pp += n;
3746
3747 strncat (s, save, sz);
3748 }
3749
3750 static void
on_button_clicked(GtkButton * button,gpointer user_data)3751 on_button_clicked (GtkButton *button,
3752 gpointer user_data)
3753 {
3754 w_button_t *w = user_data;
3755 DB_plugin_t **plugins = deadbeef->plug_get_list();
3756 int i;
3757 int added_entries = 0;
3758 for (i = 0; plugins[i]; i++) {
3759 if (!plugins[i]->get_actions) {
3760 continue;
3761 }
3762 DB_plugin_action_t *acts = plugins[i]->get_actions (NULL);
3763 while (acts) {
3764 if (!strcmp (acts->name, w->action)) {
3765 if (acts->callback) {
3766 gtkui_exec_action_14 (acts, -1);
3767 }
3768 else if (acts->callback2) {
3769 acts->callback2 (acts, w->action_ctx);
3770 }
3771 return;
3772 }
3773 acts = acts->next;
3774 }
3775 }
3776 }
3777
3778 static void
w_button_init(ddb_gtkui_widget_t * ww)3779 w_button_init (ddb_gtkui_widget_t *ww) {
3780 w_button_t *w = (w_button_t *)ww;
3781
3782 // clean before re-creating
3783 if (w->button) {
3784 gtk_widget_destroy (w->button);
3785 w->button = NULL;
3786 }
3787
3788 w->button = gtk_button_new ();
3789 gtk_widget_show (w->button);
3790 gtk_container_add (GTK_CONTAINER (w->base.widget), w->button);
3791
3792 GtkWidget *alignment = gtk_alignment_new (0.5, 0.5, 0, 0);
3793 gtk_widget_show (alignment);
3794 gtk_container_add (GTK_CONTAINER (w->button), alignment);
3795
3796 GtkWidget *hbox = gtk_hbox_new (FALSE, 2);
3797 gtk_widget_show (hbox);
3798 gtk_container_add (GTK_CONTAINER (alignment), hbox);
3799
3800 if (w->icon) {
3801 GtkWidget *image = gtk_image_new_from_stock (w->icon, GTK_ICON_SIZE_BUTTON);
3802 gtk_widget_show (image);
3803 gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
3804 }
3805
3806 GtkWidget *label = gtk_label_new_with_mnemonic (w->label ? w->label : _("Button"));
3807 gtk_widget_show (label);
3808 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
3809
3810 if (w->use_color) {
3811 gtk_widget_modify_bg (w->button, GTK_STATE_NORMAL, &w->color);
3812 }
3813
3814 if (w->use_textcolor) {
3815 gtk_widget_modify_fg (label, GTK_STATE_NORMAL, &w->textcolor);
3816 }
3817
3818 if (w->action) {
3819 g_signal_connect ((gpointer) w->button, "clicked",
3820 G_CALLBACK (on_button_clicked),
3821 w);
3822 }
3823
3824 w_override_signals (w->button, w);
3825 }
3826
3827 static void
w_button_destroy(ddb_gtkui_widget_t * w)3828 w_button_destroy (ddb_gtkui_widget_t *w) {
3829 w_button_t *b = (w_button_t *)w;
3830 if (b->icon) {
3831 free (b->icon);
3832 }
3833 if (b->label) {
3834 free (b->label);
3835 }
3836 if (b->action) {
3837 free (b->action);
3838 }
3839 }
3840
3841 static void
on_button_set_action_clicked(GtkButton * button,gpointer user_data)3842 on_button_set_action_clicked (GtkButton *button,
3843 gpointer user_data)
3844 {
3845 w_button_t *b = user_data;
3846 GtkWidget *dlg = create_select_action ();
3847 GtkWidget *treeview = lookup_widget (dlg, "actions");
3848 init_action_tree (treeview, b->action, b->action_ctx);
3849 int response = gtk_dialog_run (GTK_DIALOG (dlg));
3850 if (response == GTK_RESPONSE_OK) {
3851 if (b->action) {
3852 free (b->action);
3853 b->action = NULL;
3854 }
3855 b->action_ctx = -1;
3856 GtkTreePath *path;
3857 gtk_tree_view_get_cursor (GTK_TREE_VIEW (treeview), &path, NULL);
3858 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
3859 GtkTreeIter iter;
3860 if (path && gtk_tree_model_get_iter (model, &iter, path)) {
3861 GValue val = {0,};
3862 gtk_tree_model_get_value (model, &iter, 1, &val);
3863 const gchar *name = g_value_get_string (&val);
3864 GValue val_ctx = {0,};
3865 gtk_tree_model_get_value (model, &iter, 2, &val_ctx);
3866 int ctx = g_value_get_int (&val_ctx);
3867 if (name && ctx >= 0) {
3868 b->action = strdup (name);
3869 b->action_ctx = ctx;
3870 }
3871 }
3872 set_button_action_label (b->action, b->action_ctx, GTK_WIDGET (button));
3873 }
3874 gtk_widget_destroy (dlg);
3875 }
3876
3877 static void
on_button_config(GtkMenuItem * menuitem,gpointer user_data)3878 on_button_config (GtkMenuItem *menuitem, gpointer user_data) {
3879 w_button_t *b = user_data;
3880 GtkWidget *dlg = create_button_properties ();
3881 GtkWidget *color = lookup_widget (dlg, "color");
3882 GtkWidget *use_color = lookup_widget (dlg, "use_color");
3883 GtkWidget *textcolor = lookup_widget (dlg, "textcolor");
3884 GtkWidget *use_textcolor = lookup_widget (dlg, "use_textcolor");
3885 GtkWidget *label = lookup_widget (dlg, "label");
3886 GtkWidget *action = lookup_widget (dlg, "action");
3887 GtkWidget *icon = lookup_widget (dlg, "icon");
3888 gtk_color_button_set_color (GTK_COLOR_BUTTON (color), &b->color);
3889 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (use_color), b->use_color);
3890 gtk_color_button_set_color (GTK_COLOR_BUTTON (textcolor), &b->textcolor);
3891 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (use_textcolor), b->use_textcolor);
3892 gtk_entry_set_text (GTK_ENTRY (label), b->label ? b->label : "");
3893 set_button_action_label (b->action, b->action_ctx, action);
3894 g_signal_connect ((gpointer) action, "clicked",
3895 G_CALLBACK (on_button_set_action_clicked),
3896 user_data);
3897
3898 GtkListStore *store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
3899
3900 GtkTreeIter iter;
3901 gtk_list_store_append (store, &iter);
3902 gtk_list_store_set (store, &iter, 0, NULL, 1, _("None"), -1);
3903 int sel = 0;
3904 for (int n = 0; GtkNamedIcons[n]; n++) {
3905 gtk_list_store_append (store, &iter);
3906
3907 GtkStockItem it;
3908 if (gtk_stock_lookup (GtkNamedIcons[n], &it)) {
3909 char *s = strdupa (it.label);
3910 for (char *c = s; *c; c++) {
3911 if (*c == '_') {
3912 memmove (c, c+1, strlen (c));
3913 c--;
3914 }
3915 }
3916 gtk_list_store_set (store, &iter, 0, GtkNamedIcons[n], 1, s, -1);
3917 }
3918 else {
3919 gtk_list_store_set (store, &iter, 0, GtkNamedIcons[n], 1, GtkNamedIcons[n], -1);
3920 }
3921
3922 if (b->icon && !strcmp (GtkNamedIcons[n], b->icon)) {
3923 sel = n+1;
3924 }
3925 }
3926
3927 gtk_cell_layout_clear (GTK_CELL_LAYOUT (icon));
3928 GtkCellRenderer *renderer;
3929 renderer = gtk_cell_renderer_pixbuf_new ();
3930 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (icon), renderer, FALSE );
3931 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (icon), renderer, "stock-id", 0, NULL );
3932
3933 renderer = gtk_cell_renderer_text_new ();
3934 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (icon), renderer, FALSE );
3935 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (icon), renderer, "text", 1, NULL );
3936
3937
3938 gtk_combo_box_set_model (GTK_COMBO_BOX (icon), GTK_TREE_MODEL (store));
3939
3940 gtk_combo_box_set_active (GTK_COMBO_BOX (icon), sel);
3941
3942 for (;;) {
3943 int response = gtk_dialog_run (GTK_DIALOG (dlg));
3944 if (response == GTK_RESPONSE_OK || response == GTK_RESPONSE_APPLY) {
3945 gtk_color_button_get_color (GTK_COLOR_BUTTON (color), &b->color);
3946 b->use_color = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (use_color));
3947 gtk_color_button_get_color (GTK_COLOR_BUTTON (textcolor), &b->textcolor);
3948 b->use_textcolor = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (use_textcolor));
3949 const char *l = gtk_entry_get_text (GTK_ENTRY (label));
3950 if (b->label) {
3951 free (b->label);
3952 b->label = NULL;
3953 }
3954 if (l[0]) {
3955 b->label = strdup (l);
3956 }
3957
3958 const char *ic = NULL;
3959 int sel = gtk_combo_box_get_active (GTK_COMBO_BOX (icon));
3960 if (sel >= 1) {
3961 ic = GtkNamedIcons[sel-1];
3962 }
3963 if (b->icon) {
3964 free (b->icon);
3965 b->icon = NULL;
3966 }
3967 if (ic) {
3968 b->icon = strdup (ic);
3969 }
3970
3971 w_button_init (user_data);
3972 }
3973 if (response == GTK_RESPONSE_APPLY) {
3974 continue;
3975 }
3976 break;
3977 }
3978 gtk_widget_destroy (dlg);
3979 }
3980
3981 static void
w_button_initmenu(struct ddb_gtkui_widget_s * w,GtkWidget * menu)3982 w_button_initmenu (struct ddb_gtkui_widget_s *w, GtkWidget *menu) {
3983 GtkWidget *item;
3984 item = gtk_menu_item_new_with_mnemonic (_("Configure button"));
3985 gtk_widget_show (item);
3986 gtk_container_add (GTK_CONTAINER (menu), item);
3987 g_signal_connect ((gpointer) item, "activate", G_CALLBACK (on_button_config), w);
3988 }
3989
3990
3991 ddb_gtkui_widget_t *
w_button_create(void)3992 w_button_create (void) {
3993 w_button_t *w = malloc (sizeof (w_button_t));
3994 memset (w, 0, sizeof (w_button_t));
3995 w->base.widget = gtk_event_box_new ();
3996 w->base.load = w_button_load;
3997 w->base.save = w_button_save;
3998 w->base.init = w_button_init;
3999 w->base.destroy = w_button_destroy;
4000 w->base.initmenu = w_button_initmenu;
4001 w_override_signals (w->base.widget, w);
4002 return (ddb_gtkui_widget_t *)w;
4003 }
4004
4005 // seekbar
4006 static gboolean
redraw_seekbar_cb(gpointer data)4007 redraw_seekbar_cb (gpointer data) {
4008 w_seekbar_t *w = data;
4009 int iconified = gdk_window_get_state(gtk_widget_get_window(mainwin)) & GDK_WINDOW_STATE_ICONIFIED;
4010 if (!gtk_widget_get_visible (mainwin) || iconified) {
4011 return FALSE;
4012 }
4013 gtk_widget_queue_draw (w->seekbar);
4014 return FALSE;
4015 }
4016
4017 static gboolean
seekbar_frameupdate(gpointer data)4018 seekbar_frameupdate (gpointer data) {
4019 w_seekbar_t *w = data;
4020 DB_output_t *output = deadbeef->get_output ();
4021 DB_playItem_t *track = deadbeef->streamer_get_playing_track ();
4022 DB_fileinfo_t *c = deadbeef->streamer_get_current_fileinfo (); // FIXME: might crash streamer
4023 float songpos = w->last_songpos;
4024 float duration = track ? deadbeef->pl_get_item_duration (track) : -1;
4025 if (!output || (output->state () == OUTPUT_STATE_STOPPED || !track || !c)) {
4026 songpos = 0;
4027 }
4028 else {
4029 songpos = deadbeef->streamer_get_playpos ();
4030 }
4031 // translate pos to seekbar pixels
4032 songpos /= duration;
4033 GtkAllocation a;
4034 gtk_widget_get_allocation (w->seekbar, &a);
4035 songpos *= a.width;
4036 if (fabs (songpos - w->last_songpos) > 0.01) {
4037 gtk_widget_queue_draw (w->seekbar);
4038 w->last_songpos = songpos;
4039 }
4040 if (track) {
4041 deadbeef->pl_item_unref (track);
4042 }
4043 return TRUE;
4044 }
4045
4046 static void
w_seekbar_init(ddb_gtkui_widget_t * base)4047 w_seekbar_init (ddb_gtkui_widget_t *base) {
4048 w_seekbar_t *w = (w_seekbar_t *)base;
4049 if (w->timer) {
4050 g_source_remove (w->timer);
4051 w->timer = 0;
4052 }
4053
4054 w->timer = g_timeout_add (1000/gtkui_get_gui_refresh_rate (), seekbar_frameupdate, w);
4055 }
4056
4057 static int
w_seekbar_message(ddb_gtkui_widget_t * w,uint32_t id,uintptr_t ctx,uint32_t p1,uint32_t p2)4058 w_seekbar_message (ddb_gtkui_widget_t *w, uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2) {
4059 switch (id) {
4060 case DB_EV_SONGCHANGED:
4061 g_idle_add (redraw_seekbar_cb, w);
4062 break;
4063 case DB_EV_CONFIGCHANGED:
4064 w_seekbar_init (w);
4065 break;
4066 }
4067 return 0;
4068 }
4069
4070 static void
w_seekbar_destroy(ddb_gtkui_widget_t * wbase)4071 w_seekbar_destroy (ddb_gtkui_widget_t *wbase) {
4072 w_seekbar_t *w = (w_seekbar_t *)wbase;
4073 if (w->timer) {
4074 g_source_remove (w->timer);
4075 w->timer = 0;
4076 }
4077 }
4078
4079 ddb_gtkui_widget_t *
w_seekbar_create(void)4080 w_seekbar_create (void) {
4081 w_seekbar_t *w = malloc (sizeof (w_seekbar_t));
4082 memset (w, 0, sizeof (w_seekbar_t));
4083 w->base.widget = gtk_event_box_new ();
4084 w->base.message = w_seekbar_message;
4085 w->base.destroy = w_seekbar_destroy;
4086 w->base.init = w_seekbar_init;
4087 w->seekbar = ddb_seekbar_new ();
4088 gtk_widget_set_size_request (w->base.widget, 20, 16);
4089 w->last_songpos = -1;
4090 ddb_seekbar_init_signals (DDB_SEEKBAR (w->seekbar), w->base.widget);
4091 gtk_widget_show (w->seekbar);
4092 gtk_container_add (GTK_CONTAINER (w->base.widget), w->seekbar);
4093 w_override_signals (w->base.widget, w);
4094 return (ddb_gtkui_widget_t*)w;
4095 }
4096
4097 // play toolbar
4098 ddb_gtkui_widget_t *
w_playtb_create(void)4099 w_playtb_create (void) {
4100 w_playtb_t *w = malloc (sizeof (w_playtb_t));
4101 memset (w, 0, sizeof (w_playtb_t));
4102 w->base.widget = gtk_hbox_new (FALSE, 0);
4103 w->base.flags = DDB_GTKUI_WIDGET_FLAG_NON_EXPANDABLE;
4104 gtk_widget_show (w->base.widget);
4105
4106 GtkWidget *stopbtn;
4107 GtkWidget *image128;
4108 GtkWidget *playbtn;
4109 GtkWidget *image2;
4110 GtkWidget *pausebtn;
4111 GtkWidget *image3;
4112 GtkWidget *prevbtn;
4113 GtkWidget *image4;
4114 GtkWidget *nextbtn;
4115 GtkWidget *image5;
4116
4117
4118 stopbtn = gtk_button_new ();
4119 gtk_widget_show (stopbtn);
4120 gtk_box_pack_start (GTK_BOX (w->base.widget), stopbtn, FALSE, FALSE, 0);
4121 gtk_widget_set_can_focus(stopbtn, FALSE);
4122 gtk_button_set_relief (GTK_BUTTON (stopbtn), GTK_RELIEF_NONE);
4123
4124 image128 = gtk_image_new_from_stock ("gtk-media-stop", GTK_ICON_SIZE_BUTTON);
4125 gtk_widget_show (image128);
4126 gtk_container_add (GTK_CONTAINER (stopbtn), image128);
4127
4128 playbtn = gtk_button_new ();
4129 gtk_widget_show (playbtn);
4130 gtk_box_pack_start (GTK_BOX (w->base.widget), playbtn, FALSE, FALSE, 0);
4131 gtk_widget_set_can_focus(playbtn, FALSE);
4132 gtk_button_set_relief (GTK_BUTTON (playbtn), GTK_RELIEF_NONE);
4133
4134 image2 = gtk_image_new_from_stock ("gtk-media-play", GTK_ICON_SIZE_BUTTON);
4135 gtk_widget_show (image2);
4136 gtk_container_add (GTK_CONTAINER (playbtn), image2);
4137
4138 pausebtn = gtk_button_new ();
4139 gtk_widget_show (pausebtn);
4140 gtk_box_pack_start (GTK_BOX (w->base.widget), pausebtn, FALSE, FALSE, 0);
4141 gtk_widget_set_can_focus(pausebtn, FALSE);
4142 gtk_button_set_relief (GTK_BUTTON (pausebtn), GTK_RELIEF_NONE);
4143
4144 image3 = gtk_image_new_from_stock ("gtk-media-pause", GTK_ICON_SIZE_BUTTON);
4145 gtk_widget_show (image3);
4146 gtk_container_add (GTK_CONTAINER (pausebtn), image3);
4147
4148 prevbtn = gtk_button_new ();
4149 gtk_widget_show (prevbtn);
4150 gtk_box_pack_start (GTK_BOX (w->base.widget), prevbtn, FALSE, FALSE, 0);
4151 gtk_widget_set_can_focus(prevbtn, FALSE);
4152 gtk_button_set_relief (GTK_BUTTON (prevbtn), GTK_RELIEF_NONE);
4153
4154 image4 = gtk_image_new_from_stock ("gtk-media-previous", GTK_ICON_SIZE_BUTTON);
4155 gtk_widget_show (image4);
4156 gtk_container_add (GTK_CONTAINER (prevbtn), image4);
4157
4158 nextbtn = gtk_button_new ();
4159 gtk_widget_show (nextbtn);
4160 gtk_box_pack_start (GTK_BOX (w->base.widget), nextbtn, FALSE, FALSE, 0);
4161 gtk_widget_set_can_focus(nextbtn, FALSE);
4162 gtk_button_set_relief (GTK_BUTTON (nextbtn), GTK_RELIEF_NONE);
4163
4164 image5 = gtk_image_new_from_stock ("gtk-media-next", GTK_ICON_SIZE_BUTTON);
4165 gtk_widget_show (image5);
4166 gtk_container_add (GTK_CONTAINER (nextbtn), image5);
4167 w_override_signals (w->base.widget, w);
4168
4169 g_signal_connect ((gpointer) stopbtn, "clicked",
4170 G_CALLBACK (on_stopbtn_clicked),
4171 NULL);
4172 g_signal_connect ((gpointer) playbtn, "clicked",
4173 G_CALLBACK (on_playbtn_clicked),
4174 NULL);
4175 g_signal_connect ((gpointer) pausebtn, "clicked",
4176 G_CALLBACK (on_pausebtn_clicked),
4177 NULL);
4178 g_signal_connect ((gpointer) prevbtn, "clicked",
4179 G_CALLBACK (on_prevbtn_clicked),
4180 NULL);
4181 g_signal_connect ((gpointer) nextbtn, "clicked",
4182 G_CALLBACK (on_nextbtn_clicked),
4183 NULL);
4184 return (ddb_gtkui_widget_t*)w;
4185 }
4186
4187 // volumebar
4188 static gboolean
redraw_volumebar_cb(gpointer data)4189 redraw_volumebar_cb (gpointer data) {
4190 w_volumebar_t *w = data;
4191 gtk_widget_queue_draw (w->volumebar);
4192 char s[100];
4193 int db = deadbeef->volume_get_db ();
4194 snprintf (s, sizeof (s), "%s%ddB", db < 0 ? "" : "+", db);
4195 gtk_widget_set_tooltip_text (w->volumebar, s);
4196 gtk_widget_trigger_tooltip_query (w->volumebar);
4197 return FALSE;
4198 }
4199
4200 static int
w_volumebar_message(ddb_gtkui_widget_t * w,uint32_t id,uintptr_t ctx,uint32_t p1,uint32_t p2)4201 w_volumebar_message (ddb_gtkui_widget_t *w, uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2) {
4202 switch (id) {
4203 case DB_EV_CONFIGCHANGED:
4204 case DB_EV_VOLUMECHANGED:
4205 g_idle_add (redraw_volumebar_cb, w);
4206 break;
4207 }
4208 return 0;
4209 }
4210
4211 ddb_gtkui_widget_t *
w_volumebar_create(void)4212 w_volumebar_create (void) {
4213 w_volumebar_t *w = malloc (sizeof (w_volumebar_t));
4214 memset (w, 0, sizeof (w_volumebar_t));
4215 w->base.widget = gtk_event_box_new ();
4216 w->base.message = w_volumebar_message;
4217 w->volumebar = ddb_volumebar_new ();
4218 #if GTK_CHECK_VERSION(3,0,0)
4219 gtk_widget_set_events (GTK_WIDGET (w->base.widget), gtk_widget_get_events (GTK_WIDGET (w->base.widget)) | GDK_SCROLL_MASK);
4220 #endif
4221 ddb_volumebar_init_signals (DDB_VOLUMEBAR (w->volumebar), w->base.widget);
4222 gtk_widget_show (w->volumebar);
4223 gtk_widget_set_size_request (w->base.widget, 70, -1);
4224 gtk_container_add (GTK_CONTAINER (w->base.widget), w->volumebar);
4225 w_override_signals (w->base.widget, w);
4226 return (ddb_gtkui_widget_t*)w;
4227 }
4228
4229 // chiptune voice ctl
4230 static void
on_voice_toggled(GtkToggleButton * togglebutton,gpointer user_data)4231 on_voice_toggled (GtkToggleButton *togglebutton, gpointer user_data) {
4232 w_ctvoices_t *w = user_data;
4233 int voices = 0;
4234 for (int i = 0; i < 8; i++) {
4235 int active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w->voices[i]));
4236 voices |= active << i;
4237 }
4238 deadbeef->conf_set_int ("chip.voices", voices);
4239 deadbeef->sendmessage (DB_EV_CONFIGCHANGED, 0, 0, 0);
4240 }
4241
4242 ddb_gtkui_widget_t *
w_ctvoices_create(void)4243 w_ctvoices_create (void) {
4244 w_ctvoices_t *w = malloc (sizeof (w_ctvoices_t));
4245 memset (w, 0, sizeof (w_ctvoices_t));
4246 w->base.widget = gtk_event_box_new ();
4247 GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
4248 gtk_widget_show (hbox);
4249 gtk_container_add (GTK_CONTAINER (w->base.widget), hbox);
4250
4251 GtkWidget *label = gtk_label_new_with_mnemonic (_("Voices:"));
4252 gtk_widget_show (label);
4253 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
4254
4255 int voices = deadbeef->conf_get_int ("chip.voices", 0xff);
4256 for (int i = 0; i < 8; i++) {
4257 w->voices[i] = gtk_check_button_new ();
4258 gtk_widget_show (w->voices[i]);
4259 gtk_box_pack_start (GTK_BOX (hbox), w->voices[i], FALSE, FALSE, 0);
4260 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w->voices[i]), voices & (1<<i));
4261 g_signal_connect ((gpointer) w->voices[i], "toggled", G_CALLBACK (on_voice_toggled), w);
4262 }
4263
4264 w_override_signals (w->base.widget, w);
4265 return (ddb_gtkui_widget_t*)w;
4266 }
4267