2 * This file is part of DisOrder.
3 * Copyright (C) 2006 Richard Kettlewell
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.
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.
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
21 #include "disobedience.h"
23 /* Track properties -------------------------------------------------------- */
27 static void kickoff_namepart(struct prefdata
*f
);
28 static void completed_namepart(struct prefdata
*f
);
29 static const char *get_edited_namepart(struct prefdata
*f
);
30 static void set_namepart(struct prefdata
*f
, const char *value
);
31 static void set_namepart_completed(void *v
);
33 static void kickoff_string(struct prefdata
*f
);
34 static void completed_string(struct prefdata
*f
);
35 static const char *get_edited_string(struct prefdata
*f
);
36 static void set_string(struct prefdata
*f
, const char *value
);
38 static void kickoff_boolean(struct prefdata
*f
);
39 static void completed_boolean(struct prefdata
*f
);
40 static const char *get_edited_boolean(struct prefdata
*f
);
41 static void set_boolean(struct prefdata
*f
, const char *value
);
43 static void prefdata_completed(void *v
, const char *value
);
44 static void prefdata_onerror(struct callbackdata
*cbd
,
47 static struct callbackdata
*make_callbackdata(struct prefdata
*f
);
48 static void prefdata_completed_common(struct prefdata
*f
,
51 static void properties_ok(GtkButton
*button
, gpointer userdata
);
52 static void properties_apply(GtkButton
*button
, gpointer userdata
);
53 static void properties_cancel(GtkButton
*button
, gpointer userdata
);
55 /* Data for a single preference */
64 /* The type of a preference is the collection of callbacks needed to get,
65 * display and set it */
67 void (*kickoff
)(struct prefdata
*f
);
68 /* Kick off the request to fetch the pref from the server. */
70 void (*completed
)(struct prefdata
*f
);
71 /* Called when the value comes back in; creates the widget. */
73 const char *(*get_edited
)(struct prefdata
*f
);
74 /* Get the edited value from the widget. */
76 void (*set
)(struct prefdata
*f
, const char *value
);
77 /* Set the new value and (if necessary) arrange for our display to update. */
81 static const struct preftype preftype_namepart
= {
89 static const struct preftype preftype_string
= {
97 static const struct preftype preftype_boolean
= {
104 /* The known prefs for each track */
105 static const struct pref
{
108 const char *default_value
;
109 const struct preftype
*type
;
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
},
118 #define NPREFS (int)(sizeof prefs / sizeof *prefs)
120 /* Buttons that appear at the bottom of the window */
121 static const struct button
{
123 void (*clicked
)(GtkButton
*button
, gpointer userdata
);
125 { GTK_STOCK_OK
, properties_ok
},
126 { GTK_STOCK_APPLY
, properties_apply
},
127 { GTK_STOCK_CANCEL
, properties_cancel
},
130 #define NBUTTONS (int)(sizeof buttons / sizeof *buttons)
132 static int prefs_unfilled
; /* Prefs remaining to get */
133 static int prefs_total
; /* Total prefs */
134 static struct prefdata
*prefdatas
; /* Current prefdatas */
135 static GtkWidget
*properties_window
;
136 static GtkWidget
*properties_table
;
137 static GtkWidget
*progress_window
, *progress_bar
;
139 void properties(int ntracks
, char **tracks
) {
142 GtkWidget
*hbox
, *vbox
, *button
, *label
, *entry
;
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
) {
148 gtk_window_present(GTK_WINDOW(properties_window
));
151 assert(properties_table
== 0);
152 if(ntracks
> INT_MAX
/ NPREFS
) {
153 popup_error("Too many tracks selected");
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
),
175 (NPREFS
+ 1) * n
, (NPREFS
+ 1) * n
+ 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
),
184 (NPREFS
+ 1) * n
, (NPREFS
+ 1) * n
+ 1,
185 GTK_EXPAND
|GTK_FILL
, 0,
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
),
193 (NPREFS
+ 1) * n
+ 1 + m
, (NPREFS
+ 1) * n
+ 2 + m
,
194 GTK_FILL
/*xoptions*/, 0/*yoptions*/,
196 f
= &prefdatas
[NPREFS
* n
+ m
];
197 f
->track
= tracks
[n
];
198 f
->row
= (NPREFS
+ 1) * n
+ 1 + m
;
200 prefs
[m
].type
->kickoff(f
);
203 prefs_unfilled
= prefs_total
;
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);
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
,
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
),
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
);
236 /* Everything is filled in now */
237 static void prefdata_alldone(void) {
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
);
246 /* Namepart preferences ---------------------------------------------------- */
248 static void kickoff_namepart(struct prefdata
*f
) {
251 byte_xasprintf(&s
, "trackname_display_%s", f
->p
->part
);
252 disorder_eclient_get(client
, prefdata_completed
, f
->track
, s
,
253 make_callbackdata(f
));
256 static void completed_namepart(struct prefdata
*f
) {
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
);
264 static const char *get_edited_namepart(struct prefdata
*f
) {
265 return gtk_entry_get_text(GTK_ENTRY(f
->widget
));
268 static void set_namepart(struct prefdata
*f
, const char *value
) {
270 struct callbackdata
*cbd
= xmalloc(sizeof *cbd
);
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
,
279 /* Same as default, just unset */
280 disorder_eclient_unset(client
, set_namepart_completed
, f
->track
, s
, cbd
);
283 /* Called when we've set a namepart */
284 static void set_namepart_completed(void *v
) {
285 struct callbackdata
*cbd
= v
;
286 struct prefdata
*f
= cbd
->u
.f
;
288 namepart_update(f
->track
, "display", f
->p
->part
);
291 /* String preferences ------------------------------------------------------ */
293 static void kickoff_string(struct prefdata
*f
) {
294 disorder_eclient_get(client
, prefdata_completed
, f
->track
, f
->p
->part
,
295 make_callbackdata(f
));
298 static void completed_string(struct prefdata
*f
) {
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
);
306 static const char *get_edited_string(struct prefdata
*f
) {
307 return gtk_entry_get_text(GTK_ENTRY(f
->widget
));
310 static 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
,
316 /* Same as default, just unset */
317 disorder_eclient_unset(client
, 0/*completed*/, f
->track
, f
->p
->part
,
321 /* Boolean preferences ----------------------------------------------------- */
323 static void kickoff_boolean(struct prefdata
*f
) {
324 disorder_eclient_get(client
, prefdata_completed
, f
->track
, f
->p
->part
,
325 make_callbackdata(f
));
328 static void completed_boolean(struct prefdata
*f
) {
329 f
->widget
= gtk_check_button_new();
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"));
337 static const char *get_edited_boolean(struct prefdata
*f
) {
338 return (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(f
->widget
))
342 static void set_boolean(struct prefdata
*f
, const char *value
) {
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
,
350 /* If default value then delete the pref */
351 disorder_eclient_unset(client
, 0/*completed*/, f
->track
, f
->p
->part
,
355 /* Querying preferences ---------------------------------------------------- */
357 /* Make a suitable callbackdata */
358 static struct callbackdata
*make_callbackdata(struct prefdata
*f
) {
359 struct callbackdata
*cbd
= xmalloc(sizeof *cbd
);
361 cbd
->onerror
= prefdata_onerror
;
366 /* No pref was set */
367 static 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);
373 /* Got the value of a pref */
374 static void prefdata_completed(void *v
, const char *value
) {
375 struct callbackdata
*cbd
= v
;
377 prefdata_completed_common(cbd
->u
.f
, value
);
380 static void prefdata_completed_common(struct prefdata
*f
,
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
,
388 GTK_EXPAND
|GTK_FILL
/*xoptions*/, 0/*yoptions*/,
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
);
398 /* Button callbacks -------------------------------------------------------- */
400 static void properties_ok(GtkButton
*button
,
402 properties_apply(button
, userdata
);
403 properties_cancel(button
, userdata
);
406 static void properties_apply(GtkButton
attribute((unused
)) *button
,
407 gpointer
attribute((unused
)) userdata
) {
412 /* For each possible property we see if we've changed it and if so tell the
414 for(n
= 0; n
< prefs_total
; ++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
);
425 static void properties_cancel(GtkButton
attribute((unused
)) *button
,
426 gpointer
attribute((unused
)) userdata
) {
427 gtk_widget_destroy(properties_window
);