1 /*
2 | Copyright (C) 2002-2005 Jorg Schuler <jcsjcs at users sourceforge net>
3 | Part of the gtkpod project.
4 |
5 | URL: http://www.gtkpod.org/
6 | URL: http://gtkpod.sourceforge.net/
7 |
8 | This program is free software; you can redistribute it and/or modify
9 | it under the terms of the GNU General Public License as published by
10 | the Free Software Foundation; either version 2 of the License, or
11 | (at your option) any later version.
12 |
13 | This program is distributed in the hope that it will be useful,
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | GNU General Public License for more details.
17 |
18 | You should have received a copy of the GNU General Public License
19 | along with this program; if not, write to the Free Software
20 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 |
22 | iTunes and iPod are trademarks of Apple
23 |
24 | This product is not supported/written/published by Apple!
25 |
26 | $Id$
27 */
28
29 #ifdef HAVE_CONFIG_H
30 # include <config.h>
31 #endif
32
33 #include <gdk/gdkkeysyms.h>
34
35 #include "display_private.h"
36 #include "info.h"
37 #include "prefs.h"
38 #include "misc.h"
39 #include "misc_track.h"
40 #include "context_menus.h"
41 #include "date_parser.h"
42 #include "itdb.h"
43 #include <string.h>
44 #include <stdlib.h>
45
46 typedef struct {
47 GtkTreeView *tree_view;
48 guint32 inst;
49 } StSelectionEvent;
50
51 #define ST_AUTOSELECT(i) TRUE
52 /* #define ST_AUTOSELECT(i) (prefs_get_int_index("st_autoselect", (i))) */
53
54 /* array with pointers to the sort tabs */
55 static SortTab *sorttab[SORT_TAB_MAX];
56 /* pointer to paned elements holding the sort tabs */
57 static GtkPaned *st_paned[PANED_NUM_ST];
58 /* compare function to be used for string comparisons */
59
60 static void sp_store_sp_entries(gint inst);
61 static void st_page_selected(GtkNotebook *notebook, guint page);
62 static void st_create_notebook(gint inst);
63 static TimeInfo *sp_update_date_interval_from_string(guint32 inst, T_item item, gboolean force_update);
64
65 /* Drag and drop definitions */
66 static GtkTargetEntry
67 st_drag_types[] = { { DND_GTKPOD_TRACKLIST_TYPE, 0, DND_GTKPOD_TRACKLIST }, { "text/uri-list", 0, DND_TEXT_URI_LIST }, { "text/plain", 0, DND_TEXT_PLAIN }, { "STRING", 0, DND_TEXT_PLAIN } };
68
69 typedef enum {
70 IS_INSIDE, /* track's timestamp is inside the specified interval */
71 IS_OUTSIDE, /* track's timestamp is outside the specified interval */
72 IS_ERROR,
73 /* error parsing date string (or wrong parameters) */
74 } IntervalState;
75
76 GladeXML *cal_xml;
77
78 /* ---------------------------------------------------------------- */
79 /* */
80 /* Section for filter tab display (callback) */
81 /* */
82 /* ---------------------------------------------------------------- */
83
84 /*
85 * utility function for appending ipod track for st treeview callback
86 */
on_st_dnd_get_track_foreach(GtkTreeModel * tm,GtkTreePath * tp,GtkTreeIter * i,gpointer data)87 static void on_st_dnd_get_track_foreach(GtkTreeModel *tm, GtkTreePath *tp, GtkTreeIter *i, gpointer data) {
88 GList *gl;
89 TabEntry *entry = NULL;
90 GString *tracklist = (GString *) data;
91
92 g_return_if_fail (tracklist);
93
94 gtk_tree_model_get(tm, i, ST_COLUMN_ENTRY, &entry, -1);
95 g_return_if_fail (entry);
96
97 /* add all member tracks of entry to tracklist */
98 for (gl = entry->members; gl; gl = gl->next) {
99 Track *tr = gl->data;
100 g_return_if_fail (tr);
101 g_string_append_printf(tracklist, "%p\n", tr);
102 }
103 }
104
105 /*
106 * utility function for appending filenames for st treeview callback
107 */
on_st_dnd_get_file_foreach(GtkTreeModel * tm,GtkTreePath * tp,GtkTreeIter * iter,gpointer data)108 static void on_st_dnd_get_file_foreach(GtkTreeModel *tm, GtkTreePath *tp, GtkTreeIter *iter, gpointer data) {
109 GList *gl;
110 TabEntry *entry = NULL;
111 GString *filelist = data;
112
113 g_return_if_fail (tm);
114 g_return_if_fail (iter);
115 g_return_if_fail (data);
116
117 gtk_tree_model_get(tm, iter, ST_COLUMN_ENTRY, &entry, -1);
118 g_return_if_fail (entry);
119
120 /* add all member tracks of entry to tracklist */
121 for (gl = entry->members; gl; gl = gl->next) {
122 gchar *name;
123 Track *tr = gl->data;
124
125 g_return_if_fail (tr);
126 name = get_file_name_from_source(tr, SOURCE_PREFER_LOCAL);
127 if (name) {
128 g_string_append_printf(filelist, "file:%s\n", name);
129 g_free(name);
130 }
131 }
132 }
133
st_drag_end(GtkWidget * widget,GdkDragContext * dc,gpointer user_data)134 static void st_drag_end(GtkWidget *widget, GdkDragContext *dc, gpointer user_data) {
135 /* puts ("st_drag_end"); */
136 gtkpod_tracks_statusbar_update();
137 }
138
139 /*
140 * utility function for appending uris for st treeview callback
141 */
on_st_dnd_get_uri_foreach(GtkTreeModel * tm,GtkTreePath * tp,GtkTreeIter * iter,gpointer data)142 static void on_st_dnd_get_uri_foreach(GtkTreeModel *tm, GtkTreePath *tp, GtkTreeIter *iter, gpointer data) {
143 GList *gl;
144 TabEntry *entry = NULL;
145 GString *filelist = data;
146
147 g_return_if_fail (tm);
148 g_return_if_fail (iter);
149 g_return_if_fail (data);
150
151 gtk_tree_model_get(tm, iter, ST_COLUMN_ENTRY, &entry, -1);
152 g_return_if_fail (entry);
153
154 /* add all member tracks of entry to tracklist */
155 for (gl = entry->members; gl; gl = gl->next) {
156 gchar *name;
157 Track *tr = gl->data;
158
159 g_return_if_fail (tr);
160 name = get_file_name_from_source(tr, SOURCE_PREFER_LOCAL);
161 if (name) {
162 gchar *uri = g_filename_to_uri(name, NULL, NULL);
163 if (uri)
164 {
165 g_string_append_printf (filelist, "file:%s\n", name);
166 g_free (uri);
167 }
168 g_free (name);
169 }
170 }
171 }
172
st_drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * data,guint info,guint time,gpointer user_data)173 static void st_drag_data_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *data, guint info, guint time, gpointer user_data) {
174 GtkTreeSelection *ts = NULL;
175 GString *reply = g_string_sized_new(2000);
176
177 if (!data)
178 return;
179
180 ts = gtk_tree_view_get_selection(GTK_TREE_VIEW (widget));
181 if (ts) {
182 switch (info) {
183 case DND_GTKPOD_TRACKLIST:
184 gtk_tree_selection_selected_foreach(ts, on_st_dnd_get_track_foreach, reply);
185 break;
186 case DND_TEXT_URI_LIST:
187 gtk_tree_selection_selected_foreach(ts, on_st_dnd_get_uri_foreach, reply);
188 break;
189 case DND_TEXT_PLAIN:
190 gtk_tree_selection_selected_foreach(ts, on_st_dnd_get_file_foreach, reply);
191 break;
192 default:
193 g_warning ("Programming error: st_drag_data_get received unknown info type (%d)\n", info);
194 break;
195 }
196 }
197 gtk_selection_data_set(data, data->target, 8, reply->str, reply->len);
198 g_string_free(reply, TRUE);
199 }
200
201 /* delete selected entry in sort tab */
on_st_treeview_key_release_event(GtkWidget * widget,GdkEventKey * event,gpointer user_data)202 static gboolean on_st_treeview_key_release_event(GtkWidget *widget, GdkEventKey *event, gpointer user_data) {
203 guint mods;
204 mods = event->state;
205
206 if (!widgets_blocked && (mods & GDK_CONTROL_MASK)) {
207 switch (event->keyval) {
208 /* case GDK_u: */
209 /* gp_do_selected_entry (update_tracks, */
210 /* st_get_instance_from_treeview ( */
211 /* GTK_TREE_VIEW (widget))); */
212 /* break; */
213 default:
214 break;
215 }
216
217 }
218 return FALSE;
219 }
220
st_build_sortkeys(TabEntry * entry)221 static void st_build_sortkeys(TabEntry *entry) {
222 C_FREE (entry->name_sortkey);
223 C_FREE (entry->name_fuzzy_sortkey);
224 entry->name_sortkey = make_sortkey(entry->name);
225 if (entry->name != fuzzy_skip_prefix(entry->name)) {
226 entry->name_fuzzy_sortkey = make_sortkey(fuzzy_skip_prefix(entry->name));
227 }
228 }
229
st_rebuild_sortkeys()230 void st_rebuild_sortkeys() {
231 gint inst;
232 for (inst = 0; inst < prefs_get_int("sort_tab_num"); inst++) {
233 SortTab *st = sorttab[inst];
234 GList *entries;
235
236 for (entries = st->entries; entries; entries = g_list_next (entries)) {
237 TabEntry *entry = (TabEntry *) entries->data;
238 st_build_sortkeys(entry);
239 }
240 }
241 }
242
compare_entry(const TabEntry * a,const TabEntry * b)243 gint compare_entry(const TabEntry *a, const TabEntry *b) {
244 return strcmp(a->name_sortkey, b->name_sortkey);
245 }
246
compare_entry_fuzzy(const TabEntry * a,const TabEntry * b)247 gint compare_entry_fuzzy(const TabEntry *a, const TabEntry *b) {
248 const gchar *ka, *kb;
249 ka = a->name_fuzzy_sortkey ? a->name_fuzzy_sortkey : a->name_sortkey;
250 kb = b->name_fuzzy_sortkey ? b->name_fuzzy_sortkey : b->name_sortkey;
251 return strcmp(ka, kb);
252 }
253
254 /* set string compare function according to whether the ignore field
255 is set or not */
st_set_string_compare_func(guint inst,guint page_num)256 static void st_set_string_compare_func(guint inst, guint page_num) {
257 gchar *buf;
258 if (page_num != ST_CAT_SPECIAL) {
259 buf = g_strdup_printf("sort_ign_field_%d", ST_to_T(page_num));
260 if (prefs_get_int(buf))
261 sorttab[inst]->entry_compare_func = compare_entry_fuzzy;
262 else
263 sorttab[inst]->entry_compare_func = compare_entry;
264 g_free(buf);
265 }
266 }
267
268 /* callback */
on_st_switch_page(GtkNotebook * notebook,GtkNotebookPage * page,guint page_num,gpointer user_data)269 static void on_st_switch_page(GtkNotebook *notebook, GtkNotebookPage *page, guint page_num, gpointer user_data) {
270 guint inst = GPOINTER_TO_UINT( user_data );
271
272 /* printf ("switch_page: inst/page: %d/%d\n", inst, page_num); */
273 /* set compare function for strings (to speed up sorting) */
274 if (page_num != ST_CAT_SPECIAL) {
275 st_set_string_compare_func(inst, page_num);
276 }
277 space_data_update();
278 st_page_selected(notebook, page_num);
279 }
280
281 /* ---------------------------------------------------------------- */
282 /* */
283 /* Section for filter tab display (special sort tab) */
284 /* */
285 /* ---------------------------------------------------------------- */
286
287 /* Set rating for tab @inst and rating @n */
set_sp_rating_n(guint32 inst,gint n,gboolean state)288 static void set_sp_rating_n(guint32 inst, gint n, gboolean state) {
289 guint32 rating;
290
291 if ((inst <= SORT_TAB_MAX) && (n <= RATING_MAX)) {
292 rating = (guint32) prefs_get_int_index("sp_rating_state", inst);
293
294 if (state)
295 rating |= (1 << n);
296 else
297 rating &= ~(1 << n);
298
299 prefs_set_int_index("sp_rating_state", inst, rating);
300 }
301 }
302
get_sp_rating_n(guint32 inst,gint n)303 static gboolean get_sp_rating_n(guint32 inst, gint n) {
304 guint32 rating;
305
306 if ((inst <= SORT_TAB_MAX) && (n <= RATING_MAX)) {
307 rating = (guint32) prefs_get_int_index("sp_rating_state", inst);
308
309 if ((rating & (1 << n)) != 0)
310 return TRUE;
311 else
312 return FALSE;
313 }
314
315 return FALSE;
316 }
317
318 /* Remove all members of special sort tab (ST_CAT_SPECIAL) in instance
319 * @inst */
sp_remove_all_members(guint32 inst)320 static void sp_remove_all_members(guint32 inst) {
321 SortTab *st;
322
323 /* Sanity */
324 if (inst >= prefs_get_int("sort_tab_num"))
325 return;
326
327 st = sorttab[inst];
328
329 if (!st)
330 return;
331
332 g_list_free(st->sp_members);
333 st->sp_members = NULL;
334 g_list_free(st->sp_selected);
335 st->sp_selected = NULL;
336 }
337
338 /* Return a pointer to ti_added, ti_modified or ti_played. Returns
339 NULL if either inst or item are out of range */
sp_get_timeinfo_ptr(guint32 inst,T_item item)340 static TimeInfo *sp_get_timeinfo_ptr(guint32 inst, T_item item) {
341 if (inst >= SORT_TAB_MAX) {
342 fprintf(stderr, "Programming error: st_get_timeinfo_ptr: inst out of range: %d\n", inst);
343 }
344 else {
345 SortTab *st = sorttab[inst];
346 switch (item) {
347 case T_TIME_ADDED:
348 return &st->ti_added;
349 case T_TIME_PLAYED:
350 return &st->ti_played;
351 case T_TIME_MODIFIED:
352 return &st->ti_modified;
353 default:
354 fprintf(stderr, "Programming error: st_get_timeinfo_ptr: item invalid: %d\n", item);
355 }
356 }
357 return NULL;
358 }
359
360 /* Update the date interval from the string provided by
361 prefs_get_sp_entry() */
362 /* @inst: instance
363 @item: T_TIME_PLAYED, or T_TIME_MODIFIED,
364 @force_update: usually the update is only performed if the string
365 has changed. TRUE will re-evaluate the string (and print an error
366 message again, if necessary */
367 /* Return value: pointer to the corresponding TimeInfo struct (for
368 convenience) or NULL if error occurred */
sp_update_date_interval_from_string(guint32 inst,T_item item,gboolean force_update)369 static TimeInfo *sp_update_date_interval_from_string(guint32 inst, T_item item, gboolean force_update) {
370 SortTab *st;
371 TimeInfo *ti;
372
373 if (inst >= SORT_TAB_MAX)
374 return NULL;
375
376 st = sorttab[inst];
377 ti = sp_get_timeinfo_ptr(inst, item);
378
379 if (ti) {
380 gchar *new_string = NULL;
381 switch (item) {
382 case T_TIME_PLAYED:
383 new_string = prefs_get_string_index("sp_played_state", inst);
384 break;
385 case T_TIME_MODIFIED:
386 new_string = prefs_get_string_index("sp_modified_state", inst);
387 break;
388 case T_TIME_ADDED:
389 new_string = prefs_get_string_index("sp_added_state", inst);
390 ;
391 break;
392 default:
393 break;
394 }
395
396 if (!new_string) {
397 new_string = g_strdup("");
398 }
399
400 if (force_update || !ti->int_str || (strcmp(new_string, ti->int_str) != 0)) { /* Re-evaluate the interval */
401 g_free(ti->int_str);
402 ti->int_str = g_strdup(new_string);
403 dp2_parse(ti);
404 }
405 g_free(new_string);
406 }
407
408 return ti;
409 }
410
411 /* check if @track's timestamp is within the interval given for @item.
412 *
413 * Return value:
414 *
415 * IS_ERROR: error parsing date string (or wrong parameters)
416 * IS_INSIDE: track's timestamp is inside the specified interval
417 * IS_OUTSIDE: track's timestamp is outside the specified interval
418 */
sp_check_time(guint32 inst,T_item item,Track * track)419 static IntervalState sp_check_time(guint32 inst, T_item item, Track *track) {
420 TimeInfo *ti;
421 IntervalState result = IS_ERROR;
422
423 ti = sp_update_date_interval_from_string(inst, item, FALSE);
424 if (ti && ti->valid) {
425 guint32 stamp = track_get_timestamp(track, item);
426 if (stamp && (ti->lower <= stamp) && (stamp <= ti->upper))
427 result = IS_INSIDE;
428 else
429 result = IS_OUTSIDE;
430 }
431 if (result == IS_ERROR) {
432 switch (item) {
433 case T_TIME_PLAYED:
434 gtkpod_statusbar_message(_("'Played' condition ignored because of error."));
435 break;
436 case T_TIME_MODIFIED:
437 gtkpod_statusbar_message(_("'Modified' condition ignored because of error."));
438 break;
439 case T_TIME_ADDED:
440 gtkpod_statusbar_message(_("'Added' condition ignored because of error."));
441 break;
442 default:
443 break;
444 }
445 }
446 return result;
447 }
448
449 /* decide whether or not @track satisfies the conditions specified in
450 * the special sort tab of instance @inst.
451 * Return value: TRUE: satisfies, FALSE: does not satisfy */
sp_check_track(Track * track,guint32 inst)452 static gboolean sp_check_track(Track *track, guint32 inst) {
453 gboolean sp_or = prefs_get_int_index("sp_or", inst);
454 gboolean result, cond, checked = FALSE;
455
456 if (!track)
457 return FALSE;
458
459 /* Initial state depends on logical operation */
460 if (sp_or)
461 result = FALSE; /* OR */
462 else
463 result = TRUE; /* AND */
464
465 /* RATING */
466 if (prefs_get_int_index("sp_rating_cond", inst)) {
467 /* checked = TRUE: at least one condition was checked */
468 checked = TRUE;
469 cond = get_sp_rating_n(inst, track->rating / ITDB_RATING_STEP);
470 /* If one of the two combinations occur, we can take a
471 shortcut and stop checking the other conditions */
472 if (sp_or && cond)
473 return TRUE;
474 if ((!sp_or) && (!cond))
475 return FALSE;
476 /* We don't have to calculate a new 'result' value because for
477 the other two combinations it does not change */
478 }
479
480 /* PLAYCOUNT */
481 if (prefs_get_int_index("sp_playcount_cond", inst)) {
482 guint32 low = prefs_get_int_index("sp_playcount_low", inst);
483 /* "-1" will translate into about 4 billion because I use
484 guint32 instead of gint32. Since 4 billion means "no upper
485 limit" the logic works fine */
486 guint32 high = prefs_get_int_index("sp_playcount_high", inst);
487 checked = TRUE;
488 if ((low <= track->playcount) && (track->playcount <= high))
489 cond = TRUE;
490 else
491 cond = FALSE;
492 if (sp_or && cond)
493 return TRUE;
494 if ((!sp_or) && (!cond))
495 return FALSE;
496 }
497 /* time played */
498 if (prefs_get_int_index("sp_played_cond", inst)) {
499 IntervalState result = sp_check_time(inst, T_TIME_PLAYED, track);
500 if (sp_or && (result == IS_INSIDE))
501 return TRUE;
502 if ((!sp_or) && (result == IS_OUTSIDE))
503 return FALSE;
504 if (result != IS_ERROR)
505 checked = TRUE;
506 }
507 /* time modified */
508 if (prefs_get_int_index("sp_modified_cond", inst)) {
509 IntervalState result = sp_check_time(inst, T_TIME_MODIFIED, track);
510 if (sp_or && (result == IS_INSIDE))
511 return TRUE;
512 if ((!sp_or) && (result == IS_OUTSIDE))
513 return FALSE;
514 if (result != IS_ERROR)
515 checked = TRUE;
516 }
517 /* time added */
518 if (prefs_get_int_index("sp_added_cond", inst)) {
519 IntervalState result = sp_check_time(inst, T_TIME_ADDED, track);
520 if (sp_or && (result == IS_INSIDE))
521 return TRUE;
522 if ((!sp_or) && (result == IS_OUTSIDE))
523 return FALSE;
524 if (result != IS_ERROR)
525 checked = TRUE;
526 }
527
528 if (checked)
529 return result;
530 else
531 return FALSE;
532 }
533
534 /* called by st_add_track(): add a track to ST_CAT_SPECIAL */
st_add_track_special(Track * track,gboolean final,gboolean display,guint32 inst)535 static void st_add_track_special(Track *track, gboolean final, gboolean display, guint32 inst) {
536 SortTab *st;
537
538 /* Sanity */
539 if (inst >= prefs_get_int("sort_tab_num"))
540 return;
541
542 st = sorttab[inst];
543
544 /* Sanity */
545 if (!st)
546 return;
547
548 /* Sanity */
549 if (st->current_category != ST_CAT_SPECIAL)
550 return;
551
552 st->final = final;
553
554 if (track != NULL) {
555 /* Add track to member list */
556 st->sp_members = g_list_append(st->sp_members, track);
557 /* Check if track is to be passed on to next sort tab */
558 if (st->is_go || prefs_get_int_index("sp_autodisplay", inst)) { /* Check if track matches sort criteria to be displayed */
559 if (sp_check_track(track, inst)) {
560 st->sp_selected = g_list_append(st->sp_selected, track);
561 st_add_track(track, final, display, inst + 1);
562 }
563 }
564 }
565 if (!track && final) {
566 if (st->is_go || prefs_get_int_index("sp_autodisplay", inst))
567 st_add_track(NULL, final, display, inst + 1);
568
569 }
570 }
571
572 /* Callback for sp_go() */
sp_go_cb(gpointer user_data1,gpointer user_data2)573 static void sp_go_cb(gpointer user_data1, gpointer user_data2) {
574 guint32 inst = (guint32) GPOINTER_TO_UINT(user_data1);
575 SortTab *st = sorttab[inst];
576
577 #if DEBUG_TIMING
578 GTimeVal time;
579 g_get_current_time (&time);
580 printf ("sp_go_cb enter: %ld.%06ld sec\n",
581 time.tv_sec % 3600, time.tv_usec);
582 #endif
583
584 space_data_update();
585
586 /* Sanity */
587 if (st == NULL)
588 return;
589
590 /* remember that "Display" was already pressed */
591 st->is_go = TRUE;
592
593 /* Clear the sp_selected list */
594 g_list_free(st->sp_selected);
595 st->sp_selected = NULL;
596
597 /* initialize next instance */
598 st_init(-1, inst + 1);
599
600 if (st->sp_members) {
601 GList *gl;
602
603 st_enable_disable_view_sort(inst + 1, FALSE);
604 for (gl = st->sp_members; gl; gl = gl->next) { /* add all member tracks to next instance */
605 Track *track = (Track *) gl->data;
606 if (sp_check_track(track, inst)) {
607 st->sp_selected = g_list_append(st->sp_selected, track);
608 st_add_track(track, FALSE, TRUE, inst+1);
609 }
610 }
611 st_enable_disable_view_sort (inst+1, TRUE);
612 st_add_track (NULL, TRUE, st->final, inst+1);
613 }
614 gtkpod_tracks_statusbar_update();
615 #if DEBUG_TIMING
616 g_get_current_time (&time);
617 printf ("st_selection_changed_cb exit: %ld.%06ld sec\n",
618 time.tv_sec % 3600, time.tv_usec);
619 #endif
620 }
621
622 /* save the contents of the entry to prefs */
sp_store_sp_entries(gint inst)623 static void sp_store_sp_entries(gint inst) {
624 SortTab *st;
625
626 /* Sanity */
627 if (inst >= prefs_get_int("sort_tab_num"))
628 return;
629
630 st = sorttab[inst];
631
632 /* Sanity */
633 if (!st || (st->current_category != ST_CAT_SPECIAL))
634 return;
635
636 prefs_set_string_index("sp_played_state", inst, gtk_entry_get_text(GTK_ENTRY(st->ti_played.entry)));
637 prefs_set_string_index("sp_modified_state", inst, gtk_entry_get_text(GTK_ENTRY(st->ti_modified.entry)));
638 prefs_set_string_index("sp_added_state", inst, gtk_entry_get_text(GTK_ENTRY(st->ti_added.entry)));
639 }
640
641 /* display the members satisfying the conditions specified in the
642 * special sort tab of instance @inst */
sp_go(guint32 inst)643 void sp_go(guint32 inst) {
644 SortTab *st;
645
646 #if DEBUG_CB_INIT
647 printf ("st_go: inst: %d\n", inst);
648 #endif
649
650 /* Sanity */
651 if (inst >= prefs_get_int("sort_tab_num"))
652 return;
653
654 st = sorttab[inst];
655
656 /* Sanity */
657 if (st->current_category != ST_CAT_SPECIAL)
658 return;
659
660 /* Make sure the information typed into the entries is actually
661 * being used (maybe the user 'forgot' to press enter */
662 sp_store_sp_entries(inst);
663
664 sp_go_cb(GUINT_TO_POINTER(inst), NULL);
665 }
666
667 /* called by st_remove_track() */
st_remove_track_special(Track * track,guint32 inst)668 static void st_remove_track_special(Track *track, guint32 inst) {
669 SortTab *st;
670 GList *link;
671
672 /* Sanity */
673 if (inst >= prefs_get_int("sort_tab_num"))
674 return;
675
676 st = sorttab[inst];
677
678 /* Sanity */
679 if (st->current_category != ST_CAT_SPECIAL)
680 return;
681
682 /* Remove track from member list */
683 link = g_list_find(st->sp_members, track);
684 if (link) { /* only remove track from next sort tab if it was a member of
685 this sort tab (slight performance improvement when being
686 called with non-existing tracks */
687 st->sp_members = g_list_delete_link(st->sp_members, link);
688 st_remove_track(track, inst + 1);
689 }
690 }
691
692 /* called by st_track_changed */
st_track_changed_special(Track * track,gboolean removed,guint32 inst)693 static void st_track_changed_special(Track *track, gboolean removed, guint32 inst) {
694 SortTab *st;
695
696 /* Sanity */
697 if (inst >= prefs_get_int("sort_tab_num"))
698 return;
699
700 st = sorttab[inst];
701
702 /* Sanity */
703 if (st->current_category != ST_CAT_SPECIAL)
704 return;
705
706 if (g_list_find(st->sp_members, track)) { /* only do anything if @track was a member of this sort tab
707 (slight performance improvement when being called with
708 non-existing tracks */
709 if (removed) {
710 /* Remove track from member list */
711 st->sp_members = g_list_remove(st->sp_members, track);
712 if (g_list_find(st->sp_selected, track)) { /* only remove from next sort tab if it was passed on */
713 st->sp_selected = g_list_remove(st->sp_selected, track);
714 st_track_changed(track, TRUE, inst + 1);
715 }
716 }
717 else {
718 if (g_list_find(st->sp_selected, track)) { /* track is being passed on to next sort tab */
719 if (sp_check_track(track, inst)) { /* only changed */
720 st_track_changed(track, FALSE, inst + 1);
721 }
722 else { /* has to be removed */
723 st->sp_selected = g_list_remove(st->sp_selected, track);
724 st_track_changed(track, TRUE, inst + 1);
725 }
726 }
727 else { /* track is not being passed on to next sort tab */
728 if (sp_check_track(track, inst)) { /* add to next sort tab */
729 st->sp_selected = g_list_append(st->sp_selected, track);
730 st_add_track(track, TRUE, TRUE, inst+1);
731 }
732 }
733 }
734 }
735 }
736
737 /* Called when the user changed the sort conditions in the special
738 * sort tab */
sp_conditions_changed(guint32 inst)739 void sp_conditions_changed(guint32 inst) {
740 SortTab *st;
741
742 /* Sanity */
743 if (inst >= prefs_get_int("sort_tab_num"))
744 return;
745
746 st = sorttab[inst];
747 /* Sanity */
748 if (!st || st->current_category != ST_CAT_SPECIAL)
749 return;
750
751 /* Only redisplay if data is actually being passed on to the next
752 sort tab */
753 if (st->is_go || prefs_get_int_index("sp_autodisplay", inst)) {
754 st_redisplay(inst);
755 }
756 }
757
758 /* ---------------------------------------------------------------- */
759 /* */
760 /* Callbacks for special sort tab display */
761 /* */
762 /* ---------------------------------------------------------------- */
763
on_sp_or_button_toggled(GtkToggleButton * togglebutton,gpointer user_data)764 void on_sp_or_button_toggled(GtkToggleButton *togglebutton, gpointer user_data) {
765 guint32 inst = (guint32) (GPOINTER_TO_UINT(user_data)& SP_MASK);
766
767 prefs_set_int_index("sp_or", inst,
768 gtk_toggle_button_get_active (togglebutton));
769
770 sp_conditions_changed (inst);
771 }
772
on_sp_cond_button_toggled(GtkToggleButton * togglebutton,gpointer user_data)773 void on_sp_cond_button_toggled(GtkToggleButton *togglebutton, gpointer user_data) {
774 guint32 inst = (guint32) (GPOINTER_TO_UINT(user_data)& SP_MASK);
775 T_item cond = (guint32)GPOINTER_TO_UINT(user_data) >> SP_SHIFT;
776
777 switch (cond)
778 {
779 case T_RATING:
780 prefs_set_int_index("sp_rating_cond", inst,
781 gtk_toggle_button_get_active(togglebutton));
782 break;
783 case T_PLAYCOUNT:
784 prefs_set_int_index("sp_playcount_cond", inst,
785 gtk_toggle_button_get_active(togglebutton));
786 break;
787 case T_TIME_PLAYED:
788 prefs_set_int_index("sp_played_cond", inst,
789 gtk_toggle_button_get_active(togglebutton));
790 break;
791 case T_TIME_MODIFIED:
792 prefs_set_int_index("sp_modified_cond", inst,
793 gtk_toggle_button_get_active(togglebutton));
794 break;
795 case T_TIME_ADDED:
796 prefs_set_int_index("sp_added_cond", inst,
797 gtk_toggle_button_get_active(togglebutton));
798 break;
799 default:
800 break;
801 }
802 sp_conditions_changed (inst);
803 }
804
on_sp_rating_n_toggled(GtkToggleButton * togglebutton,gpointer user_data)805 void on_sp_rating_n_toggled(GtkToggleButton *togglebutton, gpointer user_data) {
806 guint32 inst = (guint32) (GPOINTER_TO_UINT(user_data)& SP_MASK);
807 guint32 n = (guint32)GPOINTER_TO_UINT(user_data) >> SP_SHIFT;
808
809 set_sp_rating_n (inst, n,
810 gtk_toggle_button_get_active (togglebutton));
811 if (prefs_get_int_index("sp_rating_cond", inst))
812 sp_conditions_changed (inst);
813 }
814
on_sp_entry_activate(GtkEditable * editable,gpointer user_data)815 void on_sp_entry_activate(GtkEditable *editable, gpointer user_data) {
816 guint32 inst = (guint32) (GPOINTER_TO_UINT(user_data)& SP_MASK);
817 T_item item = (guint32)GPOINTER_TO_UINT(user_data) >> SP_SHIFT;
818 gchar *buf = gtk_editable_get_chars(editable,0, -1);
819
820 /* printf ("sp_entry_activate inst: %d, item: %d\n", inst, item);*/
821
822 switch (item)
823 {
824 case T_TIME_PLAYED:
825 prefs_set_string_index("sp_played_state", inst, buf);
826 break;
827 case T_TIME_MODIFIED:
828 prefs_set_string_index("sp_modified_state", inst, buf);
829 break;
830 case T_TIME_ADDED:
831 prefs_set_string_index("sp_added_state", inst, buf);
832 break;
833 default:
834 break;
835 }
836
837 g_free (buf);
838 sp_update_date_interval_from_string (inst, item, TRUE);
839 sp_go (inst);
840 }
841
on_sp_cal_button_clicked(GtkButton * button,gpointer user_data)842 void on_sp_cal_button_clicked(GtkButton *button, gpointer user_data) {
843 guint32 inst = (guint32) GPOINTER_TO_UINT(user_data) & SP_MASK;
844 T_item item = (guint32) GPOINTER_TO_UINT(user_data) >> SP_SHIFT;
845
846 cal_open_calendar(inst, item);
847 }
848
on_sp_go_clicked(GtkButton * button,gpointer user_data)849 void on_sp_go_clicked(GtkButton *button, gpointer user_data) {
850 guint32 inst = (guint32) GPOINTER_TO_UINT(user_data) & SP_MASK;
851 sp_go(inst);
852 }
853
on_sp_go_always_toggled(GtkToggleButton * togglebutton,gpointer user_data)854 void on_sp_go_always_toggled(GtkToggleButton *togglebutton, gpointer user_data) {
855 guint32 inst = (guint32) GPOINTER_TO_UINT(user_data) & SP_MASK;
856 gboolean state = gtk_toggle_button_get_active(togglebutton);
857
858 /* display data if autodisplay is turned on */
859 if (state)
860 on_sp_go_clicked(NULL, user_data);
861 prefs_set_int_index("sp_autodisplay", inst, state);
862 }
863
on_sp_playcount_low_value_changed(GtkSpinButton * spinbutton,gpointer user_data)864 void on_sp_playcount_low_value_changed(GtkSpinButton *spinbutton, gpointer user_data) {
865 guint32 inst = (guint32) GPOINTER_TO_UINT(user_data) & SP_MASK;
866
867 prefs_set_int_index("sp_playcount_low", inst, gtk_spin_button_get_value(spinbutton));
868 if (prefs_get_int_index("sp_playcount_cond", inst))
869 sp_conditions_changed(inst);
870 }
871
on_sp_playcount_high_value_changed(GtkSpinButton * spinbutton,gpointer user_data)872 void on_sp_playcount_high_value_changed(GtkSpinButton *spinbutton, gpointer user_data) {
873 guint32 inst = (guint32) GPOINTER_TO_UINT(user_data) & SP_MASK;
874
875 prefs_set_int_index("sp_playcount_high", inst, gtk_spin_button_get_value(spinbutton));
876 if (prefs_get_int_index("sp_playcount_cond", inst))
877 sp_conditions_changed(inst);
878 }
879
880 /* ---------------------------------------------------------------- */
881 /* */
882 /* Section for sort tab display (normal and general) */
883 /* */
884 /* ---------------------------------------------------------------- */
885
886 /* return a pointer to the list of members selected in the sort tab
887 @inst. For a normal sort tab this is
888 sorttab[inst]->current_entry->members, for a special sort tab this
889 is sorttab[inst]->sp_selected.
890 You must not g_list_free() the returned list */
st_get_selected_members(guint32 inst)891 GList *st_get_selected_members(guint32 inst) {
892 SortTab *st;
893
894 /* Sanity */
895 if (inst >= prefs_get_int("sort_tab_num"))
896 return NULL;
897
898 st = sorttab[inst];
899
900 /* Sanity */
901 if (!st)
902 return NULL;
903
904 if (st->current_category != ST_CAT_SPECIAL) {
905 if (st->current_entry)
906 return st->current_entry->members;
907 else
908 return NULL;
909 }
910 else {
911 return st->sp_selected;
912 }
913 }
914
915 /* Get the instance of the sort tab that corresponds to
916 "notebook". Returns -1 if sort tab could not be found
917 and prints error message */
st_get_instance_from_notebook(GtkNotebook * notebook)918 static gint st_get_instance_from_notebook(GtkNotebook *notebook) {
919 gint i;
920
921 for (i = 0; i < SORT_TAB_MAX; ++i) {
922 if (sorttab[i] && (sorttab[i]->notebook == notebook))
923 return i;
924 }
925 /* g_warning ("Programming error (st_get_instance_from_notebook): notebook could
926 not be found.\n"); function somehow can get called after notebooks got
927 destroyed */
928 return -1;
929 }
930
931 /* Get the instance of the sort tab that corresponds to
932 "treeview". Returns -1 if sort tab could not be found
933 and prints error message */
st_get_instance_from_treeview(GtkTreeView * tv)934 gint st_get_instance_from_treeview(GtkTreeView *tv) {
935 gint i, cat;
936
937 for (i = 0; i < SORT_TAB_MAX; ++i) {
938 for (cat = 0; cat < ST_CAT_NUM; ++cat) {
939 if (sorttab[i] && (sorttab[i]->treeview[cat] == tv))
940 return i;
941 }
942 }
943 return -1;
944 }
945
946 /* returns the selected entry */
st_get_selected_entry(gint inst)947 TabEntry *st_get_selected_entry(gint inst) {
948 TabEntry *result = NULL;
949
950 if ((inst >= 0) && (inst < SORT_TAB_MAX) && sorttab[inst])
951 /* return sorttab[inst]->current_entry;*/
952 /* we can't just return the "->current_entry" because the context
953 menus require the selection before "->current_entry" is updated */
954 {
955 SortTab *st = sorttab[inst];
956 GtkTreeView *tv = st->treeview[st->current_category];
957 GtkTreeSelection *ts = gtk_tree_view_get_selection(tv);
958 GtkTreeIter iter;
959
960 if (gtk_tree_selection_get_selected(ts, NULL, &iter)) {
961 gtk_tree_model_get(st->model, &iter, ST_COLUMN_ENTRY, &result, -1);
962 }
963 }
964 /* wait until current_entry was updated */
965 if (result != sorttab[inst]->current_entry)
966 result = NULL;
967 return result;
968 }
969
970 /* Append playlist to the playlist model. */
st_add_entry(TabEntry * entry,guint32 inst)971 static void st_add_entry(TabEntry *entry, guint32 inst) {
972 GtkTreeIter iter;
973 GtkTreeModel *model;
974 SortTab *st;
975
976 st = sorttab[inst];
977 model = st->model;
978 g_return_if_fail (model != NULL);
979 /* Insert the compilation entry between All and the first entry
980 so it remains at the top even when the list is not sorted */
981 if (entry->compilation) {
982 gtk_list_store_insert(GTK_LIST_STORE (model), &iter, 1);
983 }
984 else {
985 gtk_list_store_append(GTK_LIST_STORE (model), &iter);
986 }
987 gtk_list_store_set(GTK_LIST_STORE (model), &iter, ST_COLUMN_ENTRY, entry, -1);
988 /* Prepend entry to the list, but always add after the master. */
989 st->entries = g_list_insert(st->entries, entry, 1);
990
991 if (!entry->master && !entry->compilation) {
992 if (!st->entry_hash) {
993 st->entry_hash = g_hash_table_new(g_str_hash, g_str_equal);
994 }
995 g_hash_table_insert(st->entry_hash, entry->name, entry);
996 }
997 }
998
999 /* Used by st_remove_entry_from_model() to remove entry from model by calling
1000 gtk_tree_model_foreach () */
st_delete_entry_from_model(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)1001 static gboolean st_delete_entry_from_model(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) {
1002 TabEntry *entry;
1003
1004 gtk_tree_model_get(model, iter, ST_COLUMN_ENTRY, &entry, -1);
1005 if (entry == (TabEntry *) data) {
1006 gtk_list_store_remove(GTK_LIST_STORE (model), iter);
1007 return TRUE;
1008 }
1009 return FALSE;
1010 }
1011
1012 /* Remove entry from the display model and the sorttab */
st_remove_entry_from_model(TabEntry * entry,guint32 inst)1013 static void st_remove_entry_from_model(TabEntry *entry, guint32 inst) {
1014 SortTab *st = sorttab[inst];
1015 GtkTreeModel *model = st->model;
1016 if (model && entry) {
1017 /* printf ("entry: %p, cur_entry: %p\n", entry, st->current_entry); */
1018 if (entry == st->current_entry) {
1019 GtkTreeSelection *selection = gtk_tree_view_get_selection(st->treeview[st->current_category]);
1020 st->current_entry = NULL;
1021 /* We have to unselect the previous selection */
1022 gtk_tree_selection_unselect_all(selection);
1023 }
1024 gtk_tree_model_foreach(model, st_delete_entry_from_model, entry);
1025 st->entries = g_list_remove(st->entries, entry);
1026 g_list_free(entry->members);
1027 /* remove entry from hash */
1028 if (st->entry_hash) {
1029 TabEntry *hashed_entry = (TabEntry *) g_hash_table_lookup(st->entry_hash, entry->name);
1030 if (hashed_entry == entry)
1031 g_hash_table_remove(st->entry_hash, entry->name);
1032 }
1033 g_free(entry->name);
1034 g_free(entry->name_sortkey);
1035 g_free(entry->name_fuzzy_sortkey);
1036 g_free(entry);
1037 }
1038 }
1039
st_free_entry_cb(gpointer data,gpointer user_data)1040 static void st_free_entry_cb(gpointer data, gpointer user_data) {
1041 TabEntry *entry = (TabEntry *) data;
1042 g_list_free(entry->members);
1043 }
1044
1045 /* Remove all entries from the display model and the sorttab */
1046 /* @clear_sort: reset sorted columns to the non-sorted state */
st_remove_all_entries_from_model(guint32 inst)1047 void st_remove_all_entries_from_model(guint32 inst) {
1048 SortTab *st = sorttab[inst];
1049 gint column;
1050 GtkSortType order;
1051
1052 if (st) {
1053 if (st->current_entry) {
1054 GtkTreeSelection *selection = gtk_tree_view_get_selection(st->treeview[st->current_category]);
1055 st->current_entry = NULL;
1056 /* We may have to unselect the previous selection */
1057 gtk_tree_selection_unselect_all(selection);
1058 }
1059 if (st->model) {
1060 gtk_list_store_clear(GTK_LIST_STORE (st->model));
1061 }
1062 g_list_foreach(st->entries, st_free_entry_cb, NULL);
1063 g_list_free(st->entries);
1064 st->entries = NULL;
1065 if (st->entry_hash)
1066 g_hash_table_destroy(st->entry_hash);
1067 st->entry_hash = NULL;
1068
1069 if ((prefs_get_int("st_sort") == SORT_NONE) && gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE (st->model),
1070 &column, &order)) { /* recreate track treeview to unset sorted column */
1071 if (column >= 0) {
1072 st_create_notebook(inst);
1073 }
1074 }
1075 }
1076 }
1077
1078 /* Remove "entry" from the model (used by delete_entry_ok()). The
1079 * entry should be empty (otherwise it's not removed).
1080 * If "entry" is the master entry 'All', the sort tab is redisplayed
1081 * (it's empty).
1082 * If the entry is currently selected (usually will be), the next
1083 * or previous entry will be selected automatically (unless it's the
1084 * master entry and prefs_get_int_index("st_autoselect", inst) says don't select
1085 * the 'All' entry). If no new entry is selected, the next sort tab will be
1086 * redisplayed (should be empty) */
st_remove_entry(TabEntry * entry,guint32 inst)1087 void st_remove_entry(TabEntry *entry, guint32 inst) {
1088 TabEntry *next = NULL;
1089 GtkTreeIter iter;
1090 GtkTreeSelection *selection;
1091 SortTab *st = sorttab[inst];
1092
1093 if (!entry)
1094 return;
1095 /* is the entry empty (contains no tracks)? */
1096 if (g_list_length(entry->members) != 0)
1097 return;
1098 /* if the entry is the master entry 'All' -> the tab is empty,
1099 re-init tab */
1100 if (entry->master) {
1101 st_init(-1, inst);
1102 return;
1103 }
1104
1105 /* is the entry currently selected? Remember! */
1106 selection = gtk_tree_view_get_selection(st->treeview[st->current_category]);
1107 #if 0 /* it doesn't make much sense to select the next entry, or? */
1108 if (sorttab[inst]->current_entry == entry)
1109 {
1110 gboolean valid;
1111 TabEntry *entry2=NULL;
1112 GtkTreeIter iter2;
1113 /* what's the next entry (displayed)? */
1114 if (gtk_tree_selection_get_selected (selection, NULL, &iter))
1115 { /* found selected entry -> now chose next one */
1116 if (gtk_tree_model_iter_next (st->model, &iter))
1117 {
1118 gtk_tree_model_get(st->model, &iter, ST_COLUMN_ENTRY, &next, -1);
1119 }
1120 else
1121 { /* no next entry, try to find previous one */
1122 /* There doesn't seem to be a ..._iter_previous()
1123 * call... */
1124 next = NULL;
1125 valid = gtk_tree_model_get_iter_first(st->model, &iter2);
1126 while(valid)
1127 {
1128 gtk_tree_model_get(st->model, &iter2, ST_COLUMN_ENTRY, &entry2, -1);
1129 if (entry == entry2) break; /* found it */
1130 iter = iter2;
1131 next = entry2;
1132 valid = gtk_tree_model_iter_next(st->model, &iter2);
1133 }
1134 if (!valid) next = NULL;
1135 }
1136 /* don't select master entry 'All' until requested to do so */
1137 if (next && next->master && !ST_AUTOSELECT (inst))
1138 next = NULL;
1139 }
1140 }
1141 #endif
1142 /* remove entry from display model */
1143 st_remove_entry_from_model(entry, inst);
1144 /* if we have a next entry, select it. */
1145 if (next) {
1146 gtk_tree_selection_select_iter(selection, &iter);
1147 }
1148 }
1149
1150 /* Get the correct name for the entry according to currently
1151 selected category (page). Do _not_ g_free() the return value! */
st_get_entryname(Track * track,guint32 inst)1152 static const gchar *st_get_entryname(Track *track, guint32 inst) {
1153 T_item t_item = ST_to_T(sorttab[inst]->current_category);
1154
1155 return track_get_item(track, t_item);
1156 }
1157
1158 /* Returns the entry "track" is stored in or NULL. The master entry
1159 "All" is skipped */
st_get_entry_by_track(Track * track,guint32 inst)1160 static TabEntry *st_get_entry_by_track(Track *track, guint32 inst) {
1161 GList *entries;
1162 TabEntry *entry;
1163 guint i;
1164
1165 if (track == NULL)
1166 return NULL;
1167 entries = sorttab[inst]->entries;
1168 i = 1; /* skip master entry, which is supposed to be at first position */
1169 while ((entry = (TabEntry *) g_list_nth_data(entries, i)) != NULL) {
1170 if (g_list_find(entry->members, track) != NULL)
1171 break; /* found! */
1172 ++i;
1173 }
1174 return entry;
1175 }
1176
1177 /* Find TabEntry with name "name". Return NULL if no entry was found.
1178 If "name" is {-1, 0x00}, it returns the master entry. Otherwise
1179 it skips the master entry. */
st_get_entry_by_name(const gchar * name,guint32 inst)1180 static TabEntry *st_get_entry_by_name(const gchar *name, guint32 inst) {
1181 TabEntry *entry = NULL;
1182 SortTab *st = sorttab[inst];
1183 GList *entries = st->entries;
1184
1185 if (name == NULL)
1186 return NULL;
1187 /* check if we need to return the master entry */
1188 if ((strlen(name) == 1) && (*name == -1)) {
1189 entry = (TabEntry *) g_list_nth_data(entries, 0);
1190 }
1191 else {
1192 if (st->entry_hash)
1193 entry = g_hash_table_lookup(st->entry_hash, name);
1194 }
1195 return entry;
1196 }
1197
1198 /* Find TabEntry with compilation set true. Return NULL if no entry was found. */
st_get_compilation_entry(guint32 inst)1199 static TabEntry *st_get_compilation_entry(guint32 inst) {
1200 GList *entries;
1201 TabEntry *entry;
1202 guint i;
1203
1204 entries = sorttab[inst]->entries;
1205 i = 1; /* skip master entry, which is supposed to be at first position */
1206 while ((entry = (TabEntry *) g_list_nth_data(entries, i)) != NULL) {
1207 if (entry->compilation)
1208 break; /* found! */
1209 ++i;
1210 }
1211 return entry;
1212 }
1213
1214 /* moves a track from the entry it is currently in to the one it
1215 should be in according to its tags (if a Tag had been changed).
1216 Returns TRUE, if track has been moved, FALSE otherwise */
1217 /* 07 Feb 2003: I decided that recategorizing is a bad thing: the
1218 current code only moves the tracks "up" in the entry list, so it's
1219 incomplete. More important: it leaves the display in an
1220 inconsistent state: the tracks are not removed from the
1221 trackview (this would confuse the user). But if he changes the entry
1222 again, nothing happens to the tracks displayed, because they are no
1223 longer members. Merging the two identical entries is no option
1224 either, because that takes away the possibility to easily "undo"
1225 what you have just done. It's also not intuitive if you have
1226 additional tracks appear on the screen. JCS */
st_recategorize_track(Track * track,guint32 inst)1227 static gboolean st_recategorize_track(Track *track, guint32 inst) {
1228 #if 0
1229 TabEntry *oldentry, *newentry;
1230 gchar *entryname;
1231
1232 oldentry = st_get_entry_by_track (track, inst);
1233 /* printf("%d: recat_oldentry: %x\n", inst, oldentry);*/
1234 /* should not happen: track is not in sort tab */
1235 if (oldentry == NULL) return FALSE;
1236 entryname = st_get_entryname (track, inst);
1237 newentry = st_get_entry_by_name (entryname, inst);
1238 if (newentry == NULL)
1239 { /* not found, create new one */
1240 newentry = g_malloc0 (sizeof (TabEntry));
1241 newentry->name = g_strdup (entryname);
1242 newentry->master = FALSE;
1243 newentry->compilation = FALSE;
1244 st_add_entry (newentry, inst);
1245 }
1246 if (newentry != oldentry)
1247 { /* track category changed */
1248 /* add track to entry members list */
1249 newentry->members = g_list_append (newentry->members, track);
1250 /* remove track from old entry members list */
1251 oldentry->members = g_list_remove (oldentry->members, track);
1252 /* printf("%d: recat_return_TRUE\n", inst);*/
1253 return TRUE;
1254 }
1255 /* printf("%d: recat_return_FALSE\n", inst);*/
1256 #endif
1257 return FALSE;
1258 }
1259
1260 /* called by st_track_changed */
st_track_changed_normal(Track * track,gboolean removed,guint32 inst)1261 static void st_track_changed_normal(Track *track, gboolean removed, guint32 inst) {
1262 SortTab *st;
1263 TabEntry *master, *entry;
1264
1265 st = sorttab[inst];
1266 master = g_list_nth_data(st->entries, 0);
1267 if (master == NULL)
1268 return; /* should not happen */
1269 /* if track is not in tab, don't proceed (should not happen) */
1270 if (g_list_find(master->members, track) == NULL)
1271 return;
1272 if (removed) {
1273 /* remove "track" from master entry "All" */
1274 master->members = g_list_remove(master->members, track);
1275 /* find entry which other entry contains the track... */
1276 entry = st_get_entry_by_track(track, inst);
1277 /* ...and remove it */
1278 if (entry)
1279 entry->members = g_list_remove(entry->members, track);
1280 if ((st->current_entry == entry) || (st->current_entry == master))
1281 st_track_changed(track, TRUE, inst + 1);
1282 }
1283 else {
1284 if (st->current_entry && g_list_find(st->current_entry->members, track) != NULL) { /* "track" is in currently selected entry */
1285 if (!st->current_entry->master) { /* it's not the master list */
1286 if (st_recategorize_track(track, inst))
1287 st_track_changed(track, TRUE, inst + 1);
1288 else
1289 st_track_changed(track, FALSE, inst + 1);
1290 }
1291 else { /* master entry ("All") is currently selected */
1292 st_recategorize_track(track, inst);
1293 st_track_changed(track, FALSE, inst + 1);
1294 }
1295 }
1296 else { /* "track" is not in an entry currently selected */
1297 if (st_recategorize_track(track, inst)) { /* track was moved to a different entry */
1298 if (st_get_entry_by_track(track, inst) == st->current_entry) { /* this entry is selected! */
1299 st_add_track(track, TRUE, TRUE, inst+1);
1300 }
1301 }
1302 }
1303 }
1304 }
1305
1306 /* Some tags of a track currently stored in a sort tab have been changed.
1307 - if not "removed"
1308 - if the track is in the entry currently selected:
1309 - remove entry and put into correct category
1310 - if current entry != "All":
1311 - if sort category changed:
1312 - notify next sort tab ("removed")
1313 - if sort category did not change:
1314 - notify next sort tab ("not removed")
1315 - if current entry == "All":
1316 - notify next sort tab ("not removed")
1317 - if the track is not in the entry currently selected (I don't know
1318 how that could happen, though):
1319 - if sort category changed: remove entry and put into correct category
1320 - if this "correct" category is selected, call st_add_track for next
1321 instance.
1322 - if "removed"
1323 - remove the track from the sort tab
1324 - if track was in the entry currently selected, notify next instance
1325 ("removed")
1326 "removed": track has been removed from sort tab. This is different
1327 from st_remove_track, because we will not notify the track model if a
1328 track has been removed: it might confuse the user if the track, whose
1329 tabs he/she just edited, disappeared from the display */
st_track_changed(Track * track,gboolean removed,guint32 inst)1330 void st_track_changed(Track *track, gboolean removed, guint32 inst) {
1331 if (inst == prefs_get_int("sort_tab_num")) {
1332 tm_track_changed(track);
1333 return;
1334 }
1335 else if (inst < prefs_get_int("sort_tab_num")) {
1336 switch (sorttab[inst]->current_category) {
1337 case ST_CAT_ARTIST:
1338 case ST_CAT_ALBUM:
1339 case ST_CAT_GENRE:
1340 case ST_CAT_COMPOSER:
1341 case ST_CAT_TITLE:
1342 case ST_CAT_YEAR:
1343 st_track_changed_normal(track, removed, inst);
1344 break;
1345 case ST_CAT_SPECIAL:
1346 st_track_changed_special(track, removed, inst);
1347 break;
1348 default:
1349 g_return_if_reached();
1350 }
1351 }
1352 }
1353
1354 /* Reorders the tracks stored in the sort tabs according to the order
1355 * in the selected playlist. This has to be done e.g. if we change the
1356 * order in the track view.
1357 *
1358 * Right now I simply delete all members of all tab entries, then add
1359 * the tracks again without having them added to the track view. This
1360 * is very fast because neither the sort tab display nor the track
1361 * view display is affected in any way. For my 2459 tracks that takes
1362 * approx. 1.3 seconds (850 MHz AMD Duron) */
st_adopt_order_in_playlist(void)1363 void st_adopt_order_in_playlist(void) {
1364 gint inst;
1365 Playlist *current_playlist = pm_get_selected_playlist();
1366
1367 #if DEBUG_TIMING
1368 GTimeVal time;
1369 g_get_current_time (&time);
1370 printf ("st_adopt_order_in_playlist enter: %ld.%06ld sec\n",
1371 time.tv_sec % 3600, time.tv_usec);
1372 #endif
1373
1374 /* first delete all tracks in all visible sort tabs */
1375 for (inst = 0; inst < prefs_get_int("sort_tab_num"); ++inst) {
1376 SortTab *st = sorttab[inst];
1377 GList *link;
1378 for (link = st->entries; link; link = link->next) { /* in each entry delete all tracks */
1379 TabEntry *entry = (TabEntry *) link->data;
1380 g_list_free(entry->members);
1381 entry->members = NULL;
1382 }
1383 }
1384
1385 /* now add the tracks again, without adding them to the track view */
1386 if (current_playlist) {
1387 GList *link;
1388
1389 for (link = current_playlist->members; link; link = link->next) {
1390 st_add_track((Track *) link->data, FALSE, FALSE, 0);
1391 }
1392 }
1393 #if DEBUG_TIMING
1394 g_get_current_time (&time);
1395 printf ("st_adopt_order_in_playlist enter: %ld.%06ld sec\n",
1396 time.tv_sec % 3600, time.tv_usec);
1397 #endif
1398 }
1399
1400 /* called by st_add_track() */
st_add_track_normal(Track * track,gboolean final,gboolean display,guint32 inst)1401 static void st_add_track_normal(Track *track, gboolean final, gboolean display, guint32 inst) {
1402 SortTab *st;
1403 TabEntry *entry, *master_entry, *iter_entry;
1404 const gchar *entryname = NULL;
1405 GtkTreeSelection *selection;
1406 GtkTreeIter iter;
1407 TabEntry *select_entry = NULL;
1408 gboolean first = FALSE;
1409 gboolean group_track = FALSE;
1410
1411 st = sorttab[inst];
1412 st->final = final;
1413
1414 /* if (track) printf ("%d: add track: %s\n", inst, track->title); */
1415 /* else printf ("%d: add track: %p\n", inst, track); */
1416
1417 if (track != NULL) {
1418 /* add track to "All" (master) entry */
1419 master_entry = g_list_nth_data(st->entries, 0);
1420 if (master_entry == NULL) { /* doesn't exist yet -- let's create it */
1421 master_entry = g_malloc0(sizeof(TabEntry));
1422 master_entry->name = g_strdup(_("All"));
1423 st_build_sortkeys(master_entry);
1424 master_entry->master = TRUE;
1425 master_entry->compilation = FALSE;
1426 st_add_entry(master_entry, inst);
1427 first = TRUE; /* this is the first track */
1428 }
1429 master_entry->members = g_list_prepend(master_entry->members, track);
1430 /* Check if this track should go in the compilation artist group */
1431 group_track = (prefs_get_int("group_compilations") && (track->compilation == TRUE) && (st->current_category
1432 == ST_CAT_ARTIST));
1433 /* Check whether entry of same name already exists */
1434 if (group_track) {
1435 entry = st_get_compilation_entry(inst);
1436 }
1437 else {
1438 entryname = st_get_entryname(track, inst);
1439 entry = st_get_entry_by_name(entryname, inst);
1440 }
1441 if (entry == NULL) { /* not found, create new one */
1442 entry = g_malloc0(sizeof(TabEntry));
1443 if (group_track) {
1444 entry->name = g_strdup(_("Compilations"));
1445 }
1446 else {
1447 entry->name = g_strdup(entryname);
1448 }
1449 st_build_sortkeys(entry);
1450 entry->compilation = group_track;
1451 entry->master = FALSE;
1452 st_add_entry(entry, inst);
1453 }
1454 /* add track to entry members list */
1455 entry->members = g_list_prepend(entry->members, track);
1456 /* add track to next tab if "entry" is selected */
1457 if (st->current_entry && ((st->current_entry->master) || (entry == st->current_entry))) {
1458 st_add_track(track, final, display, inst + 1);
1459 }
1460 /* check if we should select some entry */
1461 if (!st->current_entry) {
1462 if (st->lastselection[st->current_category] == NULL) {
1463 /* no last selection -- check if we should select "All" */
1464 /* only select "All" when currently adding the first track */
1465 if (first && ST_AUTOSELECT (inst)) {
1466 select_entry = master_entry;
1467 }
1468 }
1469 else {
1470 /* select current entry if it corresponds to the last
1471 selection, or last_entry if that's the master entry */
1472 TabEntry *last_entry = st_get_entry_by_name(st->lastselection[st->current_category], inst);
1473 if (last_entry && ((entry == last_entry) || last_entry->master)) {
1474 select_entry = last_entry;
1475 }
1476 }
1477 }
1478 }
1479 /* select "All" if it's the last track added, no entry currently
1480 selected (including "select_entry", which is to be selected" and
1481 prefs_get_int_index("st_autoselect", index) allows us to select "All" */
1482 if (final && !st->current_entry && !select_entry && !st->unselected && ST_AUTOSELECT (inst)) { /* auto-select entry "All" */
1483 select_entry = g_list_nth_data(st->entries, 0);
1484 }
1485
1486 if (select_entry) { /* select current select_entry */
1487 /* printf("%d: selecting: %p: %s\n", inst, select_entry, select_entry->name); */
1488 if (!gtk_tree_model_get_iter_first(st->model, &iter)) {
1489 g_warning ("Programming error: st_add_track: iter invalid\n");
1490 return;
1491 }
1492 do {
1493 gtk_tree_model_get(st->model, &iter, ST_COLUMN_ENTRY, &iter_entry, -1);
1494 if (iter_entry == select_entry) {
1495 selection = gtk_tree_view_get_selection(st->treeview[st->current_category]);
1496 /* We may need to unselect the previous selection */
1497 /* gtk_tree_selection_unselect_all (selection); */
1498 st->current_entry = select_entry;
1499 gtk_tree_selection_select_iter(selection, &iter);
1500 break;
1501 }
1502 }
1503 while (gtk_tree_model_iter_next(st->model, &iter));
1504 }
1505 else if (!track && final) {
1506 st_add_track(NULL, final, display, inst + 1);
1507 }
1508 }
1509
1510 /* Add track to sort tab. If the track matches the currently
1511 selected sort criteria, it will be passed on to the next
1512 sort tab. The last sort tab will pass the track on to the
1513 track model (currently two sort tabs).
1514 When the first track is added, the "All" entry is created.
1515 If prefs_get_int_index("st_autoselect", inst) is true, the "All" entry is
1516 automatically selected, if there was no former selection
1517 @display: TRUE: add to track model (i.e. display it) */
st_add_track(Track * track,gboolean final,gboolean display,guint32 inst)1518 void st_add_track(Track *track, gboolean final, gboolean display, guint32 inst) {
1519 #if DEBUG_ADD_TRACK
1520 printf ("st_add_track: inst: %d, final: %d, display: %d, track: %p\n",
1521 inst, final, display, track);
1522 #endif
1523
1524 if (inst == prefs_get_int("sort_tab_num")) { /* just add to track model */
1525 if ((track != NULL) && display)
1526 tm_add_track_to_track_model(track, NULL);
1527 if (final)
1528 gtkpod_tracks_statusbar_update();
1529 }
1530 else if (inst < prefs_get_int("sort_tab_num")) {
1531 switch (sorttab[inst]->current_category) {
1532 case ST_CAT_ARTIST:
1533 case ST_CAT_ALBUM:
1534 case ST_CAT_GENRE:
1535 case ST_CAT_COMPOSER:
1536 case ST_CAT_TITLE:
1537 case ST_CAT_YEAR:
1538 st_add_track_normal(track, final, display, inst);
1539 break;
1540 case ST_CAT_SPECIAL:
1541 st_add_track_special(track, final, display, inst);
1542 break;
1543 default:
1544 g_return_if_reached();
1545 }
1546 }
1547 }
1548
1549 /* called by st_remove_track() */
st_remove_track_normal(Track * track,guint32 inst)1550 static void st_remove_track_normal(Track *track, guint32 inst) {
1551 TabEntry *master, *entry;
1552 SortTab *st = sorttab[inst];
1553
1554 master = g_list_nth_data(st->entries, 0);
1555 if (master == NULL)
1556 return; /* should not happen! */
1557 /* remove "track" from master entry "All" */
1558 master->members = g_list_remove(master->members, track);
1559 /* find entry which other entry contains the track... */
1560 entry = st_get_entry_by_track(track, inst);
1561 /* ...and remove it */
1562 if (entry)
1563 entry->members = g_list_remove(entry->members, track);
1564 st_remove_track(track, inst + 1);
1565 }
1566
1567 /* Remove track from sort tab. If the track matches the currently
1568 selected sort criteria, it will be passed on to the next
1569 sort tab (i.e. removed).
1570 The last sort tab will remove the
1571 track from the track model (currently two sort tabs). */
1572 /* 02. Feb 2003: bugfix: track is always passed on to the next sort
1573 * tab: it might have been recategorized, but still be displayed. JCS */
st_remove_track(Track * track,guint32 inst)1574 void st_remove_track(Track *track, guint32 inst) {
1575 if (inst == prefs_get_int("sort_tab_num")) {
1576 tm_remove_track(track);
1577 }
1578 else if (inst < prefs_get_int("sort_tab_num")) {
1579 switch (sorttab[inst]->current_category) {
1580 case ST_CAT_ARTIST:
1581 case ST_CAT_ALBUM:
1582 case ST_CAT_GENRE:
1583 case ST_CAT_COMPOSER:
1584 case ST_CAT_TITLE:
1585 case ST_CAT_YEAR:
1586 st_remove_track_normal(track, inst);
1587 break;
1588 case ST_CAT_SPECIAL:
1589 st_remove_track_special(track, inst);
1590 break;
1591 default:
1592 g_return_if_reached();
1593 }
1594 }
1595 }
1596
1597 /* Init a sort tab: all current entries are removed. The next sort tab
1598 is initialized as well (st_init (-1, inst+1)). Set new_category to
1599 -1 if the current category is to be left unchanged */
1600 /* Normally we do not specifically remember the "All" entry and will
1601 select "All" in accordance to the prefs settings. */
st_init(ST_CAT_item new_category,guint32 inst)1602 void st_init(ST_CAT_item new_category, guint32 inst) {
1603 if (inst == prefs_get_int("sort_tab_num")) {
1604 tm_remove_all_tracks();
1605 gtkpod_tracks_statusbar_update();
1606 return;
1607 }
1608 if (inst < prefs_get_int("sort_tab_num")) {
1609 SortTab *st = sorttab[inst];
1610
1611 if (st == NULL)
1612 return; /* could happen during initialisation */
1613 sp_store_sp_entries(inst); /* store sp entries (if applicable) */
1614 st->unselected = FALSE; /* nothing was unselected so far */
1615 st->final = TRUE; /* all tracks are added */
1616 st->is_go = FALSE; /* did not press "Display" yet (special) */
1617 #if 0
1618 if (st->current_entry != NULL)
1619 {
1620 ST_CAT_item cat = st->current_category;
1621 if (!st->current_entry->master)
1622 {
1623 C_FREE (st->lastselection[cat]);
1624 st->lastselection[cat] = g_strdup (st->current_entry->name);
1625 }
1626 /* don't remember entry 'All' */
1627 #if 0
1628 else
1629 {
1630 gchar buf[] = {-1, 0}; /* this is how I mark the "All"
1631 * entry as string: should be
1632 * illegal UTF8 */
1633 C_FREE (st->lastselection[cat]);
1634 st->lastselection[cat] = g_strdup (buf);*/
1635 }
1636 #endif
1637 }
1638 #endif
1639 switch (sorttab[inst]->current_category) {
1640 case ST_CAT_ARTIST:
1641 case ST_CAT_ALBUM:
1642 case ST_CAT_GENRE:
1643 case ST_CAT_COMPOSER:
1644 case ST_CAT_TITLE:
1645 case ST_CAT_YEAR:
1646 st_remove_all_entries_from_model(inst);
1647 break;
1648 case ST_CAT_SPECIAL:
1649 sp_remove_all_members(inst);
1650 break;
1651 default:
1652 g_return_if_reached();
1653 }
1654 if (new_category != -1) {
1655 st->current_category = new_category;
1656 prefs_set_int_index("st_category", inst, new_category);
1657 }
1658 st_init(-1, inst + 1);
1659 }
1660 }
1661
st_page_selected_cb(gpointer data)1662 static gboolean st_page_selected_cb(gpointer data) {
1663 GtkNotebook *notebook = GTK_NOTEBOOK (data);
1664 guint page;
1665 guint32 inst;
1666 guint oldpage;
1667 gboolean is_go;
1668 GList *copy = NULL;
1669 SortTab *st;
1670
1671 #if DEBUG_TIMING
1672 GTimeVal time;
1673 g_get_current_time (&time);
1674 printf ("st_page_selected_cb enter (inst: %d, page: %d): %ld.%06ld sec\n",
1675 inst, page,
1676 time.tv_sec % 3600, time.tv_usec);
1677 #endif
1678
1679 inst = st_get_instance_from_notebook(notebook);
1680 if (inst == -1)
1681 return FALSE; /* invalid notebook */
1682
1683 page = gtk_notebook_get_current_page(notebook);
1684 st = sorttab[inst];
1685 /* remember old is_go state and current page */
1686 is_go = st->is_go;
1687 oldpage = st->current_category;
1688 /* re-initialize current instance */
1689 st_init(page, inst);
1690 /* write back old is_go state if page hasn't changed (redisplay) */
1691 if (page == oldpage)
1692 st->is_go = is_go;
1693 /* Get list of tracks to re-insert */
1694 copy = display_get_selected_members(inst - 1);
1695 if (copy) {
1696 GList *gl;
1697 gboolean final;
1698 /* add all tracks previously present to sort tab */
1699 st_enable_disable_view_sort(inst, FALSE);
1700 for (gl = copy; gl; gl = gl->next) {
1701 Track *track = gl->data;
1702 st_add_track(track, FALSE, TRUE, inst);
1703 }
1704 st_enable_disable_view_sort (inst, TRUE);
1705 final = TRUE; /* playlist is always complete */
1706 /* if playlist is not source, get final flag from
1707 * corresponding sorttab */
1708 if ((inst> 0) && (sorttab[inst-1])) final = sorttab[inst-1]->final;
1709 st_add_track (NULL, final, TRUE, inst);
1710 }
1711 #if DEBUG_TIMING
1712 g_get_current_time (&time);
1713 printf ("st_page_selected_cb exit (inst: %d, page: %d): %ld.%06ld sec\n",
1714 inst, page,
1715 time.tv_sec % 3600, time.tv_usec);
1716 #endif
1717
1718 return FALSE;
1719 }
1720
1721 /* Called when page in sort tab is selected */
st_page_selected(GtkNotebook * notebook,guint page)1722 static void st_page_selected(GtkNotebook *notebook, guint page) {
1723 guint32 inst;
1724
1725 inst = st_get_instance_from_notebook(notebook);
1726 #if DEBUG_CB_INIT
1727 printf ("st_page_selected: inst: %d, page: %d\n", inst, page);
1728 #endif
1729 if (inst == -1)
1730 return; /* invalid notebook */
1731 /* inst-1: changing a page in the first sort tab is like selecting a
1732 new playlist and so on. Therefore we subtract 1 from the
1733 instance. */
1734
1735 g_idle_add(st_page_selected_cb, notebook);
1736 }
1737
1738 /* Redisplay the sort tab "inst" */
st_redisplay(guint32 inst)1739 void st_redisplay(guint32 inst) {
1740 if (inst < prefs_get_int("sort_tab_num")) {
1741 if (sorttab[inst])
1742 st_page_selected(sorttab[inst]->notebook, sorttab[inst]->current_category);
1743 }
1744 }
1745
1746 /* Start sorting */
st_sort_inst(guint32 inst,GtkSortType order)1747 static void st_sort_inst(guint32 inst, GtkSortType order) {
1748 if (inst < prefs_get_int("sort_tab_num")) {
1749 SortTab *st = sorttab[inst];
1750 if (st) {
1751 switch (st->current_category) {
1752 case ST_CAT_ARTIST:
1753 case ST_CAT_ALBUM:
1754 case ST_CAT_GENRE:
1755 case ST_CAT_COMPOSER:
1756 case ST_CAT_TITLE:
1757 case ST_CAT_YEAR:
1758 if (order != SORT_NONE)
1759 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE (st->model), ST_COLUMN_ENTRY, order);
1760 else if (inst == 0) { /* we only redisplay for st0 because the others
1761 are reinitialized automatically */
1762 /* and we only redisplay if the tree is actually
1763 sorted */
1764 gint column;
1765 GtkSortType order;
1766 if (gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE (st->model), &column, &order))
1767 st_redisplay(0);
1768 }
1769 break;
1770 case ST_CAT_SPECIAL:
1771 break;
1772 default:
1773 g_return_if_reached();
1774 }
1775 }
1776 }
1777 }
1778
st_sort(GtkSortType order)1779 void st_sort(GtkSortType order) {
1780 gint i;
1781 for (i = 0; i < prefs_get_int("sort_tab_num"); ++i)
1782 st_sort_inst(i, order);
1783
1784 /* Reset the cover images. Unfortunately the track order is not maintained when the
1785 * display list of tracks is created. Thus, if the desired track order is the original order
1786 * then unfortunately the tracks must be collected from the playlist once again and the
1787 * displaytracks list in coverart recreated.
1788 * ie. easy to sort ascending and descending but difficult to return to unsorted state
1789 */
1790 coverart_display_update(order == SORT_NONE);
1791 }
1792
st_get_sorttab_page_number(int inst)1793 gint st_get_sorttab_page_number(int inst) {
1794 if (sorttab[inst])
1795 return gtk_notebook_get_current_page(sorttab[inst]->notebook);
1796 else
1797 return -1;
1798 }
1799
st_set_sorttab_page(gint inst,gint category)1800 void st_set_sorttab_page(gint inst, gint category) {
1801 if (sorttab[inst]) {
1802 int current = gtk_notebook_get_current_page(sorttab[inst]->notebook);
1803 if (current == category)
1804 return; // nothing to do
1805
1806 gtk_notebook_set_current_page(sorttab[inst]->notebook, category);
1807 st_page_selected(sorttab[inst]->notebook, category);
1808 }
1809 }
1810
st_set_selection(Itdb_Track * track)1811 gboolean st_set_selection(Itdb_Track *track) {
1812 GtkTreeSelection *selection;
1813 GtkTreeView *treeview;
1814 GtkTreeModel *model;
1815 GtkTreeIter iter;
1816 TabEntry *entry = NULL;
1817 gboolean status;
1818
1819 gtk_notebook_set_current_page(sorttab[0]->notebook, ST_CAT_ARTIST);
1820 st_page_selected(sorttab[0]->notebook, ST_CAT_ARTIST);
1821
1822 /* ######## Select the artist from the first sorttab ######## */
1823 treeview = sorttab[0]->treeview[ST_CAT_ARTIST];
1824 model = gtk_tree_view_get_model(treeview);
1825 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
1826
1827 status = gtk_tree_model_get_iter_first(model, &iter);
1828 g_return_val_if_fail (status, FALSE);
1829
1830 do {
1831 gtk_tree_model_get(model, &iter, ST_COLUMN_ENTRY, &entry, -1);
1832 g_return_val_if_fail (entry, FALSE);
1833
1834 if (g_ascii_strcasecmp(entry->name, track->artist) == 0) {
1835 /* break out the loop once the correct iter has been found */
1836 break;
1837 }
1838 }
1839 while (gtk_tree_model_iter_next(model, &iter));
1840
1841 gtk_tree_selection_select_iter(selection, &iter);
1842
1843 /* ######## Select the album from the second sorttab ######## */
1844 gtk_notebook_set_current_page(sorttab[1]->notebook, ST_CAT_ALBUM);
1845 st_page_selected(sorttab[1]->notebook, ST_CAT_ALBUM);
1846
1847 treeview = sorttab[1]->treeview[ST_CAT_ALBUM];
1848 model = gtk_tree_view_get_model(treeview);
1849 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
1850
1851 status = gtk_tree_model_get_iter_first(model, &iter);
1852 g_return_val_if_fail (status, FALSE);
1853
1854 do {
1855 gtk_tree_model_get(model, &iter, ST_COLUMN_ENTRY, &entry, -1);
1856 g_return_val_if_fail (entry, FALSE);
1857
1858 if (g_ascii_strcasecmp(entry->name, track->album) == 0) {
1859 /* break out the loop once the correct iter has been found */
1860 break;
1861 }
1862 }
1863 while (gtk_tree_model_iter_next(model, &iter));
1864
1865 gtk_tree_selection_select_iter(selection, &iter);
1866
1867 return TRUE;
1868 }
1869
st_selection_changed_cb(gpointer data)1870 static gboolean st_selection_changed_cb(gpointer data) {
1871 StSelectionEvent *event = (StSelectionEvent *) data;
1872 GtkTreeView *tree_view = event->tree_view;
1873 GtkTreeSelection *selection = gtk_tree_view_get_selection(tree_view);
1874 guint32 inst = event->inst;
1875 GtkTreeModel *model;
1876 GtkTreeIter iter;
1877 TabEntry *new_entry;
1878 SortTab *st;
1879
1880 #if DEBUG_TIMING || DEBUG_CB_INIT
1881 GTimeVal time;
1882 g_get_current_time (&time);
1883 printf ("st_selection_changed_cb enter (inst: %d): %ld.%06ld sec\n",
1884 inst, time.tv_sec % 3600, time.tv_usec);
1885 #endif
1886
1887 /* printf("st_s_c_cb %d: entered\n", inst); */
1888 st = sorttab[inst];
1889 if (st == NULL)
1890 return FALSE;
1891 if (gtk_tree_selection_get_selected(selection, &model, &iter) == FALSE) {
1892 /* no selection -- unselect current selection (unless
1893 st->current_entry == NULL -- that means we're in the middle
1894 of a st_init() (removing all entries). In that case we don't
1895 want to forget our last selection! */
1896 if (st->current_entry) {
1897 st->current_entry = NULL;
1898 C_FREE (st->lastselection[st->current_category]);
1899 st->unselected = TRUE;
1900 }
1901 st_init(-1, inst + 1);
1902 }
1903 else { /* handle new selection */
1904 gtk_tree_model_get(model, &iter, ST_COLUMN_ENTRY, &new_entry, -1);
1905 /*printf("selected instance %d, entry %x (was: %x)\n", inst,
1906 *new_entry, st->current_entry);*/
1907
1908 /* initialize next instance */
1909 st_init(-1, inst + 1);
1910 /* remember new selection */
1911 st->current_entry = new_entry;
1912 if (!new_entry->master) {
1913 C_FREE (st->lastselection[st->current_category]);
1914 st->lastselection[st->current_category] = g_strdup(new_entry->name);
1915 }
1916 st->unselected = FALSE;
1917
1918 if (new_entry->members) {
1919 GList *gl;
1920 st_enable_disable_view_sort(inst + 1, FALSE);
1921
1922 for (gl = new_entry->members; gl; gl = gl->next) { /* add all member tracks to next instance */
1923 Track *track = gl->data;
1924 st_add_track(track, FALSE, TRUE, inst+1);
1925 }
1926 st_enable_disable_view_sort (inst+1, TRUE);
1927 st_add_track (NULL, TRUE, st->final, inst+1);
1928 }
1929 gtkpod_tracks_statusbar_update();
1930
1931 /* Select the cover in the coverart_display */
1932 GList *gl = g_list_first(new_entry->members);
1933 if (gl != NULL)
1934 {
1935 Track *track = gl->data;
1936 if (track != NULL)
1937 coverart_select_cover (track);
1938 }
1939 }
1940
1941 space_data_update ();
1942
1943 #if DEBUG_TIMING
1944 g_get_current_time (&time);
1945 printf ("st_selection_changed_cb exit: %ld.%06ld sec\n",
1946 time.tv_sec % 3600, time.tv_usec);
1947 #endif
1948
1949 return FALSE;
1950 }
1951
1952 /* Callback function called when the selection
1953 of the sort tab view has changed */
1954 /* Instead of handling the selection directly, we add a
1955 "callback". Currently running display updates will be stopped
1956 before the st_selection_changed_cb is actually called */
st_selection_changed(GtkTreeSelection * selection,gpointer user_data)1957 static void st_selection_changed(GtkTreeSelection *selection, gpointer user_data) {
1958 #if DEBUG_CB_INIT
1959 printf("st_s_c enter (inst: %d)\n", (gint)user_data);
1960 #endif
1961 StSelectionEvent *event = g_malloc0(sizeof(StSelectionEvent));
1962 event->tree_view = gtk_tree_selection_get_tree_view(selection);
1963 event->inst = (guint32) GPOINTER_TO_UINT(user_data);
1964 g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
1965 st_selection_changed_cb, event, g_free);
1966 #if DEBUG_CB_INIT
1967 printf("st_s_c exit (inst: %d)\n", (gint)user_data);
1968 #endif
1969 }
1970
1971 /* Called when editable cell is being edited. Stores new data to
1972 the entry list and changes all members. */
st_cell_edited(GtkCellRendererText * renderer,const gchar * path_string,const gchar * new_text,gpointer data)1973 static void st_cell_edited(GtkCellRendererText *renderer, const gchar *path_string, const gchar *new_text, gpointer data) {
1974 GtkTreeModel *model;
1975 GtkTreePath *path;
1976 GtkTreeIter iter;
1977 TabEntry *entry;
1978 ST_item column;
1979 gint i, n, inst;
1980 GList *members;
1981 SortTab *st;
1982
1983 inst = (guint32) GPOINTER_TO_UINT(data);
1984 st = sorttab[inst];
1985 model = st->model;
1986 path = gtk_tree_path_new_from_string(path_string);
1987 column = (ST_item) g_object_get_data(G_OBJECT (renderer), "column");
1988 gtk_tree_model_get_iter(model, &iter, path);
1989 gtk_tree_model_get(model, &iter, column, &entry, -1);
1990
1991 /*printf("Inst %d: st_cell_edited: column: %d :%lx\n", inst, column, entry);*/
1992
1993 switch (column) {
1994 case ST_COLUMN_ENTRY:
1995 /* We only do something, if the name actually got changed */
1996 if (g_utf8_collate(entry->name, new_text) != 0) {
1997 iTunesDB *itdb = NULL;
1998 /* remove old hash entry if available */
1999 TabEntry *hash_entry = g_hash_table_lookup(st->entry_hash, entry->name);
2000 if (hash_entry == entry)
2001 g_hash_table_remove(st->entry_hash, entry->name);
2002 /* replace entry name */
2003 g_free(entry->name);
2004 if (sorttab[inst]->current_category == ST_CAT_YEAR) { /* make sure the entry->name is identical to
2005 atoi(new_text) */
2006 entry->name = g_strdup_printf("%d", atoi(new_text));
2007 g_object_set(G_OBJECT (renderer), "text", entry->name, NULL);
2008 }
2009 else {
2010 entry->name = g_strdup(new_text);
2011 }
2012 st_build_sortkeys(entry);
2013
2014 /* re-insert into hash table if the same name doesn't
2015 already exist */
2016 if (g_hash_table_lookup(st->entry_hash, entry->name) == NULL)
2017 g_hash_table_insert(st->entry_hash, entry->name, entry);
2018 /* Now we look up all the tracks and change the ID3 Tag as well */
2019 /* We make a copy of the current members list, as it may change
2020 during the process */
2021 members = g_list_copy(entry->members);
2022 n = g_list_length(members);
2023 /* block user input if we write tags (might take a while) */
2024 if (prefs_get_int("id3_write"))
2025 block_widgets();
2026 for (i = 0; i < n; ++i) {
2027 ExtraTrackData *etr;
2028 Track *track = (Track *) g_list_nth_data(members, i);
2029 T_item t_item;
2030
2031 g_return_if_fail (track);
2032 etr = track->userdata;
2033 g_return_if_fail (etr);
2034 g_return_if_fail (track->itdb);
2035 if (!itdb)
2036 itdb = track->itdb;
2037
2038 t_item = ST_to_T(sorttab[inst]->current_category);
2039
2040 if (t_item == T_YEAR) {
2041 gint nr = atoi(new_text);
2042 if (nr < 0)
2043 nr = 0;
2044 track->year = nr;
2045 g_free(etr->year_str);
2046 etr->year_str = g_strdup_printf("%d", nr);
2047 }
2048 else {
2049 gchar **itemp_utf8 = track_get_item_pointer(track, t_item);
2050 g_return_if_fail (itemp_utf8);
2051 g_free(*itemp_utf8);
2052 *itemp_utf8 = g_strdup(new_text);
2053 }
2054 track->time_modified = time(NULL);
2055 pm_track_changed(track);
2056 /* If prefs say to write changes to file, do so */
2057 if (prefs_get_int("id3_write")) {
2058 /* T_item tag_id;*/
2059 /* should we update all ID3 tags or just the one
2060 changed? -- obsoleted in 0.71 */
2061 /* if (prefs_get_id3_writeall ()) tag_id = T_ALL;
2062 else tag_id = t_item;*/
2063 write_tags_to_file(track);
2064 while (widgets_blocked && gtk_events_pending())
2065 gtk_main_iteration();
2066 }
2067 }
2068 g_list_free(members);
2069 /* allow user input again */
2070 if (prefs_get_int("id3_write"))
2071 release_widgets();
2072 /* display possible duplicates that have been removed */
2073 gp_duplicate_remove(NULL, NULL);
2074 /* indicate that data has changed */
2075 if (itdb) data_changed (itdb);
2076 }
2077 break;
2078 default:
2079 break;
2080 }
2081 gtk_tree_path_free (path);
2082 }
2083
2084 /* The sort tab entries are stored in a separate list (sorttab->entries)
2085 and only pointers to the corresponding TabEntry structure are placed
2086 into the model.
2087 This function reads the data for the given cell from the list and
2088 passes it to the renderer. */
st_cell_data_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer data)2089 static void st_cell_data_func(GtkTreeViewColumn *tree_column, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) {
2090 TabEntry *entry;
2091 gint column;
2092
2093 column = (gint) GPOINTER_TO_INT(g_object_get_data (G_OBJECT (renderer), "column"));
2094 gtk_tree_model_get(model, iter, ST_COLUMN_ENTRY, &entry, -1);
2095
2096 switch (column) { /* We only have one column, so this code is overkill... */
2097 case ST_COLUMN_ENTRY:
2098 if (entry->master || entry->compilation) { /* mark the "All" entry */
2099 g_object_set(G_OBJECT (renderer),
2100 "text", entry->name, "editable", FALSE,
2101 "weight", PANGO_WEIGHT_BOLD, NULL);
2102 }
2103 else {
2104 g_object_set(G_OBJECT (renderer),
2105 "text", entry->name, "editable", TRUE,
2106 "weight", PANGO_WEIGHT_NORMAL, NULL);
2107 }
2108 break;
2109 }
2110 }
2111
2112 /* Function used to compare rows with user's search string */
st_search_equal_func(GtkTreeModel * model,gint column,const gchar * key,GtkTreeIter * iter,gpointer search_data)2113 gboolean st_search_equal_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer search_data) {
2114 TabEntry *entry1;
2115 gboolean cmp;
2116 gtk_tree_model_get(model, iter, ST_COLUMN_ENTRY, &entry1, -1);
2117
2118 cmp = (compare_string_start_case_insensitive(entry1->name, key) != 0);
2119 return cmp;
2120 }
2121 ;
2122
2123 /* Function used to compare two cells during sorting (sorttab view) */
st_data_compare_func(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user_data)2124 gint st_data_compare_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data) {
2125 TabEntry *entry1;
2126 TabEntry *entry2;
2127 GtkSortType order;
2128 gint corr, colid;
2129 gint inst;
2130 SortTab *st;
2131
2132 inst = (guint32) GPOINTER_TO_UINT(user_data);
2133
2134 gtk_tree_model_get(model, a, ST_COLUMN_ENTRY, &entry1, -1);
2135 gtk_tree_model_get(model, b, ST_COLUMN_ENTRY, &entry2, -1);
2136 if (gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE (model),
2137 &colid, &order) == FALSE)
2138 return 0;
2139
2140 /* We make sure that the "all" entry always stays on top, closely followed
2141 by the compilation entry */
2142 if (order == GTK_SORT_ASCENDING)
2143 corr = +1;
2144 else
2145 corr = -1;
2146 if (entry1->master)
2147 return (-corr);
2148 if (entry2->master)
2149 return (corr);
2150 if (entry1->compilation)
2151 return (-corr);
2152 if (entry2->compilation)
2153 return (corr);
2154
2155 /* compare the two entries */
2156 /* string_compare_func is set to either compare_string_fuzzy or
2157 compare_string in on_st_switch_page() which is called
2158 once before the comparing begins. */
2159
2160 st = sorttab[inst];
2161
2162 return st->entry_compare_func(entry1, entry2);
2163 }
2164
2165 /* Stop editing. If @cancel is TRUE, the edited value will be
2166 discarded (I have the feeling that the "discarding" part does not
2167 work quite the way intended). */
st_stop_editing(gint inst,gboolean cancel)2168 void st_stop_editing(gint inst, gboolean cancel) {
2169 if (inst < prefs_get_int("sort_tab_num")) {
2170 SortTab *st = sorttab[inst];
2171 if (st) {
2172 GtkTreeViewColumn *col;
2173 gtk_tree_view_get_cursor(st->treeview[st->current_category], NULL, &col);
2174 if (col) {
2175 if (!cancel && col->editable_widget)
2176 gtk_cell_editable_editing_done(col->editable_widget);
2177 if (col->editable_widget)
2178 gtk_cell_editable_remove_widget(col->editable_widget);
2179 }
2180 }
2181 }
2182 }
2183
2184 /* Compare function to avoid sorting */
st_nosort_comp(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user_data)2185 static gint st_nosort_comp(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data) {
2186 return 0;
2187 }
2188
2189 /* Disable sorting of the view during lengthy updates. */
2190 /* @enable: TRUE: enable, FALSE: disable */
st_enable_disable_view_sort(gint inst,gboolean enable)2191 void st_enable_disable_view_sort(gint inst, gboolean enable) {
2192 static gint disable_count[SORT_TAB_MAX];
2193
2194 if (inst >= prefs_get_int("sort_tab_num")) {
2195 tm_enable_disable_view_sort(enable);
2196 return;
2197 }
2198
2199 if (enable) {
2200 disable_count[inst]--;
2201 if (disable_count[inst] < 0)
2202 fprintf(stderr, "Programming error: disable_count < 0\n");
2203 if (disable_count[inst] == 0) {
2204 /* Re-enable sorting */
2205 if (prefs_get_int("st_sort") != SORT_NONE) {
2206 SortTab *st = sorttab[inst];
2207 if (st && (st->current_category != ST_CAT_SPECIAL) && st->model) {
2208 if (BROKEN_GTK_TREE_SORT)
2209 {
2210 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE (st->model), ST_COLUMN_ENTRY, st_data_compare_func, GINT_TO_POINTER(inst), NULL);
2211 }
2212 else
2213 {
2214 gtk_tree_sortable_set_sort_column_id (
2215 GTK_TREE_SORTABLE (st->model),
2216 ST_COLUMN_ENTRY,
2217 prefs_get_int("st_sort"));
2218 }
2219 }
2220 }
2221 st_enable_disable_view_sort (inst+1, enable);
2222 }
2223 }
2224 else
2225 {
2226 if (disable_count[inst] == 0)
2227 {
2228 /* Disable sorting */
2229 if (prefs_get_int("st_sort") != SORT_NONE)
2230 {
2231 SortTab *st = sorttab[inst];
2232 if (st &&
2233 (st->current_category != ST_CAT_SPECIAL) &&
2234 st->model)
2235 {
2236 if (BROKEN_GTK_TREE_SORT)
2237 {
2238 gtk_tree_sortable_set_sort_func (
2239 GTK_TREE_SORTABLE (st->model),
2240 ST_COLUMN_ENTRY,
2241 st_nosort_comp, NULL, NULL);
2242 }
2243 else
2244 {
2245 gtk_tree_sortable_set_sort_column_id (
2246 GTK_TREE_SORTABLE (st->model),
2247 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID,
2248 prefs_get_int("st_sort"));
2249 }
2250 }
2251 }
2252 st_enable_disable_view_sort (inst+1, enable);
2253 }
2254 disable_count[inst]++;
2255 }
2256 }
2257
st_select_current_position(gint inst,gint x,gint y)2258 void st_select_current_position(gint inst, gint x, gint y) {
2259 if (inst < prefs_get_int("sort_tab_num")) {
2260 SortTab *st = sorttab[inst];
2261 if (st) {
2262 GtkTreePath *path;
2263 GtkTreeView *tv = st->treeview[st->current_category];
2264
2265 gtk_tree_view_get_path_at_pos(tv, x, y, &path, NULL, NULL, NULL);
2266 if (path)
2267 {
2268 GtkTreeSelection *ts = gtk_tree_view_get_selection (tv);
2269 gtk_tree_selection_select_path (ts, path);
2270 gtk_tree_path_free (path);
2271 }
2272 }
2273 }
2274 }
2275
2276 /* Make the appropriate number of sort tab instances visible */
2277 /* Also: make the menu items "more/less sort tabs" active/inactive as
2278 * needed. */
st_adjust_visible(void)2279 static void st_adjust_visible(void) {
2280 gint i, n;
2281 GtkWidget *w;
2282
2283 if (!st_paned[0])
2284 return;
2285
2286 /* first initialize (clear) all sorttabs */
2287 n = prefs_get_int("sort_tab_num");
2288 prefs_set_int("sort_tab_num", SORT_TAB_MAX);
2289 st_init(-1, 0);
2290 prefs_set_int("sort_tab_num", n);
2291
2292 /* set the visible elements */
2293 for (i = 0; i < n; ++i) {
2294 gtk_widget_show(GTK_WIDGET (sorttab[i]->notebook));
2295 if (i < PANED_NUM_ST)
2296 gtk_widget_show(GTK_WIDGET (st_paned[i]));
2297 }
2298 /* set the invisible elements */
2299 for (i = n; i < SORT_TAB_MAX; ++i) {
2300 gtk_widget_hide(GTK_WIDGET (sorttab[i]->notebook));
2301 if (i < PANED_NUM_ST)
2302 gtk_widget_hide(GTK_WIDGET (st_paned[i]));
2303 }
2304
2305 /* activate / deactiveate "less sort tabs" menu item */
2306 w = gtkpod_xml_get_widget(main_window_xml, "less_sort_tabs");
2307 if (n == 0)
2308 gtk_widget_set_sensitive(w, FALSE);
2309 else
2310 gtk_widget_set_sensitive(w, TRUE);
2311
2312 /* activate / deactiveate "more sort tabs" menu item */
2313 w = gtkpod_xml_get_widget(main_window_xml, "more_sort_tabs");
2314 if (n == SORT_TAB_MAX)
2315 gtk_widget_set_sensitive(w, FALSE);
2316 else
2317 gtk_widget_set_sensitive(w, TRUE);
2318 }
2319
2320 /* Make the appropriate number of sort tab instances visible */
2321 /* Also: make the menu items "more/less sort tabs" active/inactive as
2322 * needed. */
st_show_visible(void)2323 void st_show_visible(void) {
2324 /* Adjust visibility */
2325 st_adjust_visible();
2326
2327 /* redisplay */
2328 st_redisplay(0);
2329 }
2330
2331 /* set the paned positions for the visible sort tabs in the prefs
2332 * structure. This function is called when first creating the paned
2333 * elements and from within st_arrange_visible_sort_tabs() */
st_set_visible_sort_tab_paned(void)2334 static void st_set_visible_sort_tab_paned(void) {
2335 gint i, x, y, p0, num, width;
2336
2337 num = prefs_get_int("sort_tab_num");
2338 if (num > 0) {
2339 gchar *buf;
2340 GtkWidget *w;
2341
2342 gtk_window_get_size(GTK_WINDOW (gtkpod_window), &x, &y);
2343 buf = g_strdup_printf("paned%d", PANED_PLAYLIST);
2344 if ((w = gtkpod_xml_get_widget(main_window_xml, buf))) {
2345 p0 = gtk_paned_get_position(GTK_PANED (w));
2346 width = (x - p0) / num;
2347 for (i = 0; i < num; ++i) {
2348 prefs_set_int_index("paned_pos_", PANED_NUM_GLADE + i, width);
2349 }
2350 }
2351 g_free(buf);
2352 }
2353 }
2354
2355 /* Regularly arrange the visible sort tabs */
st_arrange_visible_sort_tabs(void)2356 void st_arrange_visible_sort_tabs(void) {
2357 gint i, num;
2358
2359 num = prefs_get_int("sort_tab_num");
2360 if (num > 0) {
2361 st_set_visible_sort_tab_paned();
2362 for (i = 0; i < num; ++i) {
2363 if (prefs_get_int_index("paned_pos_", PANED_NUM_GLADE + i) != -1) {
2364 if (st_paned[i])
2365 gtk_paned_set_position(st_paned[i], prefs_get_int_index("paned_pos_", PANED_NUM_GLADE + i));
2366 }
2367 }
2368 }
2369 }
2370
2371 /* Created paned elements for sorttabs */
st_create_paned(void)2372 static void st_create_paned(void) {
2373 gint i;
2374
2375 /* sanity check */
2376 g_return_if_fail (st_paned[0] == NULL);
2377
2378 for (i = 0; i < PANED_NUM_ST; ++i) {
2379 GtkWidget *paned;
2380
2381 paned = gtk_hpaned_new();
2382 gtk_widget_show(paned);
2383
2384 if (!i) {
2385 GtkWidget *parent;
2386 GtkWidget *dummy;
2387 parent = gtkpod_xml_get_widget(main_window_xml, "paned1");
2388 dummy = gtkpod_xml_get_widget(main_window_xml, "paned1_dummy");
2389 g_return_if_fail (parent);
2390 g_return_if_fail (dummy);
2391 gtk_widget_destroy(dummy);
2392
2393 g_object_set_data(G_OBJECT (paned), "paned_id", "st_0");
2394 gtk_paned_pack2(GTK_PANED (parent), paned, TRUE, TRUE);
2395 st_update_paned_position ();
2396 }
2397 else
2398 {
2399 gtk_paned_pack2 (st_paned[i-1], paned, TRUE, TRUE);
2400 }
2401
2402 st_paned[i] = GTK_PANED (paned);
2403 }
2404
2405 /* set position of visible paned to decent values if not already
2406 set */
2407 if (prefs_get_int_index("paned_pos_", PANED_NUM_GLADE) == -1)
2408 st_set_visible_sort_tab_paned ();
2409 }
2410
st_button_press_event(GtkWidget * w,GdkEventButton * e,gpointer data)2411 static gboolean st_button_press_event(GtkWidget *w, GdkEventButton *e, gpointer data) {
2412 if (w && e) {
2413 switch (e->button) {
2414 case 3:
2415 st_select_current_position(GPOINTER_TO_INT(data), e->x, e->y);
2416 st_context_menu_init(GPOINTER_TO_INT(data));
2417 return TRUE;
2418 default:
2419 break;
2420 }
2421
2422 }
2423 return (FALSE);
2424 }
2425
2426 /* Create tracks listview */
st_create_treeview(gint inst,ST_CAT_item st_cat)2427 static void st_create_treeview(gint inst, ST_CAT_item st_cat) {
2428 SortTab *st = sorttab[inst];
2429 GtkTreeSelection *selection;
2430 GtkTreeView *treeview;
2431 GtkCellRenderer *renderer;
2432 GtkTreeViewColumn *column;
2433
2434 /* create treeview */
2435 treeview = GTK_TREE_VIEW (gtk_tree_view_new ());
2436 gtk_widget_show(GTK_WIDGET (treeview));
2437 st->treeview[st_cat] = treeview;
2438 gtk_container_add(GTK_CONTAINER (st->window[st_cat]),
2439 GTK_WIDGET (treeview));
2440 gtk_tree_view_set_model (treeview, st->model);
2441 g_signal_connect_after ((gpointer) treeview, "key_release_event",
2442 G_CALLBACK (on_st_treeview_key_release_event),
2443 NULL);
2444 gtk_drag_source_set (GTK_WIDGET (treeview), GDK_BUTTON1_MASK,
2445 st_drag_types, TGNR (st_drag_types),
2446 GDK_ACTION_COPY|GDK_ACTION_MOVE);
2447 g_signal_connect (G_OBJECT (treeview), "button-press-event",
2448 G_CALLBACK (st_button_press_event), GINT_TO_POINTER(inst));
2449 g_signal_connect ((gpointer) treeview, "drag_data_get",
2450 G_CALLBACK (st_drag_data_get),
2451 NULL);
2452 g_signal_connect ((gpointer) treeview, "drag-end",
2453 G_CALLBACK (st_drag_end),
2454 NULL);
2455 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (treeview), TRUE);
2456 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (treeview), TRUE);
2457 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
2458 gtk_tree_view_set_enable_search (GTK_TREE_VIEW (treeview), TRUE);
2459 gtk_tree_view_set_search_column (GTK_TREE_VIEW (treeview), 0);
2460 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (treeview),
2461 st_search_equal_func,
2462 NULL,
2463 NULL);
2464
2465 selection = gtk_tree_view_get_selection (treeview);
2466 gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
2467 g_signal_connect (G_OBJECT (selection), "changed",
2468 G_CALLBACK (st_selection_changed),
2469 GINT_TO_POINTER(inst));
2470 /* Add column */
2471 renderer = gtk_cell_renderer_text_new ();
2472 g_signal_connect (G_OBJECT (renderer), "edited",
2473 G_CALLBACK (st_cell_edited),
2474 GINT_TO_POINTER(inst));
2475 g_object_set_data (G_OBJECT (renderer), "column",
2476 (gint *)ST_COLUMN_ENTRY);
2477 column = gtk_tree_view_column_new ();
2478 gtk_tree_view_column_pack_start (column, renderer, TRUE);
2479 column = gtk_tree_view_column_new_with_attributes ("", renderer, NULL);
2480 gtk_tree_view_column_set_cell_data_func (column, renderer,
2481 st_cell_data_func, NULL, NULL);
2482 gtk_tree_view_column_set_sort_column_id (column, ST_COLUMN_ENTRY);
2483 gtk_tree_view_column_set_resizable (column, TRUE);
2484 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
2485 gtk_tree_view_column_set_sort_order (column, GTK_SORT_ASCENDING);
2486 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (st->model),
2487 ST_COLUMN_ENTRY,
2488 st_data_compare_func,
2489 GINT_TO_POINTER(inst), NULL);
2490 gtk_tree_view_append_column (treeview, column);
2491 }
2492
2493 /* Create the "special" page in the notebook and connect all the
2494 signals */
st_create_special(gint inst,GtkWidget * window)2495 static void st_create_special(gint inst, GtkWidget *window) {
2496 GtkWidget *special;
2497 GtkWidget *viewport;
2498 GtkWidget *w;
2499 SortTab *st = sorttab[inst];
2500 gint i;
2501 GladeXML *special_xml;
2502 gchar *buf;
2503
2504 special_xml = gtkpod_xml_new(xml_file, "special_sorttab");
2505 special = gtkpod_xml_get_widget(special_xml, "special_sorttab");
2506
2507 viewport = gtkpod_xml_get_widget(special_xml, "special_viewport");
2508
2509 /* according to GTK FAQ: move a widget to a new parent */
2510 gtk_widget_ref(viewport);
2511 gtk_container_remove(GTK_CONTAINER (special), viewport);
2512 gtk_container_add(GTK_CONTAINER (window), viewport);
2513 gtk_widget_unref(viewport);
2514
2515 /* Connect the signal handlers and set default value. User data
2516 is @inst+(additional data << SP_SHIFT) */
2517 /* AND/OR button */
2518 w = gtkpod_xml_get_widget(special_xml, "sp_or_button");
2519 g_signal_connect ((gpointer)w,
2520 "toggled", G_CALLBACK (on_sp_or_button_toggled),
2521 GINT_TO_POINTER(inst));
2522 if (prefs_get_int_index("sp_or", inst))
2523 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), TRUE);
2524 else
2525 {
2526 w = gtkpod_xml_get_widget (special_xml, "sp_and_button");
2527 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), TRUE);
2528 }
2529
2530 /* RATING */
2531 w = gtkpod_xml_get_widget (special_xml, "sp_rating_button");
2532 g_signal_connect ((gpointer)w,
2533 "toggled", G_CALLBACK (on_sp_cond_button_toggled),
2534 GUINT_TO_POINTER((T_RATING<<SP_SHIFT) + inst));
2535 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w),
2536 prefs_get_int_index("sp_rating_cond", inst));
2537 for (i=0; i<=RATING_MAX; ++i)
2538 {
2539 gchar *buf = g_strdup_printf ("sp_rating%d", i);
2540 w = gtkpod_xml_get_widget (special_xml, buf);
2541 g_signal_connect ((gpointer)w,
2542 "toggled", G_CALLBACK (on_sp_rating_n_toggled),
2543 GUINT_TO_POINTER((i<<SP_SHIFT) + inst));
2544 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w),
2545 get_sp_rating_n (inst, i));
2546 g_free (buf);
2547 }
2548
2549 /* PLAYCOUNT */
2550 w = gtkpod_xml_get_widget (special_xml, "sp_playcount_button");
2551 g_signal_connect ((gpointer)w,
2552 "toggled", G_CALLBACK (on_sp_cond_button_toggled),
2553 GUINT_TO_POINTER((T_PLAYCOUNT<<SP_SHIFT) + inst));
2554 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w),
2555 prefs_get_int_index("sp_playcound_cond", inst));
2556 w = gtkpod_xml_get_widget (special_xml, "sp_playcount_low");
2557 g_signal_connect ((gpointer)w,
2558 "value_changed",
2559 G_CALLBACK (on_sp_playcount_low_value_changed),
2560 GINT_TO_POINTER(inst));
2561 gtk_spin_button_set_value (GTK_SPIN_BUTTON (w),
2562 prefs_get_int_index("sp_playcount_low", inst));
2563 w = gtkpod_xml_get_widget (special_xml, "sp_playcount_high");
2564 g_signal_connect ((gpointer)w,
2565 "value_changed",
2566 G_CALLBACK (on_sp_playcount_high_value_changed),
2567 GINT_TO_POINTER(inst));
2568 gtk_spin_button_set_value (GTK_SPIN_BUTTON (w),
2569 prefs_get_int_index("sp_playcount_high", inst));
2570
2571 /* PLAYED */
2572 buf = prefs_get_string_index("sp_played_state", inst);
2573
2574 w = gtkpod_xml_get_widget (special_xml, "sp_played_button");
2575 st->ti_played.active = w;
2576 g_signal_connect ((gpointer)w,
2577 "toggled", G_CALLBACK (on_sp_cond_button_toggled),
2578 GUINT_TO_POINTER((T_TIME_PLAYED<<SP_SHIFT) + inst));
2579 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w),
2580 prefs_get_int_index("sp_played_cond", inst));
2581 w = gtkpod_xml_get_widget (special_xml, "sp_played_entry");
2582 st->ti_played.entry = w;
2583 gtk_entry_set_text (GTK_ENTRY (w),
2584 buf);
2585 g_signal_connect ((gpointer)w,
2586 "activate", G_CALLBACK (on_sp_entry_activate),
2587 GUINT_TO_POINTER((T_TIME_PLAYED<<SP_SHIFT) + inst));
2588 g_signal_connect ((gpointer)gtkpod_xml_get_widget (special_xml,
2589 "sp_played_cal_button"),
2590 "clicked",
2591 G_CALLBACK (on_sp_cal_button_clicked),
2592 GUINT_TO_POINTER((T_TIME_PLAYED<<SP_SHIFT) + inst));
2593 g_free(buf);
2594
2595 /* MODIFIED */
2596 buf = prefs_get_string_index("sp_modified_state", inst);
2597
2598 w = gtkpod_xml_get_widget (special_xml, "sp_modified_button");
2599 st->ti_modified.active = w;
2600 g_signal_connect ((gpointer)w,
2601 "toggled", G_CALLBACK (on_sp_cond_button_toggled),
2602 GUINT_TO_POINTER((T_TIME_MODIFIED<<SP_SHIFT) + inst));
2603 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w),
2604 prefs_get_int_index("sp_modified_cond", inst));
2605 w = gtkpod_xml_get_widget (special_xml, "sp_modified_entry");
2606 st->ti_modified.entry = w;
2607 gtk_entry_set_text (GTK_ENTRY (w),
2608 buf);
2609 g_signal_connect ((gpointer)w,
2610 "activate", G_CALLBACK (on_sp_entry_activate),
2611 GUINT_TO_POINTER((T_TIME_MODIFIED<<SP_SHIFT) + inst));
2612 g_signal_connect ((gpointer)gtkpod_xml_get_widget (special_xml,
2613 "sp_modified_cal_button"),
2614 "clicked",
2615 G_CALLBACK (on_sp_cal_button_clicked),
2616 GUINT_TO_POINTER((T_TIME_MODIFIED<<SP_SHIFT) + inst));
2617 g_free(buf);
2618
2619 /* ADDED */
2620 buf = prefs_get_string_index("sp_added_state", inst);
2621
2622 w = gtkpod_xml_get_widget (special_xml, "sp_added_button");
2623 st->ti_added.active = w;
2624 g_signal_connect ((gpointer)w,
2625 "toggled", G_CALLBACK (on_sp_cond_button_toggled),
2626 GUINT_TO_POINTER((T_TIME_ADDED<<SP_SHIFT) + inst));
2627 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w),
2628 prefs_get_int_index("sp_added_cond", inst));
2629 w = gtkpod_xml_get_widget (special_xml, "sp_added_entry");
2630 st->ti_added.entry = w;
2631 gtk_entry_set_text (GTK_ENTRY (w),
2632 buf);
2633 g_signal_connect ((gpointer)w,
2634 "activate", G_CALLBACK (on_sp_entry_activate),
2635 GUINT_TO_POINTER((T_TIME_ADDED<<SP_SHIFT) + inst));
2636 g_signal_connect ((gpointer)gtkpod_xml_get_widget (special_xml,
2637 "sp_added_cal_button"),
2638 "clicked",
2639 G_CALLBACK (on_sp_cal_button_clicked),
2640 GUINT_TO_POINTER((T_TIME_ADDED<<SP_SHIFT) + inst));
2641
2642 g_signal_connect ((gpointer)gtkpod_xml_get_widget (special_xml, "sp_go"),
2643 "clicked", G_CALLBACK (on_sp_go_clicked),
2644 GINT_TO_POINTER(inst));
2645 w = gtkpod_xml_get_widget (special_xml, "sp_go_always");
2646 g_signal_connect ((gpointer)w,
2647 "toggled", G_CALLBACK (on_sp_go_always_toggled),
2648 GINT_TO_POINTER(inst));
2649 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w),
2650 prefs_get_int_index("sp_autodisplay", inst));
2651 g_free (buf);
2652
2653 /* Safe pointer to tooltips */
2654 st->sp_tooltips_data = gtk_tooltips_data_get(gtkpod_xml_get_widget (special_xml, "sp_modified_entry"));
2655 /* Show / don't show tooltips */
2656 g_return_if_fail (st->sp_tooltips_data);
2657 if (prefs_get_int("display_tooltips_main"))
2658 gtk_tooltips_enable (st->sp_tooltips_data->tooltips);
2659 else gtk_tooltips_disable (st->sp_tooltips_data->tooltips);
2660 /* we don't need this any more */
2661 gtk_widget_destroy (special);
2662 }
2663
2664 /* create the treeview for category @st_cat of instance @inst */
st_create_page(gint inst,ST_CAT_item st_cat)2665 static void st_create_page(gint inst, ST_CAT_item st_cat) {
2666 GtkWidget *st0_notebook;
2667 GtkWidget *st0_window0;
2668 GtkWidget *st0_label0 = NULL;
2669 SortTab *st = sorttab[inst];
2670
2671 /* destroy treeview if already present */
2672 if (st->treeview[st_cat]) {
2673 gtk_widget_destroy(GTK_WIDGET (st->treeview[st_cat]));
2674 st->treeview[st_cat] = NULL;
2675 }
2676
2677 st0_notebook = GTK_WIDGET (st->notebook);
2678
2679 if (st->window[st_cat]) {
2680 st0_window0 = GTK_WIDGET (st->window[st_cat]);
2681 }
2682 else { /* create window if not already present */
2683 st0_window0 = gtk_scrolled_window_new(NULL, NULL);
2684 gtk_widget_show (st0_window0);
2685 gtk_container_add (GTK_CONTAINER (st0_notebook), st0_window0);
2686 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (st0_window0), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2687 st->window[st_cat] = st0_window0;
2688 }
2689
2690 switch (st_cat)
2691 {
2692 case ST_CAT_ARTIST:
2693 st0_label0 = gtk_label_new (_("Artist"));
2694 break;
2695 case ST_CAT_ALBUM:
2696 st0_label0 = gtk_label_new (_("Album"));
2697 break;
2698 case ST_CAT_GENRE:
2699 st0_label0 = gtk_label_new (_("Genre"));
2700 break;
2701 case ST_CAT_COMPOSER:
2702 st0_label0 = gtk_label_new (_("Comp."));
2703 break;
2704 case ST_CAT_TITLE:
2705 st0_label0 = gtk_label_new (_("Title"));
2706 break;
2707 case ST_CAT_YEAR:
2708 st0_label0 = gtk_label_new (_("Year"));
2709 break;
2710 case ST_CAT_SPECIAL:
2711 st0_label0 = gtk_label_new (_("Special"));
2712 break;
2713 default: /* should not happen... */
2714 g_return_if_reached ();
2715 }
2716 gtk_widget_show (st0_label0);
2717 gtk_notebook_set_tab_label (GTK_NOTEBOOK (st0_notebook), gtk_notebook_get_nth_page (GTK_NOTEBOOK (st0_notebook), st_cat), st0_label0);
2718 gtk_label_set_justify (GTK_LABEL (st0_label0), GTK_JUSTIFY_LEFT);
2719
2720 if (st_cat != ST_CAT_SPECIAL)
2721 {
2722 st_create_treeview (inst, st_cat);
2723 }
2724 else
2725 {
2726 st_create_special (inst, st0_window0);
2727 }
2728 }
2729
2730 /* create all ST_CAT_NUM treeviews and the special page in sort tab of
2731 * instance @inst, then set the model */
st_create_pages(gint inst)2732 static void st_create_pages(gint inst) {
2733 GtkTreeModel *model;
2734 GtkListStore *liststore;
2735 SortTab *st = sorttab[inst];
2736
2737 /* remove old model */
2738 if (st->model) {
2739 g_object_unref(G_OBJECT (st->model));
2740 st->model = NULL;
2741 }
2742 /* create model */
2743 liststore = gtk_list_store_new(ST_NUM_COLUMNS, G_TYPE_POINTER);
2744 model = GTK_TREE_MODEL (liststore);
2745 st->model = model;
2746
2747 st_create_page(inst, ST_CAT_ARTIST);
2748 st_create_page(inst, ST_CAT_ALBUM);
2749 st_create_page(inst, ST_CAT_GENRE);
2750 st_create_page(inst, ST_CAT_COMPOSER);
2751 st_create_page(inst, ST_CAT_TITLE);
2752 st_create_page(inst, ST_CAT_YEAR);
2753 st_create_page(inst, ST_CAT_SPECIAL);
2754 }
2755
2756 /* Create notebook and fill in sorttab[@inst] */
st_create_notebook(gint inst)2757 static void st_create_notebook(gint inst) {
2758 GtkWidget *st0_notebook;
2759 GtkPaned *paned;
2760 gint i, page;
2761 SortTab *st = sorttab[inst];
2762
2763 if (st->notebook) {
2764 gtk_widget_destroy(GTK_WIDGET (st->notebook));
2765 st->notebook = NULL;
2766 for (i = 0; i < ST_CAT_NUM; ++i) {
2767 st->treeview[i] = NULL;
2768 st->window[i] = NULL;
2769 }
2770 }
2771
2772 /* paned elements exist? */
2773 if (!st_paned[0])
2774 st_create_paned();
2775
2776 st0_notebook = gtk_notebook_new();
2777 if (inst < prefs_get_int("sort_tab_num"))
2778 gtk_widget_show(st0_notebook);
2779 else
2780 gtk_widget_hide(st0_notebook);
2781 /* which pane? */
2782 if (inst == SORT_TAB_MAX - 1)
2783 i = inst - 1;
2784 else
2785 i = inst;
2786 paned = st_paned[i];
2787 /* how to pack? */
2788 if (inst == SORT_TAB_MAX - 1)
2789 gtk_paned_pack2(paned, st0_notebook, TRUE, TRUE);
2790 else
2791 gtk_paned_pack1 (paned, st0_notebook, FALSE, TRUE);
2792 gtk_notebook_set_scrollable (GTK_NOTEBOOK (st0_notebook), TRUE);
2793
2794 st->notebook = GTK_NOTEBOOK (st0_notebook);
2795 st_create_pages (inst);
2796 page = prefs_get_int_index("st_category", inst);
2797 st->current_category = page;
2798 gtk_notebook_set_current_page (st->notebook, page);
2799 st_set_string_compare_func (inst, page);
2800
2801 if (prefs_get_int("st_sort") != SORT_NONE)
2802 st_sort_inst (inst, prefs_get_int("st_sort"));
2803
2804 g_signal_connect ((gpointer) st0_notebook, "switch_page",
2805 G_CALLBACK (on_st_switch_page),
2806 GINT_TO_POINTER(inst));
2807 }
2808
2809 /* Create sort tabs */
st_create_tabs(void)2810 void st_create_tabs(void) {
2811 gint inst;
2812 /* gchar *name; */
2813
2814 /* we count downward here because the smaller sort tabs might try to
2815 initialize the higher one's -> create the higher ones first */
2816 for (inst = SORT_TAB_MAX-1; inst >= 0; --inst) {
2817 sorttab[inst] = g_malloc0(sizeof(SortTab));
2818 st_create_notebook(inst);
2819 }
2820 /* adjust number of visible sorttabs (cannot use st_show_visible()
2821 because the latter calls st_redisplay(0) which refers to the
2822 playlist view which hasn't yet set up) */
2823 st_adjust_visible();
2824 }
2825
2826 /* Clean up the memory used by sort tabs (program quit). */
st_cleanup(void)2827 void st_cleanup(void) {
2828 gint i, j;
2829 for (i = 0; i < SORT_TAB_MAX; ++i) {
2830 if (sorttab[i] != NULL) {
2831 sp_store_sp_entries(i);
2832 st_remove_all_entries_from_model(i);
2833 for (j = 0; j < ST_CAT_NUM; ++j) {
2834 C_FREE (sorttab[i]->lastselection[j]);
2835 }
2836 g_free(sorttab[i]);
2837 sorttab[i] = NULL;
2838 }
2839 }
2840 }
2841
2842 /* set the default sizes for the gtkpod main window according to prefs:
2843 position of the PANED_NUM GtkPaned elements (the width of the
2844 colums is set when setting up the colums in the listview. Called by
2845 display_set_default_sizes() */
st_set_default_sizes(void)2846 void st_set_default_sizes(void) {
2847 gint i;
2848
2849 /* GtkPaned elements */
2850 g_return_if_fail (gtkpod_window);
2851 /* Elements defined with glade */
2852 for (i = 0; i < PANED_NUM_GLADE; ++i) {
2853 gchar *buf = g_strdup_printf("paned%d", i);
2854 GtkWidget *w = gtkpod_xml_get_widget(main_window_xml, buf);
2855 g_free(buf);
2856 g_return_if_fail (w);
2857 if (prefs_get_int_index("paned_pos_", i) != -1) {
2858 gtk_paned_set_position(GTK_PANED (w),
2859 prefs_get_int_index("paned_pos_", i));
2860 }
2861 }
2862 /* Elements defined with display.c (sort tab hpaned) */
2863 for (i = 0; i < PANED_NUM_ST; ++i) {
2864 g_return_if_fail (st_paned[i]);
2865 if (prefs_get_int_index("paned_pos_", PANED_NUM_GLADE + i) != -1) {
2866 gtk_paned_set_position(st_paned[i], prefs_get_int_index("paned_pos_", PANED_NUM_GLADE + i));
2867 }
2868 }
2869 }
2870
2871 /* update the cfg structure (preferences) with the current sizes /
2872 positions (called by display_update_default_sizes():
2873 position of GtkPaned elements */
st_update_default_sizes(void)2874 void st_update_default_sizes(void) {
2875 /* GtkPaned elements */
2876 if (gtkpod_window) {
2877 gint i;
2878 /* Elements defined with glade */
2879 for (i = 0; i < PANED_NUM_GLADE; ++i) {
2880 gchar *buf;
2881 GtkWidget *w;
2882 buf = g_strdup_printf("paned%d", i);
2883 if ((w = gtkpod_xml_get_widget(main_window_xml, buf))) {
2884 prefs_set_int_index("paned_pos_", i, gtk_paned_get_position(GTK_PANED (w)));
2885 }
2886 g_free(buf);
2887 }
2888 /* Elements defined with display.c (sort tab hpaned) */
2889 for (i = 0; i < PANED_NUM_ST; ++i) {
2890 if (st_paned[i])
2891 prefs_set_int_index("paned_pos_", i + PANED_NUM_GLADE, gtk_paned_get_position(st_paned[i]));
2892 }
2893 }
2894 }
2895
2896 /* make the tooltips visible or hide it depending on the value set in
2897 * the prefs (tooltips_main) (called by display_show_hide_tooltips() */
st_show_hide_tooltips(void)2898 void st_show_hide_tooltips(void) {
2899 gint i;
2900
2901 for (i = 0; i < SORT_TAB_MAX; ++i) {
2902 GtkTooltips *tt;
2903 GtkTooltipsData *ttd;
2904
2905 g_return_if_fail (sorttab[i]);
2906 ttd = sorttab[i]->sp_tooltips_data;
2907 g_return_if_fail (ttd);
2908 tt = ttd->tooltips;
2909 g_return_if_fail (tt);
2910
2911 if (prefs_get_int("display_tooltips_main"))
2912 gtk_tooltips_enable(tt);
2913 else
2914 gtk_tooltips_disable(tt);
2915 }
2916 }
2917
2918 /*
2919 Set the correct locations of the filter tabs and track list
2920 when filter_tabs_top is changed
2921 */
st_update_paned_position()2922 void st_update_paned_position() {
2923 GtkPaned *paned = GTK_PANED (gtkpod_xml_get_widget (main_window_xml, "paned1"));
2924 GtkWidget *top = gtk_paned_get_child1(paned);
2925 GtkWidget *bottom = gtk_paned_get_child2(paned);
2926 gboolean top_is_st_paned = g_object_get_data(G_OBJECT (top), "paned_id") != NULL;
2927 gboolean st_top = prefs_get_int("filter_tabs_top");
2928
2929 if ((top_is_st_paned && !st_top) || (!top_is_st_paned && st_top)) {
2930 /* Filter tabs are not at the correct location - swap pane children */
2931 /* We use g_object_ref so the widgets are not auto-destroyed */
2932 g_object_ref(top);
2933 g_object_ref(bottom);
2934
2935 gtk_container_remove(GTK_CONTAINER (paned), top);
2936 gtk_container_remove(GTK_CONTAINER (paned), bottom);
2937
2938 gtk_paned_pack1(paned, bottom, TRUE, TRUE);
2939 gtk_paned_pack2 (paned, top, TRUE, TRUE);
2940
2941 g_object_unref (top);
2942 g_object_unref (bottom);
2943 }
2944 }
2945
2946 /* ---------------------------------------------------------------- */
2947 /* */
2948 /* Section for calendar display */
2949 /* */
2950 /* ---------------------------------------------------------------- */
2951
2952 /* Strings for 'Category-Combo' */
2953
2954 /* enum to access cat_strings */
2955 enum {
2956 CAT_STRING_PLAYED = 0, CAT_STRING_MODIFIED = 1, CAT_STRING_ADDED = 2
2957 };
2958
2959 /* typedef to specify lower or upper margin */
2960 typedef enum {
2961 LOWER_MARGIN, UPPER_MARGIN
2962 } MarginType;
2963
2964 /* Set the calendar @calendar, as well as spin buttons @hour and @min
2965 * according to @mactime. If @mactime is 0, check @no_margin
2966 * togglebutton, otherwise uncheck it. */
cal_set_time_widgets(GtkCalendar * cal,GtkSpinButton * hour,GtkSpinButton * min,GtkToggleButton * no_margin,time_t timet)2967 static void cal_set_time_widgets(GtkCalendar *cal, GtkSpinButton *hour, GtkSpinButton *min, GtkToggleButton *no_margin, time_t timet) {
2968 struct tm *tm;
2969 time_t tt = time(NULL);
2970
2971 /* 0, -1 are treated in a special way (no lower/upper margin
2972 * -> set calendar to current time */
2973 if ((timet != 0) && (timet != -1)) {
2974 tt = timet;
2975 if (no_margin)
2976 gtk_toggle_button_set_active(no_margin, FALSE);
2977 }
2978 else if (no_margin)
2979 gtk_toggle_button_set_active(no_margin, TRUE);
2980
2981 tm = localtime(&tt);
2982
2983 if (cal) {
2984 gtk_calendar_select_month(cal, tm->tm_mon, 1900 + tm->tm_year);
2985 gtk_calendar_select_day(cal, tm->tm_mday);
2986 }
2987
2988 if (hour)
2989 gtk_spin_button_set_value(hour, tm->tm_hour);
2990 if (min)
2991 gtk_spin_button_set_value(min, tm->tm_min);
2992 }
2993
cal_set_time(GtkWidget * cal,MarginType type,time_t timet)2994 static void cal_set_time(GtkWidget *cal, MarginType type, time_t timet) {
2995 GtkCalendar *calendar = NULL;
2996 GtkSpinButton *hour = NULL;
2997 GtkSpinButton *min = NULL;
2998 GtkToggleButton *no_margin = NULL;
2999
3000 switch (type) {
3001 case LOWER_MARGIN:
3002 calendar = GTK_CALENDAR (gtkpod_xml_get_widget (cal_xml, "lower_cal"));
3003 hour = GTK_SPIN_BUTTON (gtkpod_xml_get_widget (cal_xml, "lower_hours"));
3004 min = GTK_SPIN_BUTTON (gtkpod_xml_get_widget (cal_xml, "lower_minutes"));
3005 no_margin = GTK_TOGGLE_BUTTON (gtkpod_xml_get_widget (cal_xml, "no_lower_margin"));
3006 break;
3007 case UPPER_MARGIN:
3008 calendar = GTK_CALENDAR (gtkpod_xml_get_widget (cal_xml, "upper_cal"));
3009 hour = GTK_SPIN_BUTTON (gtkpod_xml_get_widget (cal_xml, "upper_hours"));
3010 min = GTK_SPIN_BUTTON (gtkpod_xml_get_widget (cal_xml, "upper_minutes"));
3011 no_margin = GTK_TOGGLE_BUTTON (gtkpod_xml_get_widget (cal_xml, "no_upper_margin"));
3012 break;
3013 }
3014 cal_set_time_widgets(calendar, hour, min, no_margin, timet);
3015 }
3016
3017 /* Extract data from calendar/time.
3018 *
3019 * Return value:
3020 *
3021 * pointer to 'struct tm' filled with the relevant data or NULL, if
3022 * the button no_margin was selected.
3023 *
3024 * If @tm is != NULL, modify that instead.
3025 *
3026 * You must g_free() the retuned value.
3027 */
cal_get_time(GtkWidget * cal,MarginType type,struct tm * tm)3028 static struct tm *cal_get_time(GtkWidget *cal, MarginType type, struct tm *tm) {
3029 GtkCalendar *calendar = NULL;
3030 GtkSpinButton *hour = NULL;
3031 GtkSpinButton *min = NULL;
3032 GtkSpinButton *sec = NULL;
3033 GtkToggleButton *no_margin = NULL;
3034 GtkToggleButton *no_time = NULL;
3035
3036 switch (type) {
3037 case LOWER_MARGIN:
3038 calendar = GTK_CALENDAR (gtkpod_xml_get_widget (cal_xml, "lower_cal"));
3039 hour = GTK_SPIN_BUTTON (gtkpod_xml_get_widget (cal_xml, "lower_hours"));
3040 min = GTK_SPIN_BUTTON (gtkpod_xml_get_widget (cal_xml, "lower_minutes"));
3041 no_margin = GTK_TOGGLE_BUTTON (gtkpod_xml_get_widget (cal_xml, "no_lower_margin"));
3042 no_time = GTK_TOGGLE_BUTTON (gtkpod_xml_get_widget (cal_xml, "lower_time"));
3043 break;
3044 case UPPER_MARGIN:
3045 calendar = GTK_CALENDAR (gtkpod_xml_get_widget (cal_xml, "upper_cal"));
3046 hour = GTK_SPIN_BUTTON (gtkpod_xml_get_widget (cal_xml, "upper_hours"));
3047 min = GTK_SPIN_BUTTON (gtkpod_xml_get_widget (cal_xml, "upper_minutes"));
3048 no_margin = GTK_TOGGLE_BUTTON (gtkpod_xml_get_widget (cal_xml, "no_upper_margin"));
3049 no_time = GTK_TOGGLE_BUTTON (gtkpod_xml_get_widget (cal_xml, "upper_time"));
3050 break;
3051 }
3052
3053 if (!gtk_toggle_button_get_active(no_margin)) {
3054 /* Initialize tm with current time and copy the result of
3055 * localtime() to persistent memory that can be g_free()'d */
3056 time_t tt = time(NULL);
3057 if (!tm) {
3058 tm = g_malloc(sizeof(struct tm));
3059 memcpy(tm, localtime(&tt), sizeof(struct tm));
3060 }
3061
3062 if (calendar) {
3063 guint year, month, day;
3064 gtk_calendar_get_date(calendar, &year, &month, &day);
3065 tm->tm_year = year - 1900;
3066 tm->tm_mon = month;
3067 tm->tm_mday = day;
3068 }
3069 if (gtk_toggle_button_get_active(no_time)) {
3070 if (hour)
3071 tm->tm_hour = gtk_spin_button_get_value_as_int(hour);
3072 if (min)
3073 tm->tm_min = gtk_spin_button_get_value_as_int(min);
3074 if (sec)
3075 tm->tm_min = gtk_spin_button_get_value_as_int(sec);
3076 }
3077 else { /* use 0:00 for lower and 23:59 for upper margin */
3078 switch (type) {
3079 case LOWER_MARGIN:
3080 if (hour)
3081 tm->tm_hour = 0;
3082 if (min)
3083 tm->tm_min = 0;
3084 if (sec)
3085 tm->tm_sec = 0;
3086 break;
3087 case UPPER_MARGIN:
3088 if (hour)
3089 tm->tm_hour = 23;
3090 if (min)
3091 tm->tm_min = 59;
3092 if (sec)
3093 tm->tm_sec = 59;
3094 break;
3095 }
3096 }
3097 }
3098 return tm;
3099 }
3100
3101 /* get the category (T_TIME_PLAYED or T_TIME_MODIFIED) selected in the
3102 * combo */
cal_get_category(GtkWidget * cal)3103 static T_item cal_get_category(GtkWidget *cal) {
3104 GtkWidget *w;
3105 T_item item;
3106 gint i = -1;
3107
3108 w = gtkpod_xml_get_widget(cal_xml, "cat_combo");
3109 i = gtk_combo_box_get_active(GTK_COMBO_BOX (w));
3110
3111 switch (i) {
3112 case CAT_STRING_PLAYED:
3113 item = T_TIME_PLAYED;
3114 break;
3115 case CAT_STRING_MODIFIED:
3116 item = T_TIME_MODIFIED;
3117 break;
3118 case CAT_STRING_ADDED:
3119 item = T_TIME_ADDED;
3120 break;
3121 default:
3122 fprintf(stderr, "Programming error: cal_get_category () -- item not found.\n");
3123 /* set to something reasonable at least */
3124 item = T_TIME_PLAYED;
3125 }
3126
3127 return item;
3128 }
3129
3130 /* Returns a string "DD/MM/YYYY HH:MM". Data is taken from
3131 * @tm. Returns NULL if tm==NULL. You must g_free() the returned
3132 * string */
cal_get_time_string(struct tm * tm)3133 static gchar *cal_get_time_string(struct tm *tm) {
3134 gchar *str = NULL;
3135
3136 if (tm)
3137 str
3138 = g_strdup_printf("%02d/%02d/%04d %d:%02d", tm->tm_mday, tm->tm_mon + 1, 1900 + tm->tm_year, tm->tm_hour, tm->tm_min);
3139 return str;
3140 }
3141
3142 /* Extract data from calendar/time and write it to the corresponding
3143 entry in the specified sort tab */
cal_apply_data(GtkWidget * cal)3144 static void cal_apply_data(GtkWidget *cal) {
3145 struct tm *lower, *upper;
3146 TimeInfo *ti;
3147 T_item item;
3148 gint inst;
3149
3150 lower = cal_get_time(cal, LOWER_MARGIN, NULL);
3151 upper = cal_get_time(cal, UPPER_MARGIN, NULL);
3152
3153 /* Get selected instance */
3154 inst = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON (gtkpod_xml_get_widget (cal_xml, "sorttab_num_spin"))) - 1;
3155 /* Get selected category (played, modified or added) */
3156 item = cal_get_category(cal);
3157 /* Get pointer to corresponding TimeInfo struct */
3158 ti = sp_get_timeinfo_ptr(inst, item);
3159
3160 if (ti) {
3161 GtkToggleButton *act = GTK_TOGGLE_BUTTON (ti->active);
3162 /* is criteria currently checked (active)? */
3163 gboolean active = gtk_toggle_button_get_active(act);
3164 gchar *str = NULL;
3165 gchar *str1 = cal_get_time_string(lower);
3166 gchar *str2 = cal_get_time_string(upper);
3167
3168 if (!lower && !upper)
3169 if (!active) /* deactivate this criteria */
3170 gtk_toggle_button_set_active(act, FALSE);
3171 if (lower && !upper)
3172 str = g_strdup_printf("> %s", str1);
3173 if (!lower && upper)
3174 str = g_strdup_printf("< %s", str2);
3175 if (lower && upper)
3176 str = g_strdup_printf("%s < < %s", str1, str2);
3177 C_FREE (str1);
3178 C_FREE (str2);
3179
3180 if (str) { /* set the new string if it's different */
3181 if (strcmp(str, gtk_entry_get_text(GTK_ENTRY (ti->entry))) != 0) {
3182 gtk_entry_set_text(GTK_ENTRY (ti->entry), str);
3183 /* notification that contents have changed */
3184 g_signal_emit_by_name(ti->entry, "activate");
3185 }
3186 g_free(str);
3187 }
3188 if (!active) { /* activate the criteria */
3189 gtk_toggle_button_set_active(act, TRUE);
3190 }
3191 }
3192 g_free(lower);
3193 g_free(upper);
3194 }
3195
3196 /* Callback for 'Lower/Upper time ' buttons */
cal_time_toggled(GtkToggleButton * togglebutton,gpointer user_data)3197 static void cal_time_toggled(GtkToggleButton *togglebutton, gpointer user_data) {
3198 gboolean sens = gtk_toggle_button_get_active(togglebutton);
3199
3200 if ((GtkWidget *) togglebutton == gtkpod_xml_get_widget(cal_xml, "lower_time")) {
3201 gtk_widget_set_sensitive(gtkpod_xml_get_widget(cal_xml, "lower_time_box"), sens);
3202 }
3203 if ((GtkWidget *) togglebutton == gtkpod_xml_get_widget(cal_xml, "upper_time")) {
3204 gtk_widget_set_sensitive(gtkpod_xml_get_widget(cal_xml, "upper_time_box"), sens);
3205 }
3206 }
3207
3208 /* Callback for 'No Lower/Upper Margin' buttons */
cal_no_margin_toggled(GtkToggleButton * togglebutton,gpointer user_data)3209 static void cal_no_margin_toggled(GtkToggleButton *togglebutton, gpointer user_data) {
3210 gboolean sens = !gtk_toggle_button_get_active(togglebutton);
3211
3212 if ((GtkWidget *) togglebutton == gtkpod_xml_get_widget(cal_xml, "no_lower_margin")) {
3213 gtk_widget_set_sensitive(gtkpod_xml_get_widget(cal_xml, "lower_cal_box"), sens);
3214 }
3215 if ((GtkWidget *) togglebutton == gtkpod_xml_get_widget(cal_xml, "no_upper_margin")) {
3216 gtk_widget_set_sensitive(gtkpod_xml_get_widget(cal_xml, "upper_cal_box"), sens);
3217 }
3218 }
3219
3220 /* Save the default geometry of the window */
cal_save_default_geometry(GtkWindow * cal)3221 static void cal_save_default_geometry(GtkWindow *cal) {
3222 gint x, y;
3223
3224 gtk_window_get_size(cal, &x, &y);
3225 prefs_set_int("size_cal.x", x);
3226 prefs_set_int("size_cal.y", y);
3227
3228 }
3229
3230 /* Callback for 'delete' event */
cal_delete_event(GtkWidget * widget,GdkEvent * event,gpointer user_data)3231 static gboolean cal_delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
3232 cal_save_default_geometry(GTK_WINDOW (user_data));
3233 return FALSE;
3234 }
3235
3236 /* Callback for 'Cancel' button */
cal_cancel(GtkButton * button,gpointer user_data)3237 static void cal_cancel(GtkButton *button, gpointer user_data) {
3238 cal_save_default_geometry(GTK_WINDOW (user_data));
3239 gtk_widget_destroy(user_data);
3240 }
3241
3242 /* Callback for 'Apply' button */
cal_apply(GtkButton * button,gpointer user_data)3243 static void cal_apply(GtkButton *button, gpointer user_data) {
3244 cal_save_default_geometry(GTK_WINDOW (user_data));
3245 cal_apply_data(GTK_WIDGET (user_data));
3246 }
3247
3248 /* Callback for 'OK' button */
cal_ok(GtkButton * button,gpointer user_data)3249 static void cal_ok(GtkButton *button, gpointer user_data) {
3250 cal_apply(button, user_data);
3251 gtk_widget_destroy(user_data);
3252 }
3253
3254 /* Open a calendar window. Preset the values for instance @inst,
3255 category @item (time played, time modified or time added) */
cal_open_calendar(gint inst,T_item item)3256 void cal_open_calendar(gint inst, T_item item) {
3257 SortTab *st;
3258 GtkWidget *w;
3259 GtkWidget *cal;
3260 int index = -1;
3261 gint defx, defy;
3262 TimeInfo *ti;
3263
3264 /* Sanity */
3265 if (inst >= SORT_TAB_MAX)
3266 return;
3267
3268 st = sorttab[inst];
3269
3270 /* Sanity */
3271 if (!st)
3272 return;
3273
3274 cal_xml = gtkpod_xml_new(xml_file, "calendar_window");
3275
3276 glade_xml_signal_autoconnect(cal_xml);
3277
3278 cal = gtkpod_xml_get_widget(cal_xml, "calendar_window");
3279
3280 /* Set to saved size */
3281 defx = prefs_get_int("size_cal.x");
3282 defy = prefs_get_int("size_cal.y");
3283 gtk_window_set_default_size(GTK_WINDOW (cal), defx, defy);
3284
3285 /* Set sorttab number */
3286 w = gtkpod_xml_get_widget(cal_xml, "sorttab_num_spin");
3287 gtk_spin_button_set_range(GTK_SPIN_BUTTON (w),
3288 1, SORT_TAB_MAX);
3289 gtk_spin_button_set_value(GTK_SPIN_BUTTON (w), inst + 1);
3290
3291 /* Set Category-Combo */
3292 w = gtkpod_xml_get_widget(cal_xml, "cat_combo");
3293
3294 switch (item) {
3295 case T_TIME_PLAYED:
3296 index = CAT_STRING_PLAYED;
3297 break;
3298 case T_TIME_MODIFIED:
3299 index = CAT_STRING_MODIFIED;
3300 break;
3301 case T_TIME_ADDED:
3302 index = CAT_STRING_ADDED;
3303 break;
3304 default:
3305 fprintf(stderr, "Programming error: cal_open_calendar() -- item not found\n");
3306 break;
3307 }
3308
3309 gtk_combo_box_set_active(GTK_COMBO_BOX (w), index);
3310
3311 /* Make sure we use the current contents of the entry */
3312 sp_store_sp_entries(inst);
3313 /* set calendar */
3314 ti = sp_update_date_interval_from_string(inst, item, TRUE);
3315
3316 /* set the calendar if we have a valid TimeInfo */
3317 if (ti) {
3318 if (!ti->valid) { /* set to reasonable default */
3319 ti->lower = 0;
3320 ti->upper = 0;
3321 }
3322
3323 /* Lower Margin */
3324 w = gtkpod_xml_get_widget(cal_xml, "no_lower_margin");
3325 g_signal_connect (w,
3326 "toggled",
3327 G_CALLBACK (cal_no_margin_toggled),
3328 cal);
3329 w = gtkpod_xml_get_widget(cal_xml, "lower_time");
3330 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (w), TRUE);
3331 g_signal_connect (w,
3332 "toggled",
3333 G_CALLBACK (cal_time_toggled),
3334 cal);
3335
3336 cal_set_time (cal, LOWER_MARGIN, ti->lower);
3337
3338 /* Upper Margin */
3339 w = gtkpod_xml_get_widget (cal_xml, "no_upper_margin");
3340 g_signal_connect (w,
3341 "toggled",
3342 G_CALLBACK (cal_no_margin_toggled),
3343 cal);
3344 w = gtkpod_xml_get_widget (cal_xml, "upper_time");
3345 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), TRUE);
3346 g_signal_connect (w,
3347 "toggled",
3348 G_CALLBACK (cal_time_toggled),
3349 cal);
3350 cal_set_time (cal, UPPER_MARGIN, ti->upper);
3351 }
3352
3353 /* Connect delete-event */
3354 g_signal_connect (cal, "delete_event",
3355 G_CALLBACK (cal_delete_event), cal);
3356 /* Connect cancel-button */
3357 g_signal_connect (gtkpod_xml_get_widget (cal_xml, "cal_cancel"), "clicked",
3358 G_CALLBACK (cal_cancel), cal);
3359 /* Connect apply-button */
3360 g_signal_connect (gtkpod_xml_get_widget (cal_xml, "cal_apply"), "clicked",
3361 G_CALLBACK (cal_apply), cal);
3362 /* Connect ok-button */
3363 g_signal_connect (gtkpod_xml_get_widget (cal_xml, "cal_ok"), "clicked",
3364 G_CALLBACK (cal_ok), cal);
3365
3366 gtk_widget_show (cal);
3367 }
3368