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