chiark / gitweb /
more verbose menu items
[disorder] / disobedience / properties.c
... / ...
CommitLineData
1/*
2 * This file is part of DisOrder.
3 * Copyright (C) 2006 Richard Kettlewell
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18 * USA
19 */
20
21#include "disobedience.h"
22
23/* Track properties -------------------------------------------------------- */
24
25struct prefdata;
26
27static void kickoff_namepart(struct prefdata *f);
28static void completed_namepart(struct prefdata *f);
29static const char *get_edited_namepart(struct prefdata *f);
30static void set_namepart(struct prefdata *f, const char *value);
31static void set_namepart_completed(void *v);
32
33static void kickoff_string(struct prefdata *f);
34static void completed_string(struct prefdata *f);
35static const char *get_edited_string(struct prefdata *f);
36static void set_string(struct prefdata *f, const char *value);
37
38static void kickoff_boolean(struct prefdata *f);
39static void completed_boolean(struct prefdata *f);
40static const char *get_edited_boolean(struct prefdata *f);
41static void set_boolean(struct prefdata *f, const char *value);
42
43static void prefdata_completed(void *v, const char *value);
44static void prefdata_onerror(struct callbackdata *cbd,
45 int code,
46 const char *msg);
47static struct callbackdata *make_callbackdata(struct prefdata *f);
48static void prefdata_completed_common(struct prefdata *f,
49 const char *value);
50
51static void properties_ok(GtkButton *button, gpointer userdata);
52static void properties_apply(GtkButton *button, gpointer userdata);
53static void properties_cancel(GtkButton *button, gpointer userdata);
54
55/* Data for a single preference */
56struct prefdata {
57 const char *track;
58 int row;
59 const struct pref *p;
60 const char *value;
61 GtkWidget *widget;
62};
63
64/* The type of a preference is the collection of callbacks needed to get,
65 * display and set it */
66struct preftype {
67 void (*kickoff)(struct prefdata *f);
68 /* Kick off the request to fetch the pref from the server. */
69
70 void (*completed)(struct prefdata *f);
71 /* Called when the value comes back in; creates the widget. */
72
73 const char *(*get_edited)(struct prefdata *f);
74 /* Get the edited value from the widget. */
75
76 void (*set)(struct prefdata *f, const char *value);
77 /* Set the new value and (if necessary) arrange for our display to update. */
78};
79
80/* A namepart pref */
81static const struct preftype preftype_namepart = {
82 kickoff_namepart,
83 completed_namepart,
84 get_edited_namepart,
85 set_namepart
86};
87
88/* A string pref */
89static const struct preftype preftype_string = {
90 kickoff_string,
91 completed_string,
92 get_edited_string,
93 set_string
94};
95
96/* A boolean pref */
97static const struct preftype preftype_boolean = {
98 kickoff_boolean,
99 completed_boolean,
100 get_edited_boolean,
101 set_boolean
102};
103
104/* The known prefs for each track */
105static const struct pref {
106 const char *label;
107 const char *part;
108 const char *default_value;
109 const struct preftype *type;
110} prefs[] = {
111 { "Artist", "artist", 0, &preftype_namepart },
112 { "Album", "album", 0, &preftype_namepart },
113 { "Title", "title", 0, &preftype_namepart },
114 { "Tags", "tags", "", &preftype_string },
115 { "Random", "pick_at_random", "1", &preftype_boolean },
116};
117
118#define NPREFS (int)(sizeof prefs / sizeof *prefs)
119
120/* Buttons that appear at the bottom of the window */
121static const struct button {
122 const gchar *stock;
123 void (*clicked)(GtkButton *button, gpointer userdata);
124} buttons[] = {
125 { GTK_STOCK_OK, properties_ok },
126 { GTK_STOCK_APPLY, properties_apply },
127 { GTK_STOCK_CANCEL, properties_cancel },
128};
129
130#define NBUTTONS (int)(sizeof buttons / sizeof *buttons)
131
132static int prefs_unfilled; /* Prefs remaining to get */
133static int prefs_total; /* Total prefs */
134static struct prefdata *prefdatas; /* Current prefdatas */
135static GtkWidget *properties_window;
136static GtkWidget *properties_table;
137static GtkWidget *progress_window, *progress_bar;
138
139void properties(int ntracks, char **tracks) {
140 int n, m;
141 struct prefdata *f;
142 GtkWidget *hbox, *vbox, *button, *label, *entry;
143
144 /* If there is a properties window open then just bring it to the
145 * front. It might not have the right values in... */
146 if(properties_window) {
147 if(!prefs_unfilled)
148 gtk_window_present(GTK_WINDOW(properties_window));
149 return;
150 }
151 assert(properties_table == 0);
152 if(ntracks > INT_MAX / NPREFS) {
153 popup_error("Too many tracks selected");
154 return;
155 }
156 /* Create a new properties window */
157 properties_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
158 g_signal_connect(properties_window, "destroy",
159 G_CALLBACK(gtk_widget_destroyed), &properties_window);
160 /* Most of the action is the table of preferences */
161 properties_table = gtk_table_new((NPREFS + 1) * ntracks, 2, FALSE);
162 g_signal_connect(properties_table, "destroy",
163 G_CALLBACK(gtk_widget_destroyed), &properties_table);
164 gtk_window_set_title(GTK_WINDOW(properties_window), "Track Properties");
165 /* Create labels for each pref of each track and kick off requests to the
166 * server to fill in the values */
167 prefs_total = NPREFS * ntracks;
168 prefdatas = xcalloc(prefs_total, sizeof *prefdatas);
169 for(n = 0; n < ntracks; ++n) {
170 label = gtk_label_new("Track");
171 gtk_misc_set_alignment(GTK_MISC(label), 1, 0);
172 gtk_table_attach(GTK_TABLE(properties_table),
173 label,
174 0, 1,
175 (NPREFS + 1) * n, (NPREFS + 1) * n + 1,
176 GTK_FILL, 0,
177 1, 1);
178 entry = gtk_entry_new();
179 gtk_entry_set_text(GTK_ENTRY(entry), tracks[n]);
180 gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
181 gtk_table_attach(GTK_TABLE(properties_table),
182 entry,
183 1, 2,
184 (NPREFS + 1) * n, (NPREFS + 1) * n + 1,
185 GTK_EXPAND|GTK_FILL, 0,
186 1, 1);
187 for(m = 0; m < NPREFS; ++m) {
188 label = gtk_label_new(prefs[m].label);
189 gtk_misc_set_alignment(GTK_MISC(label), 1, 0);
190 gtk_table_attach(GTK_TABLE(properties_table),
191 label,
192 0, 1,
193 (NPREFS + 1) * n + 1 + m, (NPREFS + 1) * n + 2 + m,
194 GTK_FILL/*xoptions*/, 0/*yoptions*/,
195 1, 1);
196 f = &prefdatas[NPREFS * n + m];
197 f->track = tracks[n];
198 f->row = (NPREFS + 1) * n + 1 + m;
199 f->p = &prefs[m];
200 prefs[m].type->kickoff(f);
201 }
202 }
203 prefs_unfilled = prefs_total;
204 /* Buttons */
205 hbox = gtk_hbox_new(FALSE, 1);
206 for(n = 0; n < NBUTTONS; ++n) {
207 button = gtk_button_new_from_stock(buttons[n].stock);
208 g_signal_connect(G_OBJECT(button), "clicked",
209 G_CALLBACK(buttons[n].clicked), 0);
210 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 1);
211 }
212 /* Put it all together */
213 vbox = gtk_vbox_new(FALSE, 1);
214 gtk_box_pack_start(GTK_BOX(vbox),
215 scroll_widget(properties_table,
216 "properties"),
217 TRUE, TRUE, 1);
218 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 1);
219 gtk_container_add(GTK_CONTAINER(properties_window), vbox);
220 /* The table only really wants to be vertically scrollable */
221 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(GTK_WIDGET(properties_table)->parent->parent),
222 GTK_POLICY_NEVER,
223 GTK_POLICY_AUTOMATIC);
224 /* Pop up a progress bar while we're waiting */
225 progress_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
226 g_signal_connect(progress_window, "destroy",
227 G_CALLBACK(gtk_widget_destroyed), &progress_window);
228 gtk_window_set_default_size(GTK_WINDOW(progress_window), 360, -1);
229 gtk_window_set_title(GTK_WINDOW(progress_window),
230 "Fetching Track Properties");
231 progress_bar = gtk_progress_bar_new();
232 gtk_container_add(GTK_CONTAINER(progress_window), progress_bar);
233 gtk_widget_show_all(progress_window);
234}
235
236/* Everything is filled in now */
237static void prefdata_alldone(void) {
238 if(progress_window)
239 gtk_widget_destroy(progress_window);
240 /* Default size may be too small */
241 gtk_window_set_default_size(GTK_WINDOW(properties_window), 480, 512);
242 /* TODO: relate default size to required size more closely */
243 gtk_widget_show_all(properties_window);
244}
245
246/* Namepart preferences ---------------------------------------------------- */
247
248static void kickoff_namepart(struct prefdata *f) {
249 char *s;
250
251 byte_xasprintf(&s, "trackname_display_%s", f->p->part);
252 disorder_eclient_get(client, prefdata_completed, f->track, s,
253 make_callbackdata(f));
254}
255
256static void completed_namepart(struct prefdata *f) {
257 if(!f->value)
258 /* No setting, use the computed default value instead */
259 f->value = trackname_part(f->track, "display", f->p->part);
260 f->widget = gtk_entry_new();
261 gtk_entry_set_text(GTK_ENTRY(f->widget), f->value);
262}
263
264static const char *get_edited_namepart(struct prefdata *f) {
265 return gtk_entry_get_text(GTK_ENTRY(f->widget));
266}
267
268static void set_namepart(struct prefdata *f, const char *value) {
269 char *s;
270 struct callbackdata *cbd = xmalloc(sizeof *cbd);
271
272 cbd->u.f = f;
273 byte_xasprintf(&s, "trackname_display_%s", f->p->part);
274 if(strcmp(trackname_part(f->track, "display", f->p->part), value))
275 /* Different from default, set it */
276 disorder_eclient_set(client, set_namepart_completed, f->track, s, value,
277 cbd);
278 else
279 /* Same as default, just unset */
280 disorder_eclient_unset(client, set_namepart_completed, f->track, s, cbd);
281}
282
283/* Called when we've set a namepart */
284static void set_namepart_completed(void *v) {
285 struct callbackdata *cbd = v;
286 struct prefdata *f = cbd->u.f;
287
288 namepart_update(f->track, "display", f->p->part);
289}
290
291/* String preferences ------------------------------------------------------ */
292
293static void kickoff_string(struct prefdata *f) {
294 disorder_eclient_get(client, prefdata_completed, f->track, f->p->part,
295 make_callbackdata(f));
296}
297
298static void completed_string(struct prefdata *f) {
299 if(!f->value)
300 /* No setting, use the default value instead */
301 f->value = f->p->default_value;
302 f->widget = gtk_entry_new();
303 gtk_entry_set_text(GTK_ENTRY(f->widget), f->value);
304}
305
306static const char *get_edited_string(struct prefdata *f) {
307 return gtk_entry_get_text(GTK_ENTRY(f->widget));
308}
309
310static void set_string(struct prefdata *f, const char *value) {
311 if(strcmp(f->p->default_value, value))
312 /* Different from default, set it */
313 disorder_eclient_set(client, 0/*completed*/, f->track, f->p->part,
314 value, 0/*v*/);
315 else
316 /* Same as default, just unset */
317 disorder_eclient_unset(client, 0/*completed*/, f->track, f->p->part,
318 0/*v*/);
319}
320
321/* Boolean preferences ----------------------------------------------------- */
322
323static void kickoff_boolean(struct prefdata *f) {
324 disorder_eclient_get(client, prefdata_completed, f->track, f->p->part,
325 make_callbackdata(f));
326}
327
328static void completed_boolean(struct prefdata *f) {
329 f->widget = gtk_check_button_new();
330 if(!f->value)
331 /* Not set, use the default */
332 f->value = f->p->default_value;
333 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(f->widget),
334 strcmp(f->value, "0"));
335}
336
337static const char *get_edited_boolean(struct prefdata *f) {
338 return (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(f->widget))
339 ? "1" : "0");
340}
341
342static void set_boolean(struct prefdata *f, const char *value) {
343 char *s;
344
345 byte_xasprintf(&s, "trackname_display_%s", f->p->part);
346 if(strcmp(value, f->p->default_value))
347 disorder_eclient_set(client, 0/*completed*/, f->track, f->p->part, value,
348 0/*v*/);
349 else
350 /* If default value then delete the pref */
351 disorder_eclient_unset(client, 0/*completed*/, f->track, f->p->part,
352 0/*v*/);
353}
354
355/* Querying preferences ---------------------------------------------------- */
356
357/* Make a suitable callbackdata */
358static struct callbackdata *make_callbackdata(struct prefdata *f) {
359 struct callbackdata *cbd = xmalloc(sizeof *cbd);
360
361 cbd->onerror = prefdata_onerror;
362 cbd->u.f = f;
363 return cbd;
364}
365
366/* No pref was set */
367static void prefdata_onerror(struct callbackdata *cbd,
368 int attribute((unused)) code,
369 const char attribute((unused)) *msg) {
370 prefdata_completed_common(cbd->u.f, 0);
371}
372
373/* Got the value of a pref */
374static void prefdata_completed(void *v, const char *value) {
375 struct callbackdata *cbd = v;
376
377 prefdata_completed_common(cbd->u.f, value);
378}
379
380static void prefdata_completed_common(struct prefdata *f,
381 const char *value) {
382 f->value = value;
383 f->p->type->completed(f);
384 assert(f->value != 0); /* Had better set a default */
385 gtk_table_attach(GTK_TABLE(properties_table), f->widget,
386 1, 2,
387 f->row, f->row + 1,
388 GTK_EXPAND|GTK_FILL/*xoptions*/, 0/*yoptions*/,
389 1, 1);
390 --prefs_unfilled;
391 if(prefs_total && progress_window)
392 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress_bar),
393 1.0 - (double)prefs_unfilled / prefs_total);
394 if(!prefs_unfilled)
395 prefdata_alldone();
396}
397
398/* Button callbacks -------------------------------------------------------- */
399
400static void properties_ok(GtkButton *button,
401 gpointer userdata) {
402 properties_apply(button, userdata);
403 properties_cancel(button, userdata);
404}
405
406static void properties_apply(GtkButton attribute((unused)) *button,
407 gpointer attribute((unused)) userdata) {
408 int n;
409 const char *edited;
410 struct prefdata *f;
411
412 /* For each possible property we see if we've changed it and if so tell the
413 * server */
414 for(n = 0; n < prefs_total; ++n) {
415 f = &prefdatas[n];
416 edited = f->p->type->get_edited(f);
417 if(strcmp(edited, f->value)) {
418 /* The value has changed */
419 f->p->type->set(f, edited);
420 f->value = xstrdup(edited);
421 }
422 }
423}
424
425static void properties_cancel(GtkButton attribute((unused)) *button,
426 gpointer attribute((unused)) userdata) {
427 gtk_widget_destroy(properties_window);
428}
429
430/*
431Local Variables:
432c-basic-offset:2
433comment-column:40
434fill-column:79
435indent-tabs-mode:nil
436End:
437*/