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