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 = >k_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 = >k_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 = >k_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