1 /*
2  * columns.c
3  * Copyright 2011 John Lindgren
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  *    this list of conditions, and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  *    this list of conditions, and the following disclaimer in the documentation
13  *    provided with the distribution.
14  *
15  * This software is provided "as is" and without any warranty, express or
16  * implied. In no event shall the authors be liable for any damages arising from
17  * the use of this software.
18  */
19 
20 #include <string.h>
21 
22 #include <gtk/gtk.h>
23 
24 #include <libaudcore/i18n.h>
25 #include <libaudcore/runtime.h>
26 #include <libaudcore/index.h>
27 #include <libaudcore/audstrings.h>
28 #include <libaudgui/libaudgui-gtk.h>
29 #include <libaudgui/list.h>
30 
31 #include "ui_playlist_notebook.h"
32 #include "ui_playlist_widget.h"
33 
34 const char * const pw_col_names[PW_COLS] = {
35     N_("Entry number"),
36     N_("Title"),
37     N_("Artist"),
38     N_("Year"),
39     N_("Album"),
40     N_("Album artist"),
41     N_("Track"),
42     N_("Genre"),
43     N_("Queue position"),
44     N_("Length"),
45     N_("File path"),
46     N_("File name"),
47     N_("Custom title"),
48     N_("Bitrate"),
49     N_("Comment")
50 };
51 
52 int pw_num_cols;
53 int pw_cols[PW_COLS];
54 int pw_col_widths[PW_COLS];
55 
56 static const char * const pw_col_keys[PW_COLS] = {
57     "number",
58     "title",
59     "artist",
60     "year",
61     "album",
62     "album-artist",
63     "track",
64     "genre",
65     "queued",
66     "length",
67     "path",
68     "filename",
69     "custom",
70     "bitrate",
71     "comment"
72 };
73 
74 static const int pw_default_widths[PW_COLS] = {
75     10,   // entry number
76     275,  // title
77     175,  // artist
78     10,   // year
79     175,  // album
80     175,  // album artist
81     10,   // track
82     100,  // genre
83     10,   // queue position
84     10,   // length
85     275,  // path
86     275,  // filename
87     275,  // custom title
88     10,   // bitrate
89     275   // comment
90 };
91 
pw_col_init()92 void pw_col_init ()
93 {
94     pw_num_cols = 0;
95 
96     String columns = aud_get_str ("gtkui", "playlist_columns");
97     Index<String> index = str_list_to_index (columns, " ");
98 
99     int count = index.len ();
100     if (count > PW_COLS)
101         count = PW_COLS;
102 
103     for (int c = 0; c < count; c ++)
104     {
105         const String & column = index[c];
106 
107         int i = 0;
108         while (i < PW_COLS && strcmp (column, pw_col_keys[i]))
109             i ++;
110 
111         if (i == PW_COLS)
112             break;
113 
114         pw_cols[pw_num_cols ++] = i;
115     }
116 
117     auto widths = str_list_to_index (aud_get_str ("gtkui", "column_widths"), ", ");
118     int nwidths = aud::min (widths.len (), (int) PW_COLS);
119 
120     for (int i = 0; i < nwidths; i ++)
121         pw_col_widths[i] = audgui_to_native_dpi (str_to_int (widths[i]));
122     for (int i = nwidths; i < PW_COLS; i ++)
123         pw_col_widths[i] = audgui_to_native_dpi (pw_default_widths[i]);
124 }
125 
126 struct Column {
127     int column;
128     bool selected;
129 };
130 
131 static GtkWidget * chosen_list = nullptr, * avail_list = nullptr;
132 static Index<Column> chosen, avail;
133 
apply_changes()134 static void apply_changes ()
135 {
136     int cols = chosen.len ();
137     g_return_if_fail (cols <= PW_COLS);
138 
139     pl_notebook_purge ();
140 
141     for (pw_num_cols = 0; pw_num_cols < cols; pw_num_cols ++)
142         pw_cols[pw_num_cols] = chosen[pw_num_cols].column;
143 
144     pl_notebook_populate ();
145 }
146 
get_value(void * user,int row,int column,GValue * value)147 static void get_value (void * user, int row, int column, GValue * value)
148 {
149     auto & index = * (Index<Column> *) user;
150     g_return_if_fail (row >= 0 && row < index.len ());
151     g_value_set_string (value, _(pw_col_names[index[row].column]));
152 }
153 
get_selected(void * user,int row)154 static bool get_selected (void * user, int row)
155 {
156     auto & index = * (Index<Column> *) user;
157     g_return_val_if_fail (row >= 0 && row < index.len (), false);
158     return index[row].selected;
159 }
160 
set_selected(void * user,int row,bool selected)161 static void set_selected (void * user, int row, bool selected)
162 {
163     auto & index = * (Index<Column> *) user;
164     g_return_if_fail (row >= 0 && row < index.len ());
165     index[row].selected = selected;
166 }
167 
select_all(void * user,bool selected)168 static void select_all (void * user, bool selected)
169 {
170     auto & index = * (Index<Column> *) user;
171     for (Column & col : index)
172         col.selected = selected;
173 }
174 
shift_rows(void * user,int row,int before)175 static void shift_rows (void * user, int row, int before)
176 {
177     auto & index = * (Index<Column> *) user;
178     int rows = index.len ();
179     g_return_if_fail (row >= 0 && row < rows);
180     g_return_if_fail (before >= 0 && before <= rows);
181 
182     if (before == row)
183         return;
184 
185     Index<Column> move;
186     Index<Column> others;
187 
188     int begin, end;
189     if (before < row)
190     {
191         begin = before;
192         end = row + 1;
193         while (end < rows && index[end].selected)
194             end ++;
195     }
196     else
197     {
198         begin = row;
199         while (begin > 0 && index[begin - 1].selected)
200             begin --;
201         end = before;
202     }
203 
204     for (int i = begin; i < end; i ++)
205     {
206         if (index[i].selected)
207             move.append (index[i]);
208         else
209             others.append (index[i]);
210     }
211 
212     if (before < row)
213         move.move_from (others, 0, -1, -1, true, true);
214     else
215         move.move_from (others, 0, 0, -1, true, true);
216 
217     index.move_from (move, 0, begin, end - begin, false, true);
218 
219     GtkWidget * list = (& index == & chosen) ? chosen_list : avail_list;
220     audgui_list_update_rows (list, begin, end - begin);
221     audgui_list_update_selection (list, begin, end - begin);
222 
223     apply_changes ();
224 }
225 
226 static const AudguiListCallbacks callbacks = {
227     get_value,
228     get_selected,
229     set_selected,
230     select_all,
231     nullptr,  // activate_row
232     nullptr,  // right_click
233     shift_rows
234 };
235 
transfer(Index<Column> * source)236 static void transfer (Index<Column> * source)
237 {
238     Index<Column> * dest;
239     GtkWidget * source_list, * dest_list;
240     if (source == & chosen)
241     {
242         dest = & avail;
243         source_list = chosen_list;
244         dest_list = avail_list;
245     }
246     else
247     {
248         dest = & chosen;
249         source_list = avail_list;
250         dest_list = chosen_list;
251     }
252 
253     int source_rows = source->len ();
254     int dest_rows = dest->len ();
255 
256     for (int row = 0; row < source_rows; )
257     {
258         Column c = (* source)[row];
259         if (! c.selected)
260         {
261             row ++;
262             continue;
263         }
264 
265         source->remove (row, 1);
266         audgui_list_delete_rows (source_list, row, 1);
267         source_rows --;
268         dest->append (c);
269         audgui_list_insert_rows (dest_list, dest_rows, 1);
270         dest_rows ++;
271     }
272 
273     apply_changes ();
274 }
275 
destroy_cb()276 static void destroy_cb ()
277 {
278     chosen_list = nullptr;
279     avail_list = nullptr;
280 
281     chosen.clear ();
282     avail.clear ();
283 }
284 
pw_col_create_chooser()285 void * pw_col_create_chooser ()
286 {
287     bool added[PW_COLS] = {};
288 
289     for (int i = 0; i < pw_num_cols; i ++)
290     {
291         if (! added[pw_cols[i]])
292         {
293             added[pw_cols[i]] = true;
294             chosen.append (pw_cols[i], false);
295         }
296     }
297 
298     for (int i = 0; i < PW_COLS; i ++)
299     {
300         if (! added[i])
301             avail.append (i, false);
302     }
303 
304     GtkWidget * hbox = gtk_hbox_new (false, 6);
305     gtk_widget_set_size_request (hbox, -1, audgui_get_dpi () * 5 / 4);
306 
307     GtkWidget * scroll = gtk_scrolled_window_new (nullptr, nullptr);
308     gtk_scrolled_window_set_policy ((GtkScrolledWindow *) scroll,
309      GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
310     gtk_scrolled_window_set_shadow_type ((GtkScrolledWindow *) scroll,
311      GTK_SHADOW_IN);
312     gtk_box_pack_start ((GtkBox *) hbox, scroll, true, true, 0);
313 
314     avail_list = audgui_list_new (& callbacks, & avail, avail.len ());
315     audgui_list_add_column (avail_list, _("Available columns"), 0, G_TYPE_STRING, -1);
316     gtk_container_add ((GtkContainer *) scroll, avail_list);
317 
318     GtkWidget * vbox = gtk_vbox_new (false, 6);
319     gtk_box_pack_start ((GtkBox *) hbox, vbox, false, false, 0);
320 
321     GtkWidget * button = gtk_button_new ();
322     gtk_container_add ((GtkContainer *) button, gtk_image_new_from_icon_name
323      ("go-next", GTK_ICON_SIZE_BUTTON));
324     gtk_box_pack_start ((GtkBox *) vbox, button, true, false, 0);
325     g_signal_connect_swapped (button, "clicked", (GCallback) transfer, & avail);
326 
327     button = gtk_button_new ();
328     gtk_container_add ((GtkContainer *) button, gtk_image_new_from_icon_name
329      ("go-previous", GTK_ICON_SIZE_BUTTON));
330     gtk_box_pack_start ((GtkBox *) vbox, button, true, false, 0);
331     g_signal_connect_swapped (button, "clicked", (GCallback) transfer, & chosen);
332 
333     scroll = gtk_scrolled_window_new (nullptr, nullptr);
334     gtk_scrolled_window_set_policy ((GtkScrolledWindow *) scroll,
335      GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
336     gtk_scrolled_window_set_shadow_type ((GtkScrolledWindow *) scroll,
337      GTK_SHADOW_IN);
338     gtk_box_pack_start ((GtkBox *) hbox, scroll, true, true, 0);
339 
340     chosen_list = audgui_list_new (& callbacks, & chosen, chosen.len ());
341     audgui_list_add_column (chosen_list, _("Displayed columns"), 0, G_TYPE_STRING, -1);
342     gtk_container_add ((GtkContainer *) scroll, chosen_list);
343 
344     g_signal_connect (hbox, "destroy", (GCallback) destroy_cb, nullptr);
345 
346     return hbox;
347 }
348 
pw_col_save()349 void pw_col_save ()
350 {
351     Index<String> index;
352     for (int i = 0; i < pw_num_cols; i ++)
353         index.append (String (pw_col_keys[pw_cols[i]]));
354 
355     int widths[PW_COLS];
356     for (int i = 0; i < PW_COLS; i ++)
357         widths[i] = audgui_to_portable_dpi (pw_col_widths[i]);
358 
359     aud_set_str ("gtkui", "playlist_columns", index_to_str_list (index, " "));
360     aud_set_str ("gtkui", "column_widths", int_array_to_str (widths, PW_COLS));
361 }
362