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 <unistd.h>
27 #include <gdk/gdkkeysyms.h>
28 #include "gtkui.h"
29 #include "plcommon.h"
30 #include "coverart.h"
31 #include "drawing.h"
32 #include "trkproperties.h"
33 #include "mainplaylist.h"
34 #include "support.h"
35 #include "interface.h"
36 #include "../libparser/parser.h"
37 #include "actions.h"
38 #include "actionhandlers.h"
39 #include "../../strdupa.h"
40 #include <jansson.h>
41 
42 #define min(x,y) ((x)<(y)?(x):(y))
43 //#define trace(...) { fprintf(stderr, __VA_ARGS__); }
44 #define trace(fmt,...)
45 
46 // disable custom title function, until we have new title formatting (0.7)
47 #define DISABLE_CUSTOM_TITLE
48 
49 // playlist theming
50 GtkWidget *theme_button;
51 GtkWidget *theme_treeview;
52 static GdkPixbuf *play16_pixbuf;
53 static GdkPixbuf *pause16_pixbuf;
54 static GdkPixbuf *buffering16_pixbuf;
55 
56 static int clicked_idx = -1;
57 
58 void
pl_common_init(void)59 pl_common_init(void)
60 {
61     play16_pixbuf = create_pixbuf("play_16.png");
62     pause16_pixbuf = create_pixbuf("pause_16.png");
63     buffering16_pixbuf = create_pixbuf("buffering_16.png");
64 
65     gtkui_groups_pinned = deadbeef->conf_get_int ("playlist.pin.groups", 0);
66 
67     theme_treeview = gtk_tree_view_new ();
68     gtk_widget_show (theme_treeview);
69     gtk_widget_set_can_focus (theme_treeview, FALSE);
70     GtkWidget *vbox1 = lookup_widget (mainwin, "vbox1");
71     gtk_box_pack_start (GTK_BOX (vbox1), theme_treeview, FALSE, FALSE, 0);
72     gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (theme_treeview), TRUE);
73 
74     theme_button = mainwin;//lookup_widget (mainwin, "stopbtn");
75 }
76 
77 void
pl_common_free(void)78 pl_common_free (void)
79 {
80     if (theme_treeview) {
81         gtk_widget_destroy (theme_treeview);
82         theme_treeview = NULL;
83     }
84 
85     g_object_unref(play16_pixbuf);
86     g_object_unref(pause16_pixbuf);
87     g_object_unref(buffering16_pixbuf);
88 }
89 
90 #define COL_CONF_BUFFER_SIZE 10000
91 
92 int
rewrite_column_config(DdbListview * listview,const char * name)93 rewrite_column_config (DdbListview *listview, const char *name) {
94     char *buffer = malloc (COL_CONF_BUFFER_SIZE);
95     strcpy (buffer, "[");
96     char *p = buffer+1;
97     int n = COL_CONF_BUFFER_SIZE-3;
98 
99     int cnt = ddb_listview_column_get_count (listview);
100     for (int i = 0; i < cnt; i++) {
101         const char *title;
102         int width;
103         int align;
104         col_info_t *info;
105         int minheight;
106         int color_override;
107         GdkColor color;
108         ddb_listview_column_get_info (listview, i, &title, &width, &align, &minheight, &color_override, &color, (void **)&info);
109 
110         char *esctitle = parser_escape_string (title);
111         char *escformat = info->format ? parser_escape_string (info->format) : NULL;
112 
113         size_t written = snprintf (p, n, "{\"title\":\"%s\",\"id\":\"%d\",\"format\":\"%s\",\"size\":\"%d\",\"align\":\"%d\",\"color_override\":\"%d\",\"color\":\"#ff%02x%02x%02x\"}%s", esctitle, info->id, escformat ? escformat : "", width, align, color_override, color.red>>8, color.green>>8, color.blue>>8, i < cnt-1 ? "," : "");
114         free (esctitle);
115         if (escformat) {
116             free (escformat);
117         }
118         p += written;
119         n -= written;
120         if (n <= 0) {
121             fprintf (stderr, "Column configuration is too large, doesn't fit in the buffer. Won't be written.\n");
122             return -1;
123         }
124     }
125     strcpy (p, "]");
126     deadbeef->conf_set_str (name, buffer);
127     deadbeef->conf_save ();
128     return 0;
129 }
130 
131 static gboolean
redraw_playlist_cb(gpointer user_data)132 redraw_playlist_cb (gpointer user_data) {
133     DDB_LISTVIEW(user_data)->cover_size = DDB_LISTVIEW(user_data)->new_cover_size;
134     gtk_widget_queue_draw (GTK_WIDGET(user_data));
135     return FALSE;
136 }
137 
138 static gboolean
tf_redraw_cb(gpointer user_data)139 tf_redraw_cb (gpointer user_data) {
140     DdbListview *lv = user_data;
141 
142     ddb_listview_draw_row (lv, lv->tf_redraw_track_idx, lv->tf_redraw_track);
143     lv->tf_redraw_track_idx = -1;
144     if (lv->tf_redraw_track) {
145         lv->binding->unref (lv->tf_redraw_track);
146         lv->tf_redraw_track = NULL;
147     }
148     DDB_LISTVIEW(user_data)->tf_redraw_timeout_id = 0;
149     return FALSE;
150 }
151 
152 static void
redraw_playlist(void * user_data)153 redraw_playlist (void *user_data) {
154     g_idle_add (redraw_playlist_cb, user_data);
155 }
156 
157 static gboolean
redraw_playlist_single_cb(gpointer user_data)158 redraw_playlist_single_cb (gpointer user_data) {
159     gtk_widget_queue_draw (GTK_WIDGET(user_data));
160     g_object_unref (GTK_WIDGET (user_data));
161     return FALSE;
162 }
163 
164 static void
redraw_playlist_single(void * user_data)165 redraw_playlist_single (void *user_data) {
166     g_object_ref (GTK_WIDGET (user_data));
167     g_idle_add (redraw_playlist_single_cb, user_data);
168 }
169 
170 #define ART_PADDING_HORZ 8
171 #define ART_PADDING_VERT 0
172 
173 static gboolean
deferred_cover_load_cb(void * ctx)174 deferred_cover_load_cb (void *ctx) {
175     DdbListview *lv = ctx;
176     lv->cover_refresh_timeout_id = 0;
177     deadbeef->pl_lock ();
178     ddb_listview_groupcheck (lv);
179     // find 1st group
180     DdbListviewGroup *grp = lv->groups;
181     int idx = 0;
182     int grp_y = 0;
183     while (grp && grp_y + grp->height < lv->scrollpos) {
184         grp_y += grp->height;
185         idx += grp->num_items + 1;
186         grp = grp->next;
187     }
188     GtkAllocation a;
189     gtk_widget_get_allocation (GTK_WIDGET (lv), &a);
190     while (grp && grp_y < a.height + lv->scrollpos) {
191         // render title
192         DdbListviewIter group_it = grp->head;
193         int grpheight = grp->height;
194         if (grp_y >= a.height + lv->scrollpos) {
195             break;
196         }
197         const char *album = deadbeef->pl_find_meta (group_it, "album");
198         const char *artist = deadbeef->pl_find_meta (group_it, "artist");
199         if (!album || !*album) {
200             album = deadbeef->pl_find_meta (group_it, "title");
201         }
202 
203         grp_y += grpheight;
204         grp = grp->next;
205         int last = 0;
206         if (!grp || grp_y >= a.height + lv->scrollpos) {
207             last = 1;
208         }
209         GdkPixbuf *pixbuf = get_cover_art_thumb (deadbeef->pl_find_meta (((DB_playItem_t *)group_it), ":URI"), artist, album, lv->new_cover_size, NULL, NULL);
210         if (last) {
211             queue_cover_callback (redraw_playlist, lv);
212         }
213         if (pixbuf) {
214             g_object_unref (pixbuf);
215         }
216     }
217     deadbeef->pl_unlock ();
218 
219     return FALSE;
220 }
221 
222 void
draw_album_art(DdbListview * listview,cairo_t * cr,DdbListviewIter group_it,int column,int group_pinned,int grp_next_y,int x,int y,int width,int height)223 draw_album_art (DdbListview *listview, cairo_t *cr, DdbListviewIter group_it, int column, int group_pinned, int grp_next_y, int x, int y, int width, int height) {
224     const char *ctitle;
225     int cwidth;
226     int calign_right;
227     col_info_t *cinf;
228     int minheight;
229     int color_override;
230     GdkColor fg_clr;
231     int res = ddb_listview_column_get_info (listview, column, &ctitle, &cwidth, &calign_right, &minheight, &color_override, &fg_clr, (void **)&cinf);
232     if (res == -1) {
233         return;
234     }
235 
236     DB_playItem_t *playing_track = deadbeef->streamer_get_playing_track ();
237     int theming = !gtkui_override_listview_colors ();
238 
239     if (cinf->id == DB_COLUMN_ALBUM_ART) {
240         if (theming) {
241 #if GTK_CHECK_VERSION(3,0,0)
242             cairo_save (cr);
243             cairo_rectangle (cr, x, y, width, MAX (height,minheight));
244             cairo_clip (cr);
245             gtk_paint_flat_box (gtk_widget_get_style (theme_treeview), cr, GTK_STATE_NORMAL, GTK_SHADOW_NONE, theme_treeview, "cell_even_ruled", x-1, y, width+2, MAX (height,minheight));
246             cairo_restore (cr);
247 #else
248             GdkRectangle clip = {
249                 .x = x,
250                 .y = y,
251                 .width = width,
252                 .height = MAX (height,minheight),
253             };
254             gtk_paint_flat_box (gtk_widget_get_style (theme_treeview), gtk_widget_get_window (listview->list), GTK_STATE_NORMAL, GTK_SHADOW_NONE, &clip, theme_treeview, "cell_even_ruled", x-1, y, width+2, height);
255 #endif
256         }
257         else {
258             GdkColor clr;
259             gtkui_get_listview_even_row_color (&clr);
260             cairo_set_source_rgb (cr, clr.red/65535.f, clr.green/65535.f, clr.blue/65535.f);
261             cairo_rectangle (cr, x, y, width, height);
262             cairo_fill (cr);
263         }
264         int real_art_width = width - ART_PADDING_HORZ * 2;
265         if (real_art_width > 7 && group_it) {
266             const char *album = deadbeef->pl_find_meta (group_it, "album");
267             const char *artist = deadbeef->pl_find_meta (group_it, "artist");
268             if (!album || !*album) {
269                 album = deadbeef->pl_find_meta (group_it, "title");
270             }
271             if (listview->new_cover_size != real_art_width) {
272                 listview->new_cover_size = real_art_width;
273                 if (listview->cover_refresh_timeout_id) {
274                     g_source_remove (listview->cover_refresh_timeout_id);
275                     listview->cover_refresh_timeout_id = 0;
276                 }
277                 if (listview->cover_size == -1) {
278                     listview->cover_size = real_art_width;
279                 }
280                 else {
281                     if (!listview->cover_refresh_timeout_id) {
282                         listview->cover_refresh_timeout_id = g_timeout_add (1000, deferred_cover_load_cb, listview);
283                     }
284                 }
285             }
286 
287             // destination coordinates and sizes
288             int art_x = x + ART_PADDING_HORZ;
289             int art_y = y + ART_PADDING_VERT;
290             int art_w = listview->cover_size;
291             int art_h = height;
292 
293             GdkPixbuf *pixbuf = get_cover_art_thumb (deadbeef->pl_find_meta (((DB_playItem_t *)group_it), ":URI"), artist, album, real_art_width == art_w ? art_w : -1, redraw_playlist_single, listview);
294             if (pixbuf) {
295                 art_w = gdk_pixbuf_get_width (pixbuf);
296                 art_h = gdk_pixbuf_get_height (pixbuf);
297 
298                 int draw_pinned = (y - listview->grouptitle_height < real_art_width && group_pinned == 1 && gtkui_groups_pinned) ? 1 : 0;
299 
300                 if (art_y > -(real_art_width + listview->grouptitle_height) || draw_pinned) {
301                     float art_scale = real_art_width;
302                     if (art_w < art_h) {
303                         art_scale /= (float)art_h;
304                     }
305                     else {
306                         art_scale /= (float)art_w;
307                     }
308 
309                     art_w *= art_scale;
310                     art_h *= art_scale;
311 
312                     cairo_save (cr);
313                     if (draw_pinned) {
314                         if (grp_next_y <= art_h + listview->grouptitle_height + ART_PADDING_VERT) {
315                             cairo_translate (cr, art_x, grp_next_y - art_h);
316                         }
317                         else {
318                             cairo_translate (cr, art_x, listview->grouptitle_height + ART_PADDING_VERT);
319                         }
320                     }
321                     else {
322                         cairo_translate (cr, art_x, art_y);
323                     }
324                     cairo_rectangle (cr, 0, 0, art_w, art_h);
325                     cairo_scale (cr, art_scale, art_scale);
326                     gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
327                     cairo_pattern_set_filter (cairo_get_source(cr), gtkui_is_default_pixbuf (pixbuf) ? CAIRO_FILTER_BEST : CAIRO_FILTER_FAST);
328                     cairo_fill (cr);
329                     cairo_restore (cr);
330                 }
331                 g_object_unref (pixbuf);
332             }
333         }
334     }
335     if (playing_track) {
336         deadbeef->pl_item_unref (playing_track);
337     }
338 }
339 
340 void
draw_column_data(DdbListview * listview,cairo_t * cr,DdbListviewIter it,int idx,int column,int iter,int x,int y,int width,int height)341 draw_column_data (DdbListview *listview, cairo_t *cr, DdbListviewIter it, int idx, int column, int iter, int x, int y, int width, int height) {
342     const char *ctitle;
343     int cwidth;
344     int calign_right;
345     col_info_t *cinf;
346     int minheight;
347     int color_override;
348     GdkColor fg_clr;
349     int res = ddb_listview_column_get_info (listview, column, &ctitle, &cwidth, &calign_right, &minheight, &color_override, &fg_clr, (void **)&cinf);
350     if (res == -1) {
351         return;
352     }
353 
354     DB_playItem_t *playing_track = deadbeef->streamer_get_playing_track ();
355     int theming = !gtkui_override_listview_colors ();
356 
357     if (!gtkui_unicode_playstate && it && it == playing_track && cinf->id == DB_COLUMN_PLAYING) {
358         int paused = deadbeef->get_output ()->state () == OUTPUT_STATE_PAUSED;
359         int buffering = !deadbeef->streamer_ok_to_read (-1);
360         GdkPixbuf *pixbuf;
361         if (paused) {
362             pixbuf = pause16_pixbuf;
363         }
364         else if (!buffering) {
365             pixbuf = play16_pixbuf;
366         }
367         else {
368             pixbuf = buffering16_pixbuf;
369         }
370         gdk_cairo_set_source_pixbuf (cr, pixbuf, x + cwidth/2 - 8, y + height/2 - 8);
371         cairo_rectangle (cr, x + cwidth/2 - 8, y + height/2 - 8, 16, 16);
372         cairo_fill (cr);
373     }
374     else if (it) {
375         char text[1024] = "";
376         if (it == playing_track && cinf->id == DB_COLUMN_PLAYING) {
377             int paused = deadbeef->get_output ()->state () == OUTPUT_STATE_PAUSED;
378             int buffering = !deadbeef->streamer_ok_to_read (-1);
379             if (paused) {
380                 strcpy (text, "||");
381             }
382             else if (!buffering) {
383                 strcpy (text, "►");
384             }
385             else {
386                 strcpy (text, "⋯");
387             }
388         }
389         else {
390             ddb_tf_context_t ctx = {
391                 ._size = sizeof (ddb_tf_context_t),
392                 .it = it,
393                 .plt = deadbeef->plt_get_curr (),
394                 .iter = iter,
395                 .id = cinf->id,
396                 .idx = idx,
397                 .flags = DDB_TF_CONTEXT_HAS_ID | DDB_TF_CONTEXT_HAS_INDEX,
398             };
399             deadbeef->tf_eval (&ctx, cinf->bytecode, text, sizeof (text));
400             if (ctx.update > 0) {
401                 ddb_listview_cancel_autoredraw (listview);
402                 if ((ctx.flags & DDB_TF_CONTEXT_HAS_INDEX) && ctx.iter == PL_MAIN) {
403                     listview->tf_redraw_track_idx = ctx.idx;
404                 }
405                 else {
406                     listview->tf_redraw_track_idx = deadbeef->plt_get_item_idx (ctx.plt, it, ctx.iter);
407                 }
408                 listview->tf_redraw_timeout_id = g_timeout_add (ctx.update, tf_redraw_cb, listview);
409                 listview->tf_redraw_track = it;
410                 deadbeef->pl_item_ref (it);
411             }
412             if (ctx.plt) {
413                 deadbeef->plt_unref (ctx.plt);
414                 ctx.plt = NULL;
415             }
416             char *lb = strchr (text, '\r');
417             if (lb) {
418                 *lb = 0;
419             }
420             lb = strchr (text, '\n');
421             if (lb) {
422                 *lb = 0;
423             }
424         }
425         GdkColor *color = NULL;
426         if (theming) {
427             if (deadbeef->pl_is_selected (it)) {
428                 color = &gtk_widget_get_style (theme_treeview)->text[GTK_STATE_SELECTED];
429             }
430             else {
431                 if (color_override) {
432                     color = &fg_clr;
433                 }
434                 else {
435                     color = &gtk_widget_get_style (theme_treeview)->text[GTK_STATE_NORMAL];
436                 }
437             }
438         }
439         else {
440             GdkColor clr;
441             if (deadbeef->pl_is_selected (it)) {
442                 color = (gtkui_get_listview_selected_text_color (&clr), &clr);
443             }
444             else if (it && it == playing_track) {
445                 if (color_override) {
446                     color = &fg_clr;
447                 }
448                 else {
449                     color = (gtkui_get_listview_playing_text_color (&clr), &clr);
450                 }
451             }
452             else {
453                 if (color_override) {
454                     color = &fg_clr;
455                 }
456                 else {
457                     color = (gtkui_get_listview_text_color (&clr), &clr);
458                 }
459             }
460         }
461         float fg[3] = {(float)color->red/0xffff, (float)color->green/0xffff, (float)color->blue/0xffff};
462         draw_set_fg_color (&listview->listctx, fg);
463 
464         draw_init_font (&listview->listctx, DDB_LIST_FONT, 0);
465         int bold = 0;
466         int italic = 0;
467         if (deadbeef->pl_is_selected (it)) {
468             bold = gtkui_embolden_selected_tracks;
469             italic = gtkui_italic_selected_tracks;
470         }
471         if (it == playing_track) {
472             bold = gtkui_embolden_current_track;
473             italic = gtkui_italic_current_track;
474         }
475         draw_text_custom (&listview->listctx, x + 5, y + 3, cwidth-10, calign_right, DDB_LIST_FONT, bold, italic, text);
476     }
477     if (playing_track) {
478         deadbeef->pl_item_unref (playing_track);
479     }
480 }
481 
482 static void
main_add_to_playback_queue_activate(GtkMenuItem * menuitem,gpointer user_data)483 main_add_to_playback_queue_activate     (GtkMenuItem     *menuitem,
484                                         gpointer         user_data)
485 {
486     DB_playItem_t *it = deadbeef->pl_get_first (PL_MAIN);
487     while (it) {
488         if (deadbeef->pl_is_selected (it)) {
489             deadbeef->playqueue_push (it);
490         }
491         DB_playItem_t *next = deadbeef->pl_get_next (it, PL_MAIN);
492         deadbeef->pl_item_unref (it);
493         it = next;
494     }
495     deadbeef->sendmessage (DB_EV_PLAYLIST_REFRESH, 0, 0, 0);
496 }
497 
498 void
main_remove_from_playback_queue_activate(GtkMenuItem * menuitem,gpointer user_data)499 main_remove_from_playback_queue_activate
500                                         (GtkMenuItem     *menuitem,
501                                         gpointer         user_data)
502 {
503     DB_playItem_t *it = deadbeef->pl_get_first (PL_MAIN);
504     while (it) {
505         if (deadbeef->pl_is_selected (it)) {
506             deadbeef->playqueue_remove (it);
507         }
508         DB_playItem_t *next = deadbeef->pl_get_next (it, PL_MAIN);
509         deadbeef->pl_item_unref (it);
510         it = next;
511     }
512     deadbeef->sendmessage (DB_EV_PLAYLIST_REFRESH, 0, 0, 0);
513 }
514 
515 void
main_reload_metadata_activate(GtkMenuItem * menuitem,gpointer user_data)516 main_reload_metadata_activate
517                                         (GtkMenuItem     *menuitem,
518                                         gpointer         user_data)
519 {
520     DB_playItem_t *it = deadbeef->pl_get_first (PL_MAIN);
521     while (it) {
522         deadbeef->pl_lock ();
523         char decoder_id[100];
524         const char *dec = deadbeef->pl_find_meta (it, ":DECODER");
525         if (dec) {
526             strncpy (decoder_id, dec, sizeof (decoder_id));
527         }
528         int match = deadbeef->pl_is_selected (it) && deadbeef->is_local_file (deadbeef->pl_find_meta (it, ":URI")) && dec;
529         deadbeef->pl_unlock ();
530 
531         if (match) {
532             uint32_t f = deadbeef->pl_get_item_flags (it);
533             if (!(f & DDB_IS_SUBTRACK)) {
534                 f &= ~DDB_TAG_MASK;
535                 deadbeef->pl_set_item_flags (it, f);
536                 DB_decoder_t **decoders = deadbeef->plug_get_decoder_list ();
537                 for (int i = 0; decoders[i]; i++) {
538                     if (!strcmp (decoders[i]->plugin.id, decoder_id)) {
539                         if (decoders[i]->read_metadata) {
540                             decoders[i]->read_metadata (it);
541                         }
542                         break;
543                     }
544                 }
545             }
546         }
547         DB_playItem_t *next = deadbeef->pl_get_next (it, PL_MAIN);
548         deadbeef->pl_item_unref (it);
549         it = next;
550     }
551     deadbeef->sendmessage (DB_EV_PLAYLIST_REFRESH, 0, 0, 0);
552 }
553 
554 void
main_properties_activate(GtkMenuItem * menuitem,gpointer user_data)555 main_properties_activate                (GtkMenuItem     *menuitem,
556                                         gpointer         user_data)
557 {
558     action_show_track_properties_handler (NULL, DDB_ACTION_CTX_SELECTION);
559 }
560 
561 void
on_clear1_activate(GtkMenuItem * menuitem,gpointer user_data)562 on_clear1_activate                     (GtkMenuItem     *menuitem,
563                                         gpointer         user_data)
564 {
565     deadbeef->pl_clear ();
566     deadbeef->pl_save_current ();
567     deadbeef->sendmessage (DB_EV_PLAYLISTCHANGED, 0, DDB_PLAYLIST_CHANGE_CONTENT, 0);
568 }
569 
570 void
on_remove1_activate(GtkMenuItem * menuitem,gpointer user_data)571 on_remove1_activate                    (GtkMenuItem     *menuitem,
572                                         gpointer         user_data)
573 {
574     action_remove_from_playlist_handler (NULL, DDB_ACTION_CTX_SELECTION);
575 }
576 
577 
578 void
on_crop1_activate(GtkMenuItem * menuitem,gpointer user_data)579 on_crop1_activate                      (GtkMenuItem     *menuitem,
580                                         gpointer         user_data)
581 {
582     action_crop_selected_handler (NULL, 0);
583 }
584 
585 void
on_remove2_activate(GtkMenuItem * menuitem,gpointer user_data)586 on_remove2_activate                    (GtkMenuItem     *menuitem,
587                                         gpointer         user_data)
588 {
589     int cursor = deadbeef->pl_delete_selected ();
590     deadbeef->pl_save_current ();
591     deadbeef->sendmessage (DB_EV_PLAYLISTCHANGED, 0, DDB_PLAYLIST_CHANGE_CONTENT, 0);
592 }
593 
594 static void
on_toggle_set_custom_title(GtkToggleButton * togglebutton,gpointer user_data)595 on_toggle_set_custom_title (GtkToggleButton *togglebutton, gpointer user_data) {
596     gboolean active = gtk_toggle_button_get_active (togglebutton);
597     deadbeef->conf_set_int ("gtkui.location_set_custom_title", active);
598 
599     GtkWidget *ct = lookup_widget (GTK_WIDGET (user_data), "custom_title");
600     gtk_widget_set_sensitive (ct, active);
601 
602     deadbeef->conf_save ();
603 }
604 
605 #ifndef DISABLE_CUSTOM_TITLE
606 void
on_set_custom_title_activate(GtkMenuItem * menuitem,gpointer user_data)607 on_set_custom_title_activate (GtkMenuItem *menuitem, gpointer user_data)
608 {
609     DdbListview *lv = user_data;
610     int idx = lv->binding->cursor ();
611     if (idx < 0) {
612         return;
613     }
614     DdbListviewIter it = lv->binding->get_for_idx (idx);
615     if (!it) {
616         return;
617     }
618 
619     GtkWidget *dlg = create_setcustomtitledlg ();
620     GtkWidget *sct = lookup_widget (dlg, "set_custom_title");
621     GtkWidget *ct = lookup_widget (dlg, "custom_title");
622     if (deadbeef->conf_get_int ("gtkui.location_set_custom_title", 0)) {
623         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sct), TRUE);
624         gtk_widget_set_sensitive (ct, TRUE);
625     }
626     else {
627         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sct), FALSE);
628         gtk_widget_set_sensitive (ct, FALSE);
629     }
630     deadbeef->pl_lock ();
631     const char *custom_title = deadbeef->pl_find_meta ((DB_playItem_t *)it, ":CUSTOM_TITLE");
632     if (custom_title) {
633         custom_title = strdupa (custom_title);
634     }
635     else {
636         custom_title = "";
637     }
638     deadbeef->pl_unlock ();
639 
640     g_signal_connect ((gpointer) sct, "toggled",
641             G_CALLBACK (on_toggle_set_custom_title),
642             dlg);
643     gtk_entry_set_text (GTK_ENTRY (ct), custom_title);
644 
645     gtk_dialog_set_default_response (GTK_DIALOG (dlg), GTK_RESPONSE_OK);
646     gint response = gtk_dialog_run (GTK_DIALOG (dlg));
647     if (response == GTK_RESPONSE_OK) {
648         if (it && deadbeef->conf_get_int ("gtkui.location_set_custom_title", 0)) {
649             deadbeef->pl_replace_meta ((DB_playItem_t *)it, ":CUSTOM_TITLE", gtk_entry_get_text (GTK_ENTRY (ct)));
650         }
651         else {
652             deadbeef->pl_delete_meta ((DB_playItem_t *)it, ":CUSTOM_TITLE");
653         }
654     }
655     gtk_widget_destroy (dlg);
656     lv->binding->unref (it);
657 }
658 #endif
659 
660 void
on_remove_from_disk_activate(GtkMenuItem * menuitem,gpointer user_data)661 on_remove_from_disk_activate                    (GtkMenuItem     *menuitem,
662                                         gpointer         user_data)
663 {
664     action_delete_from_disk_handler_cb ((void *)(intptr_t)DDB_ACTION_CTX_SELECTION);
665 }
666 
667 void
actionitem_activate(GtkMenuItem * menuitem,DB_plugin_action_t * action)668 actionitem_activate (GtkMenuItem     *menuitem,
669                      DB_plugin_action_t *action)
670 {
671     if (action->callback) {
672         gtkui_exec_action_14 (action, clicked_idx);
673     }
674     else {
675         action->callback2 (action, DDB_ACTION_CTX_SELECTION);
676     }
677 }
678 
679 #define HOOKUP_OBJECT(component,widget,name) \
680   g_object_set_data_full (G_OBJECT (component), name, \
681     g_object_ref (widget), (GDestroyNotify) g_object_unref)
682 
683 
684 static GtkWidget*
find_popup(GtkWidget * widget,const gchar * widget_name)685 find_popup                          (GtkWidget       *widget,
686                                         const gchar     *widget_name)
687 {
688   GtkWidget *parent, *found_widget;
689 
690   for (;;)
691     {
692       if (GTK_IS_MENU (widget))
693         parent = gtk_menu_get_attach_widget (GTK_MENU (widget));
694       else
695         parent = gtk_widget_get_parent (widget);
696       if (!parent)
697         parent = (GtkWidget*) g_object_get_data (G_OBJECT (widget), "GladeParentKey");
698       if (parent == NULL)
699         break;
700       widget = parent;
701     }
702 
703   found_widget = (GtkWidget*) g_object_get_data (G_OBJECT (widget),
704                                                  widget_name);
705   return found_widget;
706 }
707 
708 #if 0
709 // experimental code to position the popup at the item
710 static void
711 popup_menu_position_func (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data) {
712     // find 1st selected item
713     DdbListview *lv = user_data;
714     int winx, winy;
715     gdk_window_get_position (gtk_widget_get_window (GTK_WIDGET (lv->list)), &winx, &winy);
716     DdbListviewIter it = lv->binding->head ();
717     int idx = 0;
718     while (it) {
719         if (lv->binding->is_selected (it)) {
720             break;
721         }
722         DdbListviewIter next = lv->binding->next (it);
723         lv->binding->unref (it);
724         it = next;
725         idx++;
726     }
727     if (it) {
728         // get Y position
729         *y = ddb_listview_get_row_pos (lv, idx) + winy;
730         lv->binding->unref (it);
731     }
732     else {
733         *y = winy; // mouse_y
734     }
735     *x = winx; // mouse_x
736     *push_in = TRUE;
737 }
738 #endif
739 
740 void
list_context_menu(DdbListview * listview,DdbListviewIter it,int idx)741 list_context_menu (DdbListview *listview, DdbListviewIter it, int idx) {
742     clicked_idx = deadbeef->pl_get_idx_of (it);
743     int inqueue = deadbeef->playqueue_test (it);
744     GtkWidget *playlist_menu;
745     GtkWidget *add_to_playback_queue1;
746     GtkWidget *remove_from_playback_queue1;
747     GtkWidget *separator;
748     GtkWidget *remove2;
749     GtkWidget *remove_from_disk;
750     GtkWidget *separator8;
751     GtkWidget *properties1;
752     GtkWidget *reload_metadata;
753 #ifndef DISABLE_CUSTOM_TITLE
754     GtkWidget *set_custom_title;
755 #endif
756 
757     playlist_menu = gtk_menu_new ();
758     add_to_playback_queue1 = gtk_menu_item_new_with_mnemonic (_("Add To Playback Queue"));
759     gtk_widget_show (add_to_playback_queue1);
760     gtk_container_add (GTK_CONTAINER (playlist_menu), add_to_playback_queue1);
761     g_object_set_data (G_OBJECT (add_to_playback_queue1), "ps", listview);
762 
763     remove_from_playback_queue1 = gtk_menu_item_new_with_mnemonic (_("Remove From Playback Queue"));
764     if (inqueue == -1) {
765         gtk_widget_set_sensitive (remove_from_playback_queue1, FALSE);
766     }
767     gtk_widget_show (remove_from_playback_queue1);
768     gtk_container_add (GTK_CONTAINER (playlist_menu), remove_from_playback_queue1);
769     g_object_set_data (G_OBJECT (remove_from_playback_queue1), "ps", listview);
770 
771     reload_metadata = gtk_menu_item_new_with_mnemonic (_("Reload Metadata"));
772     gtk_widget_show (reload_metadata);
773     gtk_container_add (GTK_CONTAINER (playlist_menu), reload_metadata);
774     g_object_set_data (G_OBJECT (reload_metadata), "ps", listview);
775 
776     separator = gtk_separator_menu_item_new ();
777     gtk_widget_show (separator);
778     gtk_container_add (GTK_CONTAINER (playlist_menu), separator);
779     gtk_widget_set_sensitive (separator, FALSE);
780 
781     remove2 = gtk_menu_item_new_with_mnemonic (_("Remove"));
782     gtk_widget_show (remove2);
783     gtk_container_add (GTK_CONTAINER (playlist_menu), remove2);
784     g_object_set_data (G_OBJECT (remove2), "ps", listview);
785 
786     int hide_remove_from_disk = deadbeef->conf_get_int ("gtkui.hide_remove_from_disk", 0);
787 
788     if (!hide_remove_from_disk) {
789         remove_from_disk = gtk_menu_item_new_with_mnemonic (_("Remove From Disk"));
790         gtk_widget_show (remove_from_disk);
791         gtk_container_add (GTK_CONTAINER (playlist_menu), remove_from_disk);
792         g_object_set_data (G_OBJECT (remove_from_disk), "ps", listview);
793     }
794 
795     separator = gtk_separator_menu_item_new ();
796     gtk_widget_show (separator);
797     gtk_container_add (GTK_CONTAINER (playlist_menu), separator);
798     gtk_widget_set_sensitive (separator, FALSE);
799 
800     int selected_count = 0;
801     DB_playItem_t *pit = deadbeef->pl_get_first (PL_MAIN);
802     DB_playItem_t *selected = NULL;
803     while (pit) {
804         if (deadbeef->pl_is_selected (pit))
805         {
806             if (!selected)
807                 selected = pit;
808             selected_count++;
809         }
810         DB_playItem_t *next = deadbeef->pl_get_next (pit, PL_MAIN);
811         deadbeef->pl_item_unref (pit);
812         pit = next;
813     }
814 
815     DB_plugin_t **plugins = deadbeef->plug_get_list();
816     int i;
817     int added_entries = 0;
818     for (i = 0; plugins[i]; i++)
819     {
820         if (!plugins[i]->get_actions)
821             continue;
822 
823         DB_plugin_action_t *actions = plugins[i]->get_actions (selected);
824         DB_plugin_action_t *action;
825 
826         int count = 0;
827         for (action = actions; action; action = action->next)
828         {
829             if ((action->flags & DB_ACTION_COMMON) || !((action->callback2 && (action->flags & DB_ACTION_ADD_MENU)) || action->callback) || !(action->flags & (DB_ACTION_MULTIPLE_TRACKS | DB_ACTION_SINGLE_TRACK)))
830                 continue;
831 
832             // create submenus (separated with '/')
833             const char *prev = action->title;
834             while (*prev && *prev == '/') {
835                 prev++;
836             }
837 
838             GtkWidget *popup = NULL;
839 
840             for (;;) {
841                 const char *slash = strchr (prev, '/');
842                 if (slash && *(slash-1) != '\\') {
843                     char name[slash-prev+1];
844                     // replace \/ with /
845                     const char *p = prev;
846                     char *t = name;
847                     while (*p && p < slash) {
848                         if (*p == '\\' && *(p+1) == '/') {
849                             *t++ = '/';
850                             p += 2;
851                         }
852                         else {
853                             *t++ = *p++;
854                         }
855                     }
856                     *t = 0;
857 
858                     // add popup
859                     GtkWidget *prev_menu = popup ? popup : playlist_menu;
860 
861                     popup = find_popup (prev_menu, name);
862                     if (!popup) {
863                         GtkWidget *item = gtk_image_menu_item_new_with_mnemonic (_(name));
864                         gtk_widget_show (item);
865                         gtk_container_add (GTK_CONTAINER (prev_menu), item);
866                         popup = gtk_menu_new ();
867                         HOOKUP_OBJECT (prev_menu, popup, name);
868                         gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), popup);
869                     }
870                 }
871                 else {
872                     break;
873                 }
874                 prev = slash+1;
875             }
876 
877 
878             count++;
879             added_entries++;
880             GtkWidget *actionitem;
881 
882             // replace \/ with /
883             const char *p = popup ? prev : action->title;
884             char title[strlen (p)+1];
885             char *t = title;
886             while (*p) {
887                 if (*p == '\\' && *(p+1) == '/') {
888                     *t++ = '/';
889                     p += 2;
890                 }
891                 else {
892                     *t++ = *p++;
893                 }
894             }
895             *t = 0;
896 
897             actionitem = gtk_menu_item_new_with_mnemonic (_(title));
898             gtk_widget_show (actionitem);
899             gtk_container_add (popup ? GTK_CONTAINER (popup) : GTK_CONTAINER (playlist_menu), actionitem);
900             g_object_set_data (G_OBJECT (actionitem), "ps", listview);
901 
902             g_signal_connect ((gpointer) actionitem, "activate",
903                     G_CALLBACK (actionitem_activate),
904                     action);
905             if ((selected_count > 1 && !(action->flags & DB_ACTION_MULTIPLE_TRACKS)) ||
906                 (action->flags & DB_ACTION_DISABLED)) {
907                 gtk_widget_set_sensitive (GTK_WIDGET (actionitem), FALSE);
908             }
909         }
910         if (count > 0 && deadbeef->conf_get_int ("gtkui.action_separators", 0))
911         {
912             separator8 = gtk_separator_menu_item_new ();
913             gtk_widget_show (separator8);
914             gtk_container_add (GTK_CONTAINER (playlist_menu), separator8);
915             gtk_widget_set_sensitive (separator8, FALSE);
916         }
917     }
918     if (added_entries > 0 && !deadbeef->conf_get_int ("gtkui.action_separators", 0))
919     {
920         separator8 = gtk_separator_menu_item_new ();
921         gtk_widget_show (separator8);
922         gtk_container_add (GTK_CONTAINER (playlist_menu), separator8);
923         gtk_widget_set_sensitive (separator8, FALSE);
924     }
925 
926 #ifndef DISABLE_CUSTOM_TITLE
927     set_custom_title = gtk_menu_item_new_with_mnemonic (_("Set Custom Title"));
928     gtk_widget_show (set_custom_title);
929     gtk_container_add (GTK_CONTAINER (playlist_menu), set_custom_title);
930     if (selected_count != 1) {
931         gtk_widget_set_sensitive (GTK_WIDGET (set_custom_title), FALSE);
932     }
933 
934     separator = gtk_separator_menu_item_new ();
935     gtk_widget_show (separator);
936     gtk_container_add (GTK_CONTAINER (playlist_menu), separator);
937     gtk_widget_set_sensitive (separator, FALSE);
938 #endif
939 
940     properties1 = gtk_menu_item_new_with_mnemonic (_("Track Properties"));
941     gtk_widget_show (properties1);
942     gtk_container_add (GTK_CONTAINER (playlist_menu), properties1);
943     g_object_set_data (G_OBJECT (properties1), "ps", listview);
944 
945     g_signal_connect ((gpointer) add_to_playback_queue1, "activate",
946             G_CALLBACK (main_add_to_playback_queue_activate),
947             NULL);
948     g_signal_connect ((gpointer) remove_from_playback_queue1, "activate",
949             G_CALLBACK (main_remove_from_playback_queue_activate),
950             NULL);
951     g_signal_connect ((gpointer) reload_metadata, "activate",
952             G_CALLBACK (main_reload_metadata_activate),
953             NULL);
954     g_signal_connect ((gpointer) remove2, "activate",
955             G_CALLBACK (on_remove2_activate),
956             NULL);
957     if (!hide_remove_from_disk) {
958         g_signal_connect ((gpointer) remove_from_disk, "activate",
959                 G_CALLBACK (on_remove_from_disk_activate),
960                 NULL);
961     }
962 #ifndef DISABLE_CUSTOM_TITLE
963     g_signal_connect ((gpointer) set_custom_title, "activate",
964             G_CALLBACK (on_set_custom_title_activate),
965             listview);
966 #endif
967     g_signal_connect ((gpointer) properties1, "activate",
968             G_CALLBACK (main_properties_activate),
969             NULL);
970     gtk_menu_popup (GTK_MENU (playlist_menu), NULL, NULL, NULL/*popup_menu_position_func*/, listview, 0, gtk_get_current_event_time());
971 }
972 
973 static DdbListview *last_playlist;
974 static int active_column;
975 
976 void
on_group_by_none_activate(GtkMenuItem * menuitem,gpointer user_data)977 on_group_by_none_activate              (GtkMenuItem     *menuitem,
978                                         gpointer         user_data)
979 {
980     last_playlist->binding->groups_changed (last_playlist, "");
981 
982     ddb_playlist_t *plt = deadbeef->plt_get_curr ();
983     if (plt) {
984         deadbeef->plt_modified (plt);
985         deadbeef->plt_unref (plt);
986     }
987     main_refresh ();
988 }
989 
990 void
on_pin_groups_active(GtkMenuItem * menuitem,gpointer user_data)991 on_pin_groups_active                   (GtkMenuItem     *menuitem,
992                                         gpointer         user_data)
993 {
994     int old_val = deadbeef->conf_get_int ("playlist.pin.groups", 0);
995     deadbeef->conf_set_int ("playlist.pin.groups", old_val ? 0 : 1);
996     deadbeef->sendmessage (DB_EV_CONFIGCHANGED, 0, 0, 0);
997     gtk_check_menu_item_toggled(GTK_CHECK_MENU_ITEM(menuitem));
998     ddb_playlist_t *plt = deadbeef->plt_get_curr ();
999     if (plt) {
1000         deadbeef->plt_modified (plt);
1001         deadbeef->plt_unref(plt);
1002     }
1003     main_refresh ();
1004 }
1005 
1006 void
on_group_by_artist_date_album_activate(GtkMenuItem * menuitem,gpointer user_data)1007 on_group_by_artist_date_album_activate (GtkMenuItem     *menuitem,
1008                                         gpointer         user_data)
1009 {
1010     last_playlist->binding->groups_changed (last_playlist, "%album artist% - ['['%year%']' ]%album%");
1011     ddb_playlist_t *plt = deadbeef->plt_get_curr ();
1012     if (plt) {
1013         deadbeef->plt_modified (plt);
1014         deadbeef->plt_unref (plt);
1015     }
1016     main_refresh ();
1017 }
1018 
1019 void
on_group_by_artist_activate(GtkMenuItem * menuitem,gpointer user_data)1020 on_group_by_artist_activate            (GtkMenuItem     *menuitem,
1021                                         gpointer         user_data)
1022 {
1023     last_playlist->binding->groups_changed (last_playlist, "%artist%");
1024     ddb_playlist_t *plt = deadbeef->plt_get_curr ();
1025     if (plt) {
1026         deadbeef->plt_modified (plt);
1027         deadbeef->plt_unref (plt);
1028     }
1029     main_refresh ();
1030 }
1031 
1032 void
on_group_by_custom_activate(GtkMenuItem * menuitem,gpointer user_data)1033 on_group_by_custom_activate            (GtkMenuItem     *menuitem,
1034                                         gpointer         user_data)
1035 {
1036     GtkWidget *dlg = create_groupbydlg ();
1037 
1038     gtk_dialog_set_default_response (GTK_DIALOG (dlg), GTK_RESPONSE_OK);
1039     GtkWidget *entry = lookup_widget (dlg, "format");
1040     if (last_playlist->group_format) {
1041         gtk_entry_set_text (GTK_ENTRY (entry), last_playlist->group_format);
1042     }
1043     else {
1044         gtk_entry_set_text (GTK_ENTRY (entry), "");
1045     }
1046 //    gtk_window_set_title (GTK_WINDOW (dlg), "Group by");
1047     gint response = gtk_dialog_run (GTK_DIALOG (dlg));
1048 
1049     if (response == GTK_RESPONSE_OK) {
1050         const gchar *text = gtk_entry_get_text (GTK_ENTRY (entry));
1051         last_playlist->binding->groups_changed (last_playlist, text);
1052         ddb_playlist_t *plt = deadbeef->plt_get_curr ();
1053         if (plt) {
1054             deadbeef->plt_modified (plt);
1055             deadbeef->plt_unref (plt);
1056         }
1057         main_refresh ();
1058     }
1059     gtk_widget_destroy (dlg);
1060 }
1061 
1062 void
set_last_playlist_cm(DdbListview * pl)1063 set_last_playlist_cm (DdbListview *pl) {
1064     last_playlist = pl;
1065 }
1066 
1067 void
set_active_column_cm(int col)1068 set_active_column_cm (int col) {
1069     active_column = col;
1070 }
1071 
1072 int
load_column_config(DdbListview * listview,const char * key)1073 load_column_config (DdbListview *listview, const char *key) {
1074     deadbeef->conf_lock ();
1075     const char *json = deadbeef->conf_get_str_fast (key, NULL);
1076     json_error_t error;
1077     if (!json) {
1078         deadbeef->conf_unlock ();
1079         return -1;
1080     }
1081     json_t *root = json_loads (json, 0, &error);
1082     deadbeef->conf_unlock ();
1083     if(!root) {
1084         printf ("json parse error for config variable %s\n", key);
1085         return -1;
1086     }
1087     if (!json_is_array (root))
1088     {
1089         goto error;
1090     }
1091     for (int i = 0; i < json_array_size(root); i++) {
1092         json_t *title, *align, *id, *format, *width, *color_override, *color;
1093         json_t *data = json_array_get (root, i);
1094         if (!json_is_object (data)) {
1095             goto error;
1096         }
1097         title = json_object_get (data, "title");
1098         align = json_object_get (data, "align");
1099         id = json_object_get (data, "id");
1100         format = json_object_get (data, "format");
1101         width = json_object_get (data, "size");
1102         color_override = json_object_get (data, "color_override");
1103         color = json_object_get (data, "color");
1104         if (!json_is_string (title)
1105                 || !json_is_string (id)
1106                 || !json_is_string (width)
1107            ) {
1108             goto error;
1109         }
1110         const char *stitle = NULL;
1111         int ialign = -1;
1112         int iid = -1;
1113         const char *sformat = NULL;
1114         int iwidth = 0;
1115         int icolor_override = 0;
1116         const char *scolor = NULL;
1117         GdkColor gdkcolor = {0};
1118         stitle = json_string_value (title);
1119         if (json_is_string (align)) {
1120             ialign = atoi (json_string_value (align));
1121         }
1122         if (json_is_string (id)) {
1123             iid = atoi (json_string_value (id));
1124         }
1125         if (json_is_string (format)) {
1126             sformat = json_string_value (format);
1127             if (!sformat[0]) {
1128                 sformat = NULL;
1129             }
1130         }
1131         if (json_is_string (width)) {
1132             iwidth = atoi (json_string_value (width));
1133         }
1134         if (json_is_string (color_override)) {
1135             icolor_override = atoi (json_string_value (color_override));
1136         }
1137         if (json_is_string (color)) {
1138             scolor = json_string_value (color);
1139             int r, g, b, a;
1140             if (4 == sscanf (scolor, "#%02x%02x%02x%02x", &a, &r, &g, &b)) {
1141                 gdkcolor.red = r<<8;
1142                 gdkcolor.green = g<<8;
1143                 gdkcolor.blue = b<<8;
1144             }
1145             else {
1146                 icolor_override = 0;
1147             }
1148         }
1149 
1150         col_info_t *inf = malloc (sizeof (col_info_t));
1151         memset (inf, 0, sizeof (col_info_t));
1152         inf->id = iid;
1153         if (sformat) {
1154             inf->format = strdup (sformat);
1155             inf->bytecode = deadbeef->tf_compile (inf->format);
1156         }
1157         ddb_listview_column_append (listview, stitle, iwidth, ialign, inf->id == DB_COLUMN_ALBUM_ART ? iwidth : 0, icolor_override, gdkcolor, inf);
1158     }
1159     json_decref(root);
1160     return 0;
1161 error:
1162     fprintf (stderr, "%s config variable contains invalid data, ignored\n", key);
1163     json_decref(root);
1164     return -1;
1165 }
1166 
1167 static void
init_column(col_info_t * inf,int id,const char * format)1168 init_column (col_info_t *inf, int id, const char *format) {
1169     if (inf->format) {
1170         free (inf->format);
1171         inf->format = NULL;
1172     }
1173     if (inf->bytecode) {
1174         deadbeef->tf_free (inf->bytecode);
1175         inf->bytecode = NULL;
1176     }
1177     inf->id = -1;
1178 
1179     switch (id) {
1180     case 0:
1181         inf->id = DB_COLUMN_FILENUMBER;
1182         break;
1183     case 1:
1184         inf->id = DB_COLUMN_PLAYING;
1185         break;
1186     case 2:
1187         inf->id = DB_COLUMN_ALBUM_ART;
1188         break;
1189     case 3:
1190         inf->format = strdup ("$if(%artist%,%artist%,Unknown Artist)[ - %album%]");
1191         break;
1192     case 4:
1193         inf->format = strdup ("$if(%artist%,%artist%,Unknown Artist)");
1194         break;
1195     case 5:
1196         inf->format = strdup ("%album%");
1197         break;
1198     case 6:
1199         inf->format = strdup ("%title%");
1200         break;
1201     case 7:
1202         inf->format = strdup ("%length%");
1203         break;
1204     case 8:
1205         inf->format = strdup ("%tracknumber%");
1206         break;
1207     case 9:
1208         inf->format = strdup ("$if(%album artist%,%album artist%,Unknown Artist)");
1209         break;
1210     default:
1211         inf->format = strdup (format);
1212     }
1213     if (inf->format) {
1214         inf->bytecode = deadbeef->tf_compile (inf->format);
1215     }
1216 }
1217 
1218 int editcolumn_title_changed = 0;
1219 
1220 void
on_add_column_activate(GtkMenuItem * menuitem,gpointer user_data)1221 on_add_column_activate                 (GtkMenuItem     *menuitem,
1222                                         gpointer         user_data)
1223 {
1224     editcolumn_title_changed = 0;
1225     GdkColor color;
1226     gtkui_get_listview_text_color (&color);
1227 
1228     GtkWidget *dlg = create_editcolumndlg ();
1229     gtk_dialog_set_default_response (GTK_DIALOG (dlg), GTK_RESPONSE_OK);
1230     gtk_window_set_title (GTK_WINDOW (dlg), _("Add column"));
1231     gtk_combo_box_set_active (GTK_COMBO_BOX (lookup_widget (dlg, "id")), 0);
1232     gtk_combo_box_set_active (GTK_COMBO_BOX (lookup_widget (dlg, "align")), 0);
1233     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (dlg, "color_override")), 0);
1234 
1235     gtk_color_button_set_color (GTK_COLOR_BUTTON (lookup_widget (dlg, "color")), &color);
1236     gint response = gtk_dialog_run (GTK_DIALOG (dlg));
1237     if (response == GTK_RESPONSE_OK) {
1238         const gchar *title = gtk_entry_get_text (GTK_ENTRY (lookup_widget (dlg, "title")));
1239         const gchar *format = gtk_entry_get_text (GTK_ENTRY (lookup_widget (dlg, "format")));
1240         int sel = gtk_combo_box_get_active (GTK_COMBO_BOX (lookup_widget (dlg, "id")));
1241 
1242         int clr_override = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (lookup_widget (dlg, "color_override")));
1243         GdkColor clr;
1244         gtk_color_button_get_color (GTK_COLOR_BUTTON (lookup_widget (dlg, "color")), &clr);
1245 
1246         col_info_t *inf = malloc (sizeof (col_info_t));
1247         memset (inf, 0, sizeof (col_info_t));
1248 
1249         init_column (inf, sel, format);
1250 
1251         int align = gtk_combo_box_get_active (GTK_COMBO_BOX (lookup_widget (dlg, "align")));
1252         ddb_listview_column_insert (last_playlist, active_column, title, 100, align, inf->id == DB_COLUMN_ALBUM_ART ? 100 : 0, clr_override, clr, inf);
1253         ddb_listview_refresh (last_playlist, DDB_LIST_CHANGED | DDB_REFRESH_COLUMNS | DDB_REFRESH_LIST | DDB_REFRESH_HSCROLL);
1254     }
1255     gtk_widget_destroy (dlg);
1256 }
1257 
1258 
1259 void
on_edit_column_activate(GtkMenuItem * menuitem,gpointer user_data)1260 on_edit_column_activate                (GtkMenuItem     *menuitem,
1261                                         gpointer         user_data)
1262 {
1263     if (active_column == -1)
1264         return;
1265     GtkWidget *dlg = create_editcolumndlg ();
1266     gtk_dialog_set_default_response (GTK_DIALOG (dlg), GTK_RESPONSE_OK);
1267     gtk_window_set_title (GTK_WINDOW (dlg), _("Edit column"));
1268 
1269     const char *title;
1270     int width;
1271     int align_right;
1272     col_info_t *inf;
1273     int minheight;
1274     int color_override;
1275     GdkColor color;
1276     int res = ddb_listview_column_get_info (last_playlist, active_column, &title, &width, &align_right, &minheight, &color_override, &color, (void **)&inf);
1277     if (res == -1) {
1278         trace ("attempted to edit non-existing column\n");
1279         return;
1280     }
1281 
1282     int idx = 10;
1283     if (inf->id == -1) {
1284         if (inf->format) {
1285             if (!strcmp (inf->format, "%a - %b")) {
1286                 idx = 3;
1287             }
1288             else if (!strcmp (inf->format, "%a")) {
1289                 idx = 4;
1290             }
1291             else if (!strcmp (inf->format, "%b")) {
1292                 idx = 5;
1293             }
1294             else if (!strcmp (inf->format, "%t")) {
1295                 idx = 6;
1296             }
1297             else if (!strcmp (inf->format, "%l")) {
1298                 idx = 7;
1299             }
1300             else if (!strcmp (inf->format, "%n")) {
1301                 idx = 8;
1302             }
1303             else if (!strcmp (inf->format, "%B")) {
1304                 idx = 9;
1305             }
1306         }
1307     }
1308     else if (inf->id <= DB_COLUMN_PLAYING) {
1309         idx = inf->id;
1310     }
1311     else if (inf->id == DB_COLUMN_ALBUM_ART) {
1312         idx = 2;
1313     }
1314     gtk_combo_box_set_active (GTK_COMBO_BOX (lookup_widget (dlg, "id")), idx);
1315     if (idx == 10) {
1316         gtk_entry_set_text (GTK_ENTRY (lookup_widget (dlg, "format")), inf->format);
1317     }
1318     gtk_combo_box_set_active (GTK_COMBO_BOX (lookup_widget (dlg, "align")), align_right);
1319     gtk_entry_set_text (GTK_ENTRY (lookup_widget (dlg, "title")), title);
1320     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (lookup_widget (dlg, "color_override")), color_override);
1321 
1322     gtk_color_button_set_color (GTK_COLOR_BUTTON (lookup_widget (dlg, "color")), &color);
1323     editcolumn_title_changed = 0;
1324     gint response = gtk_dialog_run (GTK_DIALOG (dlg));
1325     if (response == GTK_RESPONSE_OK) {
1326         const gchar *title = gtk_entry_get_text (GTK_ENTRY (lookup_widget (dlg, "title")));
1327         const gchar *format = gtk_entry_get_text (GTK_ENTRY (lookup_widget (dlg, "format")));
1328         int id = gtk_combo_box_get_active (GTK_COMBO_BOX (lookup_widget (dlg, "id")));
1329         int align = gtk_combo_box_get_active (GTK_COMBO_BOX (lookup_widget (dlg, "align")));
1330 
1331         int clr_override = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (lookup_widget (dlg, "color_override")));
1332         GdkColor clr;
1333         gtk_color_button_get_color (GTK_COLOR_BUTTON (lookup_widget (dlg, "color")), &clr);
1334 
1335         init_column (inf, id, format);
1336         ddb_listview_column_set_info (last_playlist, active_column, title, width, align, inf->id == DB_COLUMN_ALBUM_ART ? width : 0, clr_override, clr, inf);
1337 
1338         ddb_listview_refresh (last_playlist, DDB_LIST_CHANGED | DDB_REFRESH_COLUMNS | DDB_REFRESH_LIST);
1339     }
1340     gtk_widget_destroy (dlg);
1341 }
1342 
1343 
1344 void
on_remove_column_activate(GtkMenuItem * menuitem,gpointer user_data)1345 on_remove_column_activate              (GtkMenuItem     *menuitem,
1346                                         gpointer         user_data)
1347 {
1348     if (active_column == -1)
1349         return;
1350 
1351     ddb_listview_column_remove (last_playlist, active_column);
1352     ddb_listview_refresh (last_playlist, DDB_LIST_CHANGED | DDB_REFRESH_COLUMNS | DDB_REFRESH_LIST | DDB_REFRESH_HSCROLL);
1353 }
1354 
1355 GtkWidget*
create_headermenu(int groupby)1356 create_headermenu (int groupby)
1357 {
1358   GtkWidget *headermenu;
1359   GtkWidget *add_column;
1360   GtkWidget *edit_column;
1361   GtkWidget *remove_column;
1362   GtkWidget *separator;
1363   GtkWidget *group_by;
1364   GtkWidget *pin_groups;
1365   GtkWidget *group_by_menu;
1366   GtkWidget *none;
1367   GtkWidget *artist_date_album;
1368   GtkWidget *artist;
1369   GtkWidget *custom;
1370 
1371   headermenu = gtk_menu_new ();
1372 
1373   add_column = gtk_menu_item_new_with_mnemonic (_("Add column"));
1374   gtk_widget_show (add_column);
1375   gtk_container_add (GTK_CONTAINER (headermenu), add_column);
1376 
1377   edit_column = gtk_menu_item_new_with_mnemonic (_("Edit column"));
1378   gtk_widget_show (edit_column);
1379   gtk_container_add (GTK_CONTAINER (headermenu), edit_column);
1380 
1381   remove_column = gtk_menu_item_new_with_mnemonic (_("Remove column"));
1382   gtk_widget_show (remove_column);
1383   gtk_container_add (GTK_CONTAINER (headermenu), remove_column);
1384 
1385   if (groupby) {
1386       separator = gtk_separator_menu_item_new ();
1387       gtk_widget_show (separator);
1388       gtk_container_add (GTK_CONTAINER (headermenu), separator);
1389       gtk_widget_set_sensitive (separator, FALSE);
1390 
1391       pin_groups = gtk_check_menu_item_new_with_mnemonic(_("Pin groups when scrolling"));
1392       gtk_widget_show (pin_groups);
1393       gtk_container_add (GTK_CONTAINER (headermenu), pin_groups);
1394       gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (pin_groups), (gboolean)deadbeef->conf_get_int("playlist.pin.groups",0));
1395 
1396       group_by = gtk_menu_item_new_with_mnemonic (_("Group by"));
1397       gtk_widget_show (group_by);
1398       gtk_container_add (GTK_CONTAINER (headermenu), group_by);
1399 
1400       group_by_menu = gtk_menu_new ();
1401       gtk_menu_item_set_submenu (GTK_MENU_ITEM (group_by), group_by_menu);
1402 
1403       none = gtk_menu_item_new_with_mnemonic (_("None"));
1404       gtk_widget_show (none);
1405       gtk_container_add (GTK_CONTAINER (group_by_menu), none);
1406 
1407       artist_date_album = gtk_menu_item_new_with_mnemonic (_("Artist/Date/Album"));
1408       gtk_widget_show (artist_date_album);
1409       gtk_container_add (GTK_CONTAINER (group_by_menu), artist_date_album);
1410 
1411       artist = gtk_menu_item_new_with_mnemonic (_("Artist"));
1412       gtk_widget_show (artist);
1413       gtk_container_add (GTK_CONTAINER (group_by_menu), artist);
1414 
1415       custom = gtk_menu_item_new_with_mnemonic (_("Custom"));
1416       gtk_widget_show (custom);
1417       gtk_container_add (GTK_CONTAINER (group_by_menu), custom);
1418 
1419       g_signal_connect ((gpointer) none, "activate",
1420               G_CALLBACK (on_group_by_none_activate),
1421               NULL);
1422 
1423       g_signal_connect ((gpointer) pin_groups, "activate",
1424               G_CALLBACK (on_pin_groups_active),
1425               NULL);
1426 
1427       g_signal_connect ((gpointer) artist_date_album, "activate",
1428               G_CALLBACK (on_group_by_artist_date_album_activate),
1429               NULL);
1430 
1431       g_signal_connect ((gpointer) artist, "activate",
1432               G_CALLBACK (on_group_by_artist_activate),
1433               NULL);
1434 
1435       g_signal_connect ((gpointer) custom, "activate",
1436               G_CALLBACK (on_group_by_custom_activate),
1437               NULL);
1438   }
1439 
1440   g_signal_connect ((gpointer) add_column, "activate",
1441                     G_CALLBACK (on_add_column_activate),
1442                     NULL);
1443   g_signal_connect ((gpointer) edit_column, "activate",
1444                     G_CALLBACK (on_edit_column_activate),
1445                     NULL);
1446   g_signal_connect ((gpointer) remove_column, "activate",
1447                     G_CALLBACK (on_remove_column_activate),
1448                     NULL);
1449 
1450   return headermenu;
1451 }
1452 
1453 void
add_column_helper(DdbListview * listview,const char * title,int width,int id,const char * format,int align_right)1454 add_column_helper (DdbListview *listview, const char *title, int width, int id, const char *format, int align_right) {
1455     if (!format) {
1456         format = "";
1457     }
1458     col_info_t *inf = malloc (sizeof (col_info_t));
1459     memset (inf, 0, sizeof (col_info_t));
1460     inf->id = id;
1461     inf->format = strdup (format);
1462     inf->bytecode = deadbeef->tf_compile (inf->format);
1463     GdkColor color = { 0, 0, 0, 0 };
1464     ddb_listview_column_append (listview, title, width, align_right, inf->id == DB_COLUMN_ALBUM_ART ? width : 0, 0, color, inf);
1465 }
1466 
1467 int
pl_common_get_group(DdbListview * listview,DdbListviewIter it,char * str,int size)1468 pl_common_get_group (DdbListview *listview, DdbListviewIter it, char *str, int size) {
1469     if (!listview->group_format || !listview->group_format[0]) {
1470         return -1;
1471     }
1472     if (listview->group_title_bytecode) {
1473         ddb_tf_context_t ctx = {
1474             ._size = sizeof (ddb_tf_context_t),
1475             .it = it,
1476             .plt = deadbeef->plt_get_curr (),
1477             .flags = DDB_TF_CONTEXT_NO_DYNAMIC,
1478         };
1479         deadbeef->tf_eval (&ctx, listview->group_title_bytecode, str, size);
1480         if (ctx.plt) {
1481             deadbeef->plt_unref (ctx.plt);
1482             ctx.plt = NULL;
1483         }
1484 
1485         char *lb = strchr (str, '\r');
1486         if (lb) {
1487             *lb = 0;
1488         }
1489         lb = strchr (str, '\n');
1490         if (lb) {
1491             *lb = 0;
1492         }
1493     }
1494     return 0;
1495 }
1496 
1497 void
pl_common_draw_group_title(DdbListview * listview,cairo_t * drawable,DdbListviewIter it,int iter,int x,int y,int width,int height)1498 pl_common_draw_group_title (DdbListview *listview, cairo_t *drawable, DdbListviewIter it, int iter, int x, int y, int width, int height) {
1499     if (listview->group_format && listview->group_format[0]) {
1500         char str[1024] = "";
1501         if (listview->group_title_bytecode) {
1502             ddb_tf_context_t ctx = {
1503                 ._size = sizeof (ddb_tf_context_t),
1504                 .it = it,
1505                 .plt = deadbeef->plt_get_curr (),
1506                 .flags = DDB_TF_CONTEXT_NO_DYNAMIC,
1507                 .iter = iter,
1508             };
1509             deadbeef->tf_eval (&ctx, listview->group_title_bytecode, str, sizeof (str));
1510             if (ctx.plt) {
1511                 deadbeef->plt_unref (ctx.plt);
1512                 ctx.plt = NULL;
1513             }
1514 
1515 
1516             char *lb = strchr (str, '\r');
1517             if (lb) {
1518                 *lb = 0;
1519             }
1520             lb = strchr (str, '\n');
1521             if (lb) {
1522                 *lb = 0;
1523             }
1524         }
1525 
1526         int theming = !gtkui_override_listview_colors ();
1527         if (theming) {
1528             GdkColor *clr = &gtk_widget_get_style(theme_treeview)->fg[GTK_STATE_NORMAL];
1529             float rgb[] = {clr->red/65535.f, clr->green/65535.f, clr->blue/65535.f};
1530             draw_set_fg_color (&listview->grpctx, rgb);
1531         }
1532         else {
1533             GdkColor clr;
1534             gtkui_get_listview_group_text_color (&clr);
1535             float rgb[] = {clr.red/65535.f, clr.green/65535.f, clr.blue/65535.f};
1536             draw_set_fg_color (&listview->grpctx, rgb);
1537         }
1538         int ew;
1539         draw_text_custom (&listview->grpctx, x + 5, y + height/2 - draw_get_listview_rowheight (&listview->grpctx)/2 + 3, -1, 0, DDB_GROUP_FONT, 0, 0, str);
1540         draw_get_layout_extents (&listview->grpctx, &ew, NULL);
1541         int len = strlen (str);
1542         int line_x = x + 5 + ew + (len ? ew / len / 2 : 0);
1543         if (line_x < x + width) {
1544             draw_line (&listview->grpctx, line_x, y+height/2, x+width, y+height/2);
1545         }
1546     }
1547 }
1548 
1549 static int
import_column_from_0_6(const uint8_t * def,char * json_out,int outsize)1550 import_column_from_0_6 (const uint8_t *def, char *json_out, int outsize) {
1551     // syntax: "title" "format" id width alignright
1552     char token[MAX_TOKEN];
1553     const char *p = def;
1554     char title[MAX_TOKEN];
1555     int id;
1556     char fmt[MAX_TOKEN];
1557     int width;
1558     int align;
1559 
1560     *json_out = 0;
1561 
1562     parser_init ();
1563 
1564     p = gettoken_warn_eof (p, token);
1565     if (!p) {
1566         return 0;
1567     }
1568     strcpy (title, token);
1569 
1570     p = gettoken_warn_eof (p, token);
1571     if (!p) {
1572         return 0;
1573     }
1574     strcpy (fmt, token);
1575 
1576     p = gettoken_warn_eof (p, token);
1577     if (!p) {
1578         return 0;
1579     }
1580     id = atoi (token);
1581 
1582     p = gettoken_warn_eof (p, token);
1583     if (!p) {
1584         return 0;
1585     }
1586     width = atoi (token);
1587 
1588     p = gettoken_warn_eof (p, token);
1589     if (!p) {
1590         return 0;
1591     }
1592     align = atoi (token);
1593 
1594     enum {
1595         DB_COLUMN_ARTIST_ALBUM = 2,
1596         DB_COLUMN_ARTIST = 3,
1597         DB_COLUMN_ALBUM = 4,
1598         DB_COLUMN_TITLE = 5,
1599         DB_COLUMN_DURATION = 6,
1600         DB_COLUMN_TRACK = 7,
1601     };
1602 
1603     int out_id = -1;
1604     const char *format;
1605 #define MAX_COLUMN_TF 2048
1606     char out_tf[MAX_COLUMN_TF];
1607 
1608     // convert IDs from pre-0.4
1609     switch (id) {
1610     case DB_COLUMN_ARTIST_ALBUM:
1611         format = "%artist% - %album%";
1612         break;
1613     case DB_COLUMN_ARTIST:
1614         format = "%artist%";
1615         break;
1616     case DB_COLUMN_ALBUM:
1617         format = "%album%";
1618         break;
1619     case DB_COLUMN_TITLE:
1620         format = "%title%";
1621         break;
1622     case DB_COLUMN_DURATION:
1623         format = "%length%";
1624         break;
1625     case DB_COLUMN_TRACK:
1626         format = "%tracknumber%";
1627         break;
1628     default:
1629         deadbeef->tf_import_legacy (fmt, out_tf, sizeof (out_tf));
1630         format = out_tf;
1631         out_id = id;
1632         break;
1633     }
1634     int ret = snprintf (json_out, outsize, "{\"title\":\"%s\",\"id\":\"%d\",\"format\":\"%s\",\"size\":\"%d\",\"align\":\"%d\"}", title, out_id, format, width, align);
1635     return min (ret, outsize);
1636 }
1637 
1638 
1639 int
import_column_config_0_6(const char * oldkeyprefix,const char * newkey)1640 import_column_config_0_6 (const char *oldkeyprefix, const char *newkey) {
1641     DB_conf_item_t *col = deadbeef->conf_find (oldkeyprefix, NULL);
1642     if (!col) {
1643         return 0;
1644     }
1645 
1646 #define MAX_COLUMN_CONFIG 20000
1647     char *json = calloc (1, MAX_COLUMN_CONFIG);
1648     char *out = json;
1649     int jsonsize = MAX_COLUMN_CONFIG-1;
1650 
1651     *out++ = '[';
1652     jsonsize--;
1653 
1654     int idx = 0;
1655     while (col) {
1656         if (jsonsize < 2) {
1657             break;
1658         }
1659         if (idx != 0) {
1660             *out++ = ',';
1661             jsonsize--;
1662         }
1663         int res = import_column_from_0_6 (col->value, out, jsonsize);
1664         out += res;
1665         jsonsize -= res;
1666         col = deadbeef->conf_find (oldkeyprefix, col);
1667         idx++;
1668     }
1669     *out++ = ']';
1670     if (*json) {
1671         deadbeef->conf_set_str (newkey, json);
1672     }
1673     free (json);
1674     return 0;
1675 }
1676