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