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 <string.h>
34 #include <stdlib.h>
35 #include <gdk/gdkkeysyms.h>
36 
37 #include "display_private.h"
38 #include "prefs.h"
39 #include "misc.h"
40 #include "misc_track.h"
41 #include "info.h"
42 #include "context_menus.h"
43 #include "display_photo.h"
44 #include "stock_icons.h"
45 
46 /* pointer to the treeview for the playlist display */
47 static GtkTreeView *playlist_treeview = NULL;
48 /* pointer to the currently selected playlist */
49 static Playlist *current_playlist = NULL;
50 /* pointer to the currently selected itdb */
51 static iTunesDB *current_itdb = NULL;
52 /* flag set if selection changes to be ignored temporarily */
53 static gboolean pm_selection_blocked = FALSE;
54 
55 
56 /* Drag and drop definitions */
57 static GtkTargetEntry pm_drag_types [] = {
58     { DND_GTKPOD_PLAYLISTLIST_TYPE, 0, DND_GTKPOD_PLAYLISTLIST },
59     { "text/uri-list", 0, DND_TEXT_URI_LIST },
60     { "text/plain", 0, DND_TEXT_PLAIN },
61     { "STRING", 0, DND_TEXT_PLAIN }
62 };
63 static GtkTargetEntry pm_drop_types [] = {
64     { DND_GTKPOD_PLAYLISTLIST_TYPE, 0, DND_GTKPOD_PLAYLISTLIST },
65     { DND_GTKPOD_TRACKLIST_TYPE, 0, DND_GTKPOD_TRACKLIST },
66     { "text/uri-list", 0, DND_TEXT_URI_LIST },
67     { "text/plain", 0, DND_TEXT_PLAIN },
68     { "STRING", 0, DND_TEXT_PLAIN }
69 };
70 
71 
72 static void pm_rows_reordered (void);
73 static GtkTreePath *pm_get_path_for_itdb (iTunesDB *itdb);
74 static GtkTreePath *pm_get_path_for_playlist (Playlist *pl);
75 static gint pm_get_position_for_playlist (Playlist *pl);
76 static gboolean pm_get_iter_for_itdb (iTunesDB *itdb, GtkTreeIter *iter);
77 static gboolean pm_get_iter_for_playlist (Playlist *pl, GtkTreeIter *iter);
78 
79 
80 
81 /* ---------------------------------------------------------------- */
82 /* Section for playlist display                                     */
83 /* drag and drop                                                    */
84 /* ---------------------------------------------------------------- */
85 
86 
87 /* ----------------------------------------------------------------
88  *
89  * For drag and drop within the playlist view the following rules apply:
90  *
91  * 1) Drags between different itdbs: playlists are copied (moved with
92  *    CONTROL pressed)
93  *
94  * 2) Drags within the same itdb: playlist is moved (copied with CONTROL
95  *    pressed)
96  *
97  * ---------------------------------------------------------------- */
98 
99 
pm_drag_begin(GtkWidget * widget,GdkDragContext * drag_context,gpointer user_data)100 static void pm_drag_begin (GtkWidget *widget,
101 			   GdkDragContext *drag_context,
102 			   gpointer user_data)
103 {
104 /*     puts ("drag_begin"); */
105 }
106 
107 
pm_drag_data_delete_remove_playlist(GtkTreeModel * tm,GtkTreePath * tp,GtkTreeIter * iter,gpointer data)108 static void pm_drag_data_delete_remove_playlist(
109 GtkTreeModel *tm, GtkTreePath *tp,
110 GtkTreeIter *iter, gpointer data)
111 {
112     Playlist *pl;
113     g_return_if_fail (tm);
114     g_return_if_fail (iter);
115     gtk_tree_model_get (tm, iter, PM_COLUMN_PLAYLIST, &pl, -1);
116     g_return_if_fail (pl);
117     gp_playlist_remove (pl);
118 }
119 
120 /* remove dragged playlist after successful MOVE */
pm_drag_data_delete(GtkWidget * widget,GdkDragContext * drag_context,gpointer user_data)121 static void pm_drag_data_delete (GtkWidget *widget,
122 			   GdkDragContext *drag_context,
123 			   gpointer user_data)
124 {
125     g_return_if_fail (widget);
126     g_return_if_fail (drag_context);
127 
128 /*     printf ("drag_data_delete: %d\n", drag_context->action); */
129 
130     if (drag_context->action == GDK_ACTION_MOVE)
131     {
132 	GtkTreeSelection *ts = gtk_tree_view_get_selection(
133 	    GTK_TREE_VIEW (widget));
134 	gtk_tree_selection_selected_foreach (ts, pm_drag_data_delete_remove_playlist, NULL);
135     }
136 }
137 
pm_drag_drop(GtkWidget * widget,GdkDragContext * drag_context,gint x,gint y,guint time,gpointer user_data)138 static gboolean pm_drag_drop (GtkWidget *widget,
139 			      GdkDragContext *drag_context,
140 			      gint x,
141 			      gint y,
142 			      guint time,
143 			      gpointer user_data)
144 {
145     GdkAtom target;
146 
147 /*     puts ("drag_data_drop"); */
148 
149     display_remove_autoscroll_row_timeout (widget);
150 
151     target = gtk_drag_dest_find_target (widget, drag_context, NULL);
152 
153     if (target != GDK_NONE)
154     {
155 	gtk_drag_get_data (widget, drag_context, target, time);
156 	return TRUE;
157     }
158     return FALSE;
159 }
160 
pm_drag_end(GtkWidget * widget,GdkDragContext * drag_context,gpointer user_data)161 static void pm_drag_end (GtkWidget *widget,
162 			 GdkDragContext *drag_context,
163 			 gpointer user_data)
164 {
165 /*     puts ("drag_end"); */
166     display_remove_autoscroll_row_timeout (widget);
167     gtkpod_tracks_statusbar_update ();
168 }
169 
pm_drag_leave(GtkWidget * widget,GdkDragContext * drag_context,guint time,gpointer user_data)170 static void pm_drag_leave (GtkWidget *widget,
171 			   GdkDragContext *drag_context,
172 			   guint time,
173 			   gpointer user_data)
174 {
175 /*     puts ("drag_leave"); */
176     display_remove_autoscroll_row_timeout (widget);
177 }
178 
pm_drag_motion(GtkWidget * widget,GdkDragContext * dc,gint x,gint y,guint time,gpointer user_data)179 static gboolean pm_drag_motion (GtkWidget *widget,
180 				GdkDragContext *dc,
181 				gint x,
182 				gint y,
183 				guint time,
184 				gpointer user_data)
185 {
186     GtkTreeModel *model;
187     GtkTreeIter iter_d;
188     GtkTreePath *path;
189     GtkTreeViewDropPosition pos;
190     GdkAtom target;
191     guint info;
192     PM_column_type type;
193     Playlist *pl_d;
194     iTunesDB *itdb;
195     PhotoDB *photodb;
196     ExtraiTunesDBData *eitdb;
197 
198     g_return_val_if_fail (widget, FALSE);
199     g_return_val_if_fail (GTK_IS_TREE_VIEW (widget), FALSE);
200 
201     display_install_autoscroll_row_timeout (widget);
202 
203     /* no drop possible if position is not valid */
204     if (!gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
205 					    x, y, &path, &pos))
206 	return FALSE;
207 
208     g_return_val_if_fail (path, FALSE);
209 
210 /*     printf ("pm_drag_motion (x/y/pos/s/a): %d %d %d %d %d\n", */
211 /* 	    x, y, pos, dc->suggested_action, dc->actions); */
212 
213     gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), path, pos);
214 
215     /* Get destination playlist in case it's needed */
216     model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
217     g_return_val_if_fail (model, FALSE);
218 
219     if(gtk_tree_model_get_iter (model, &iter_d, path))
220     {
221 	gtk_tree_model_get (model, &iter_d,
222 			    PM_COLUMN_TYPE, &type,
223 			    PM_COLUMN_ITDB, &itdb,
224 			    PM_COLUMN_PLAYLIST, &pl_d,
225 			    PM_COLUMN_PHOTOS, &photodb,
226 			    -1);
227     }
228     g_return_val_if_fail (itdb, FALSE);
229     eitdb = itdb->userdata;
230     g_return_val_if_fail (eitdb, FALSE);
231 
232     target = gtk_drag_dest_find_target (widget, dc, NULL);
233 
234     /* no drop possible if repository is not loaded */
235     if (!eitdb->itdb_imported)
236     {
237 	gtk_tree_path_free (path);
238 	gdk_drag_status (dc, 0, time);
239 	return FALSE;
240     }
241 
242     /* no drop possible if no valid target can be found */
243     if (target == GDK_NONE)
244     {
245 	gtk_tree_path_free (path);
246 	gdk_drag_status (dc, 0, time);
247 	return FALSE;
248     }
249 
250     /* no drop possible _before_ MPL */
251     if (gtk_tree_path_get_depth (path) == 1)
252     {   /* MPL */
253 	if (pos == GTK_TREE_VIEW_DROP_BEFORE)
254 	{
255 	    gtk_tree_path_free (path);
256 	    gdk_drag_status (dc, 0, time);
257 	    return FALSE;
258 	}
259     }
260 
261     /* get 'info'-id from target-atom */
262     if (!gtk_target_list_find (
263 	    gtk_drag_dest_get_target_list (widget), target, &info))
264     {
265 	gtk_tree_path_free (path);
266 	gdk_drag_status (dc, 0, time);
267 	return FALSE;
268     }
269 
270     switch (type)
271     {
272     case PM_COLUMN_PLAYLIST:
273 	switch (info)
274 	{
275 	case DND_GTKPOD_PLAYLISTLIST:
276 	    /* need to consult drag data to decide */
277 	    g_object_set_data (G_OBJECT (widget), "drag_data_by_motion_path", path);
278 	    g_object_set_data (G_OBJECT (widget), "drag_data_by_motion_pos", (gpointer)pos);
279 	    gtk_drag_get_data (widget, dc, target, time);
280 	    return TRUE;
281 	case DND_GTKPOD_TRACKLIST:
282 	    /* do not allow drop into currently selected playlist */
283 	    if (pl_d == pm_get_selected_playlist ())
284 	    {
285 		if ((pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE) ||
286 		    (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER))
287 		{
288 		    gtk_tree_path_free (path);
289 		    gdk_drag_status (dc, 0, time);
290 		    return FALSE;
291 		}
292 	    }
293 	    /* need to consult drag data to decide */
294 	    g_object_set_data (G_OBJECT (widget), "drag_data_by_motion_path", path);
295 	    g_object_set_data (G_OBJECT (widget), "drag_data_by_motion_pos", (gpointer)pos);
296 	    gtk_drag_get_data (widget, dc, target, time);
297 	    return TRUE;
298 	case DND_TEXT_PLAIN:
299 	case DND_TEXT_URI_LIST:
300 	    gdk_drag_status (dc, dc->suggested_action, time);
301 	    gtk_tree_path_free (path);
302 	    return TRUE;
303 	default:
304 	    g_warning ("Programming error: pm_drag_motion received unknown info type (%d)\n", info);
305 	    gtk_tree_path_free (path);
306 	    return FALSE;
307 	}
308 	g_return_val_if_reached (FALSE);
309 
310     case PM_COLUMN_PHOTOS:
311 	/* We don't handle drops into the photo playlist yet */
312 	return FALSE;
313     case PM_NUM_COLUMNS:
314     case PM_COLUMN_ITDB:
315     case PM_COLUMN_TYPE:
316 	g_return_val_if_reached (FALSE);
317     }
318     g_return_val_if_reached (FALSE);
319 }
320 
321 
322 
323 /*
324  * utility function for appending file for track view (DND)
325  */
326 static void
on_pm_dnd_get_file_foreach(GtkTreeModel * tm,GtkTreePath * tp,GtkTreeIter * iter,gpointer data)327 on_pm_dnd_get_file_foreach(GtkTreeModel *tm, GtkTreePath *tp,
328 			   GtkTreeIter *iter, gpointer data)
329 {
330     Playlist *pl=NULL;
331     GList *gl;
332     GString *filelist = data;
333 
334     g_return_if_fail (tm);
335     g_return_if_fail (iter);
336     g_return_if_fail (data);
337 
338     /* get current playlist */
339     gtk_tree_model_get (tm, iter, PM_COLUMN_PLAYLIST, &pl, -1);
340     g_return_if_fail (pl);
341 
342     for (gl=pl->members; gl; gl=gl->next)
343     {
344 	gchar *name;
345 	Track *track = gl->data;
346 
347 	g_return_if_fail (track);
348 	name = get_file_name_from_source (track, SOURCE_PREFER_LOCAL);
349 	if (name)
350 	{
351 	    g_string_append_printf (filelist, "file:%s\n", name);
352 	    g_free (name);
353 	}
354     }
355 }
356 
357 
358 /*
359  * utility function for appending uris for track view (DND)
360  */
361 static void
on_pm_dnd_get_uri_foreach(GtkTreeModel * tm,GtkTreePath * tp,GtkTreeIter * iter,gpointer data)362 on_pm_dnd_get_uri_foreach(GtkTreeModel *tm, GtkTreePath *tp,
363 			  GtkTreeIter *iter, gpointer data)
364 {
365     Playlist *pl=NULL;
366     GList *gl;
367     GString *filelist = data;
368 
369     g_return_if_fail (tm);
370     g_return_if_fail (iter);
371     g_return_if_fail (data);
372 
373     /* get current playlist */
374     gtk_tree_model_get (tm, iter, PM_COLUMN_PLAYLIST, &pl, -1);
375     g_return_if_fail (pl);
376 
377     for (gl=pl->members; gl; gl=gl->next)
378     {
379 	gchar *name;
380 	Track *track = gl->data;
381 
382 	g_return_if_fail (track);
383 	name = get_file_name_from_source (track, SOURCE_PREFER_LOCAL);
384 	if (name)
385 	{
386 	    gchar *uri = g_filename_to_uri (name, NULL, NULL);
387 	    if (uri)
388 	    {
389 		g_string_append_printf (filelist, "file:%s\n", name);
390 		g_free (uri);
391 	    }
392 	    g_free (name);
393 	}
394     }
395 }
396 
397 
398 /*
399  * utility function for appending pointers to a playlist (DND)
400  */
401 static void
on_pm_dnd_get_playlist_foreach(GtkTreeModel * tm,GtkTreePath * tp,GtkTreeIter * iter,gpointer data)402 on_pm_dnd_get_playlist_foreach(GtkTreeModel *tm, GtkTreePath *tp,
403 			       GtkTreeIter *iter, gpointer data)
404 {
405     Playlist *pl=NULL;
406     GString *playlistlist = (GString *)data;
407 
408     g_return_if_fail (tm);
409     g_return_if_fail (iter);
410     g_return_if_fail (playlistlist);
411 
412     gtk_tree_model_get (tm, iter, PM_COLUMN_PLAYLIST, &pl, -1);
413     g_return_if_fail (pl);
414     g_string_append_printf (playlistlist, "%p\n", pl);
415 }
416 
417 
418 
419 static void
pm_drag_data_get(GtkWidget * widget,GdkDragContext * dc,GtkSelectionData * data,guint info,guint time,gpointer user_data)420 pm_drag_data_get (GtkWidget       *widget,
421 		  GdkDragContext  *dc,
422 		  GtkSelectionData *data,
423 		  guint            info,
424 		  guint            time,
425 		  gpointer         user_data)
426 {
427     GtkTreeSelection *ts;
428     GString *reply = g_string_sized_new (2000);
429 
430     if (!data) return;
431 
432 /*     puts ("data_get"); */
433 
434     /* printf("sm drag get info: %d\n", info);*/
435 
436     ts = gtk_tree_view_get_selection(GTK_TREE_VIEW (widget));
437     if (ts)
438     {
439 	switch (info)
440 	{
441 	case DND_GTKPOD_PLAYLISTLIST:
442 	    gtk_tree_selection_selected_foreach(ts,
443 				    on_pm_dnd_get_playlist_foreach, reply);
444 	    break;
445 	case DND_TEXT_PLAIN:
446 	    gtk_tree_selection_selected_foreach(ts,
447 				    on_pm_dnd_get_file_foreach, reply);
448 	    break;
449 	case DND_TEXT_URI_LIST:
450 	    gtk_tree_selection_selected_foreach(ts,
451 				    on_pm_dnd_get_uri_foreach, reply);
452 	    break;
453 	default:
454 	    g_warning ("Programming error: pm_drag_data_get received unknown info type (%d)\n", info);
455 	    break;
456 	}
457     }
458     gtk_selection_data_set(data, data->target, 8, reply->str, reply->len);
459     g_string_free (reply, TRUE);
460 }
461 
462 
463 /* get the action to be used for a drag and drop within the playlist
464  * view */
pm_pm_get_action(Playlist * src,Playlist * dest,GtkWidget * widget,GtkTreeViewDropPosition pos,GdkDragContext * dc)465 static GdkDragAction pm_pm_get_action (Playlist *src, Playlist *dest,
466 				       GtkWidget *widget,
467 				       GtkTreeViewDropPosition pos,
468 				       GdkDragContext *dc)
469 {
470     GdkModifierType mask;
471 
472     g_return_val_if_fail (src, 0);
473     g_return_val_if_fail (dest, 0);
474     g_return_val_if_fail (widget, 0);
475     g_return_val_if_fail (dc, 0);
476 
477     /* get modifier mask */
478     gdk_window_get_pointer (
479 	gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget)),
480 	NULL, NULL, &mask);
481 
482     /* don't allow copy/move before the MPL */
483     if ((itdb_playlist_is_mpl (dest)) &&
484 	(pos == GTK_TREE_VIEW_DROP_BEFORE))
485 	return 0;
486 
487     /* don't allow moving of MPL */
488     if (itdb_playlist_is_mpl (src))
489 	return GDK_ACTION_COPY;
490 
491     /* don't allow drop onto itself */
492     if ((src == dest) &&
493 	((pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER) ||
494 	 (pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)))
495 	return 0;
496 
497     if (src->itdb == dest->itdb)
498     {   /* DND within the same itdb */
499         /* don't allow copy/move onto MPL */
500 	if ((itdb_playlist_is_mpl (dest)) &&
501 	    (pos != GTK_TREE_VIEW_DROP_AFTER))
502 	    return 0;
503 
504 	/* DND within the same itdb -> default is moving, shift means
505 	   copying */
506 	if (mask & GDK_SHIFT_MASK)
507 	{
508 	    return GDK_ACTION_COPY;
509 	}
510 	else
511 	{   /* don't allow move if view is sorted */
512 	    gint column;
513 	    GtkSortType order;
514 	    GtkTreeModel *model;
515 	    GtkWidget *src_widget = gtk_drag_get_source_widget (dc);
516 	    g_return_val_if_fail (src_widget, 0);
517 	    model = gtk_tree_view_get_model (GTK_TREE_VIEW(src_widget));
518 	    g_return_val_if_fail (model, 0);
519 	    if (gtk_tree_sortable_get_sort_column_id (
520 		    GTK_TREE_SORTABLE (model), &column, &order))
521 	    {
522 		return 0;
523 	    }
524 	    else
525 	    {
526 		return GDK_ACTION_MOVE;
527 	    }
528 	}
529     }
530     else
531     {   /* DND between different itdbs */
532 	/* Do not allow drags from the iPod in offline mode */
533 	if (get_offline (src->itdb) &&
534 	    (src->itdb->usertype & GP_ITDB_TYPE_IPOD))
535 	{   /* give a notice on the statusbar -- otherwise the user
536 	     * will never know why the drag is not possible */
537 	    gtkpod_statusbar_message (_("Error: drag from iPod not possible in offline mode."));
538 	    return 0;
539 	}
540 	/* default is copying, shift means moving */
541 	if (mask & GDK_SHIFT_MASK)
542 	    return GDK_ACTION_MOVE;
543 	else
544 	    return GDK_ACTION_COPY;
545     }
546 }
547 
548 
549 /* get the action to be used for a drag and drop from the track view
550  * or filter tab view to the playlist view */
pm_tm_get_action(Track * src,Playlist * dest,GtkTreeViewDropPosition pos,GdkDragContext * dc)551 static GdkDragAction pm_tm_get_action (Track *src, Playlist *dest,
552 				       GtkTreeViewDropPosition pos,
553 				       GdkDragContext *dc)
554 {
555     g_return_val_if_fail (src, 0);
556     g_return_val_if_fail (dest, 0);
557     g_return_val_if_fail (dc, 0);
558 
559 
560     /* don't allow copy/move before the MPL */
561     if ((itdb_playlist_is_mpl (dest)) &&
562 	(pos == GTK_TREE_VIEW_DROP_BEFORE))
563 	return 0;
564 
565     if (src->itdb == dest->itdb)
566     {   /* DND within the same itdb */
567         /* don't allow copy/move onto MPL */
568 	if ((itdb_playlist_is_mpl (dest)) &&
569 	    (pos != GTK_TREE_VIEW_DROP_AFTER))
570 	    return 0;
571     }
572     else
573     {   /* drag between different itdbs */
574 	/* Do not allow drags from the iPod in offline mode */
575 	if (get_offline (src->itdb) &&
576 	    (src->itdb->usertype & GP_ITDB_TYPE_IPOD))
577 	{   /* give a notice on the statusbar -- otherwise the user
578 	     * will never know why the drag is not possible */
579 	    gtkpod_statusbar_message (_("Error: drag from iPod not possible in offline mode."));
580 	    return 0;
581 	}
582     }
583     /* otherwise: do as suggested */
584     return dc->suggested_action;
585 }
586 
587 
588 /* Print a message about the number of tracks copied (the number of
589    tracks moved is printed in tm_drag_data_delete() */
pm_tm_tracks_moved_or_copied(gchar * tracks,gboolean moved)590 static void pm_tm_tracks_moved_or_copied (gchar *tracks, gboolean moved)
591 {
592     g_return_if_fail (tracks);
593     if (!moved)
594     {
595 	gchar *ptr = tracks;
596 	gint n = 0;
597 
598 	/* count the number of tracks */
599 	while ((ptr=strchr (ptr, '\n')))
600 	{
601 	    ++n;
602 	    ++ptr;
603 	}
604 	/* display message in statusbar */
605 	gtkpod_statusbar_message (
606 	    ngettext ("Copied one track",
607 		      "Copied %d tracks", n), n);
608     }
609 }
610 
611 
pm_drag_data_received(GtkWidget * widget,GdkDragContext * dc,gint x,gint y,GtkSelectionData * data,guint info,guint time,gpointer user_data)612 static void pm_drag_data_received (GtkWidget       *widget,
613 				   GdkDragContext  *dc,
614 				   gint             x,
615 				   gint             y,
616 				   GtkSelectionData *data,
617 				   guint            info,
618 				   guint            time,
619 				   gpointer         user_data)
620 {
621     GtkTreeIter iter_d, iter_s;
622     GtkTreePath *path_d=NULL;
623     GtkTreePath *path_m;
624     GtkTreeModel *model;
625     GtkTreeViewDropPosition pos = 0;
626     gint position = -1;
627     Playlist *pl, *pl_s, *pl_d;
628     Track *tr_s = NULL;
629     gboolean path_ok;
630     gboolean del_src;
631 
632 /* printf ("drag_data_received: x y a: %d %d %d\n", x, y, dc->suggested_action); */
633 
634     g_return_if_fail (widget);
635     g_return_if_fail (dc);
636     g_return_if_fail (data);
637     g_return_if_fail (data->length > 0);
638     g_return_if_fail (data->data);
639     g_return_if_fail (data->format == 8);
640 
641 /* puts(gtk_tree_path_to_string (path)); */
642 
643     model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
644     g_return_if_fail (model);
645 
646     path_m = g_object_get_data (G_OBJECT (widget), "drag_data_by_motion_path");
647 
648     if (path_m)
649     {   /* this callback was caused by pm_drag_motion -- we are
650 	 * supposed to call gdk_drag_status () */
651 /* puts ("...by motion"); */
652         pos = (GtkTreeViewDropPosition)g_object_get_data (G_OBJECT (widget), "drag_data_by_motion_pos");
653         /* unset flag that */
654 	g_object_set_data (G_OBJECT (widget), "drag_data_by_motion_path", NULL);
655 	g_object_set_data (G_OBJECT (widget), "drag_data_by_motion_pos", NULL);
656 	if(gtk_tree_model_get_iter (model, &iter_d, path_m))
657 	{
658 	    gtk_tree_model_get (model, &iter_d, PM_COLUMN_PLAYLIST, &pl, -1);
659 	}
660 	gtk_tree_path_free (path_m);
661 
662 	g_return_if_fail (pl);
663 
664 	switch (info)
665 	{
666 	case DND_GTKPOD_TRACKLIST:
667 	    /* get first track and check itdb */
668 	    sscanf (data->data, "%p", &tr_s);
669 	    if (!tr_s)
670 	    {
671 		gdk_drag_status (dc, 0, time);
672 		g_return_if_reached ();
673 	    }
674 	    gdk_drag_status (dc,
675 			     pm_tm_get_action (tr_s, pl, pos, dc),
676 			     time);
677 /* 	    printf ("src: %p  dest: %p  sugg: %d a:%d\n", */
678 /* 		    pl_s->itdb, pl->itdb, dc->suggested_action, */
679 /* 		    pm_tm_get_action (tr_s, pl, pos, dc)); */
680 	    return;
681 	case DND_GTKPOD_PLAYLISTLIST:
682 	    /* get first playlist and check itdb */
683 	    sscanf (data->data, "%p", &pl_s);
684 	    if (!pl_s)
685 	    {
686 		gdk_drag_status (dc, 0, time);
687 		g_return_if_reached ();
688 	    }
689 	    gdk_drag_status (dc,
690 			     pm_pm_get_action (pl_s, pl, widget, pos, dc),
691 			     time);
692 /* 	    printf ("src: %p  dest: %p  sugg: %d a:%d\n", */
693 /* 		    pl_s->itdb, pl->itdb, dc->suggested_action, */
694 /* 		    pm_pm_get_action (pl_s, pl, widget, pos, dc)); */
695 	    return;
696 	}
697 	g_return_if_reached ();
698 	return;
699     }
700 
701 /*     printf ("treeview received drag data/length/format: %p/%d/%d\n", data, data?data->length:0, data?data->format:0); */
702 /*     printf ("treeview received drag context/actions/suggested action: %p/%d/%d\n", context, context?context->actions:0, context?context->suggested_action:0); */
703 
704     display_remove_autoscroll_row_timeout (widget);
705 
706     path_ok = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW(widget),
707 						 x, y, &path_d, &pos);
708 
709     /* return if drop path is invalid */
710     if (!path_ok)
711     {
712 	gtk_drag_finish (dc, FALSE, FALSE, time);
713 	return;
714     }
715     g_return_if_fail (path_d);
716 
717     if(gtk_tree_model_get_iter (model, &iter_d, path_d))
718     {
719 	gtk_tree_model_get (model, &iter_d, PM_COLUMN_PLAYLIST, &pl, -1);
720     }
721     gtk_tree_path_free (path_d);
722     path_d = NULL;
723 
724     g_return_if_fail (pl);
725 
726     position = pm_get_position_for_playlist (pl);
727 
728 /*  printf("position: %d\n", position); */
729     switch (info)
730     {
731     case DND_GTKPOD_TRACKLIST:
732 	/* get first track */
733 	sscanf (data->data, "%p", &tr_s);
734 	if (!tr_s)
735 	{
736 	    gtk_drag_finish (dc, FALSE, FALSE, time);
737 	    g_return_if_reached ();
738 	}
739 
740 	/* Find out action */
741 	dc->action = pm_tm_get_action (tr_s, pl, pos, dc);
742 
743 	if (dc->action & GDK_ACTION_MOVE)
744 	    del_src = TRUE;
745 	else del_src = FALSE;
746 
747 	if ((pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE) ||
748 	    (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER))
749 	{ /* drop into existing playlist */
750 	    /* copy files from iPod if necessary */
751 	    GList *trackglist =
752 		export_tracklist_when_necessary (tr_s->itdb,
753 						 pl->itdb,
754 						 data->data);
755 	    if (trackglist)
756 	    {
757 		add_trackglist_to_playlist (pl, trackglist);
758 		g_list_free (trackglist);
759 		trackglist = NULL;
760 		pm_tm_tracks_moved_or_copied (data->data, del_src);
761 		gtk_drag_finish (dc, TRUE, del_src, time);
762 	    }
763 	    else
764 	    {
765 		gtk_drag_finish (dc, FALSE, FALSE, time);
766 	    }
767 	}
768 	else
769 	{ /* drop between playlists */
770 	    Playlist *plitem;
771 	    /* adjust position */
772 	    if (pos == GTK_TREE_VIEW_DROP_AFTER)
773 		plitem = add_new_pl_user_name (pl->itdb, NULL, position+1);
774 	    else
775 		plitem = add_new_pl_user_name (pl->itdb, NULL, position);
776 
777 	    if (plitem)
778 	    {
779 		/* copy files from iPod if necessary */
780 		GList *trackglist =
781 		    export_tracklist_when_necessary (tr_s->itdb,
782 						     pl->itdb,
783 						     data->data);
784 		if (trackglist)
785 		{
786 		    add_trackglist_to_playlist (plitem, trackglist);
787 		    g_list_free (trackglist);
788 		    trackglist = NULL;
789 		    pm_tm_tracks_moved_or_copied (data->data, del_src);
790 		    gtk_drag_finish (dc, TRUE, del_src, time);
791 		}
792 		else
793 		{
794 		    gp_playlist_remove (plitem);
795 		    plitem = NULL;
796 		    gtk_drag_finish (dc, FALSE, FALSE, time);
797 		}
798 	    }
799 	    else
800 	    {
801 		gtk_drag_finish (dc, FALSE, FALSE, time);
802 	    }
803 	}
804 	break;
805     case DND_TEXT_URI_LIST:
806     case DND_TEXT_PLAIN:
807 	if ((pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE) ||
808 	    (pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER))
809 	{ /* drop into existing playlist */
810 	    add_text_plain_to_playlist (pl->itdb, pl, data->data,
811 					0, NULL, NULL);
812 	    dc->action = GDK_ACTION_COPY;
813 	    gtk_drag_finish (dc, TRUE, FALSE, time);
814 	}
815 	else
816 	{ /* drop between playlists */
817 	    Playlist *plitem;
818 	    if (pos == GTK_TREE_VIEW_DROP_AFTER)
819 		plitem = add_text_plain_to_playlist (
820 		    pl->itdb, NULL, data->data, position+1, NULL, NULL);
821 	    else
822 		plitem = add_text_plain_to_playlist (
823 		    pl->itdb, NULL, data->data, position, NULL, NULL);
824 
825 	    if (plitem)
826 	    {
827 		dc->action = GDK_ACTION_COPY;
828 		gtk_drag_finish (dc, TRUE, FALSE, time);
829 	    }
830 	    else
831 	    {
832 		dc->action = 0;
833 		gtk_drag_finish (dc, FALSE, FALSE, time);
834 	    }
835 	}
836 	break;
837     case DND_GTKPOD_PLAYLISTLIST:
838 	/* get first playlist and check action */
839 	sscanf (data->data, "%p", &pl_s);
840 	if (!pl_s)
841 	{
842 	    gtk_drag_finish (dc, FALSE, FALSE, time);
843 	    g_return_if_reached ();
844 	}
845 
846 	dc->action = pm_pm_get_action (pl_s, pl, widget, pos, dc);
847 
848 	if (dc->action == 0)
849 	{
850 	    gtk_drag_finish (dc, FALSE, FALSE, time);
851 	    return;
852 	}
853 
854 	if (pl->itdb == pl_s->itdb)
855 	{   /* handle DND within the same itdb */
856 	    switch (dc->action)
857 	    {
858 	    case GDK_ACTION_COPY:
859 		/* if copy-drop is between two playlists, create new
860 		 * playlist */
861 		switch (pos)
862 		{
863 		case GTK_TREE_VIEW_DROP_BEFORE:
864 		    pl_d = itdb_playlist_duplicate (pl_s);
865 		    gp_playlist_add (pl->itdb, pl_d, position);
866 		    break;
867 		case GTK_TREE_VIEW_DROP_AFTER:
868 		    pl_d = itdb_playlist_duplicate (pl_s);
869 		    gp_playlist_add (pl->itdb, pl_d, position+1);
870 		    break;
871 		default:
872 		    pl_d = pl;
873 		    if (pl_d != pl_s)
874 			add_trackglist_to_playlist (pl_d, pl_s->members);
875 		    break;
876 		}
877 		gtk_drag_finish (dc, TRUE, FALSE, time);
878 		break;
879 	    case GDK_ACTION_MOVE:
880 		pm_get_iter_for_playlist (pl_s, &iter_s);
881 		switch (pos)
882 		{
883 		case GTK_TREE_VIEW_DROP_BEFORE:
884 		    if (prefs_get_int("pm_sort") != SORT_NONE)
885 		    {
886 			gtkpod_statusbar_message (_("Can't reorder sorted treeview."));
887 			gtk_drag_finish (dc, FALSE, FALSE, time);
888 			return;
889 		    }
890 		    gtk_tree_store_move_before (GTK_TREE_STORE (model),
891 						&iter_s, &iter_d);
892 		    pm_rows_reordered ();
893 		    gtk_drag_finish (dc, TRUE, FALSE, time);
894 		    break;
895 		case GTK_TREE_VIEW_DROP_AFTER:
896 		    if (prefs_get_int("pm_sort") != SORT_NONE)
897 		    {
898 			gtkpod_statusbar_message (_("Can't reorder sorted treeview."));
899 			gtk_drag_finish (dc, FALSE, FALSE, time);
900 			return;
901 		    }
902 		    gtk_tree_store_move_after (GTK_TREE_STORE (model),
903 					       &iter_s, &iter_d);
904 		    pm_rows_reordered ();
905 		    gtk_drag_finish (dc, TRUE, FALSE, time);
906 		    break;
907 		default:
908 		    pl_d = pl;
909 		    if (pl_d != pl_s)
910 			add_trackglist_to_playlist (pl_d, pl_s->members);
911 		    gtk_drag_finish (dc, TRUE, FALSE, time);
912 		    break;
913 		}
914 		break;
915 	    default:
916 		gtk_drag_finish (dc, FALSE, FALSE, time);
917 		g_return_if_reached ();
918 	    }
919 	}
920 	else
921 	{   /*handle DND between two itdbs */
922 	    /* set destination pl */
923 	    GList *trackglist = NULL;
924 	    pl_d = pl;
925 	    /* if drop is between two playlists, create new playlist */
926 	    /* FIXME: support copying of SPL data? */
927 	    if (pos == GTK_TREE_VIEW_DROP_BEFORE)
928 		pl_d = gp_playlist_add_new (pl->itdb, pl_s->name,
929 					    FALSE, position);
930 	    if (pos == GTK_TREE_VIEW_DROP_AFTER)
931 		pl_d = gp_playlist_add_new (pl->itdb, pl_s->name,
932 					    FALSE, position+1);
933 	    g_return_if_fail (pl_d);
934 
935 	    /* copy files from iPod if necessary */
936 	    trackglist = export_trackglist_when_necessary (pl_s->itdb,
937 							   pl_d->itdb,
938 							   pl_s->members);
939 
940 	    /* check if copying went fine (trackglist is empty if
941 	       pl_s->members is empty, so this must not be counted as
942 	       an error */
943 	    if (trackglist || !pl_s->members)
944 	    {
945 		add_trackglist_to_playlist (pl_d, trackglist);
946 		g_list_free (trackglist);
947 		trackglist = NULL;
948 		switch (dc->action)
949 		{
950 		case GDK_ACTION_MOVE:
951 		    gtk_drag_finish (dc, TRUE, TRUE, time);
952 		    break;
953 		case GDK_ACTION_COPY:
954 		    gtk_drag_finish (dc, TRUE, FALSE, time);
955 		    break;
956 		default:
957 		    gtk_drag_finish (dc, FALSE, FALSE, time);
958 		    break;
959 		}
960 	    }
961 	    else
962 	    {
963 		if (pl_d != pl)
964 		{   /* remove newly created playlist */
965 		    gp_playlist_remove (pl_d);
966 		    pl_d = NULL;
967 		}
968 		gtk_drag_finish (dc, FALSE, FALSE, time);
969 	    }
970 
971 	}
972 	pm_rows_reordered ();
973 	break;
974     default:
975 	gtkpod_warning (_("This DND type (%d) is not (yet) supported. If you feel implementing this would be useful, please contact the author.\n\n"), info);
976 	gtk_drag_finish (dc, FALSE, FALSE, time);
977 	break;
978     }
979 
980     /* display if any duplicates were skipped */
981     gp_duplicate_remove (NULL, NULL);
982 }
983 
984 
985 
986 /* ---------------------------------------------------------------- */
987 /* Section for playlist display                                     */
988 /* other callbacks                                                  */
989 /* ---------------------------------------------------------------- */
990 
991 static gboolean
on_playlist_treeview_key_release_event(GtkWidget * widget,GdkEventKey * event,gpointer user_data)992 on_playlist_treeview_key_release_event (GtkWidget       *widget,
993 					GdkEventKey     *event,
994 					gpointer         user_data)
995 {
996     guint mods;
997 
998     mods = event->state;
999 
1000     if(!widgets_blocked && (mods & GDK_CONTROL_MASK))
1001     {
1002 	iTunesDB *itdb = gp_get_selected_itdb();
1003 
1004 	switch(event->keyval)
1005 	{
1006 /* 	    case GDK_u: */
1007 /* 		gp_do_selected_playlist (update_tracks); */
1008 /* 		break; */
1009 	    case GDK_n:
1010 		if (itdb)
1011 		{
1012 		    add_new_pl_or_spl_user_name (itdb, NULL, -1);
1013 		}
1014 		else
1015 		{
1016 		    message_sb_no_itdb_selected ();
1017 		}
1018 		break;
1019 	    default:
1020 		break;
1021 	}
1022 
1023     }
1024   return FALSE;
1025 }
1026 
1027 
1028 /* ---------------------------------------------------------------- */
1029 /* Section for playlist display helper functions                    */
1030 /* ---------------------------------------------------------------- */
1031 
1032 
1033 /* Find the iter that represents the repository @itdb
1034  *
1035  * Return TRUE if the repository could be found. In that case @itdb_iter
1036  * will be set to the corresponding iter. The value of @itdb_iter is
1037  * undefined when the repository couldn't be found, in which case FALSE
1038  * is returned. */
pm_get_iter_for_itdb(iTunesDB * itdb,GtkTreeIter * itdb_iter)1039 static gboolean pm_get_iter_for_itdb (iTunesDB *itdb, GtkTreeIter *itdb_iter)
1040 {
1041     GtkTreeModel *model;
1042 
1043     g_return_val_if_fail (playlist_treeview, FALSE);
1044     g_return_val_if_fail (itdb, FALSE);
1045     g_return_val_if_fail (itdb_iter, FALSE);
1046 
1047     model = GTK_TREE_MODEL (gtk_tree_view_get_model (playlist_treeview));
1048 
1049     if (gtk_tree_model_get_iter_first (model, itdb_iter))
1050     {
1051 	do
1052 	{
1053 	    iTunesDB *itdb_model;
1054 	    gtk_tree_model_get (model, itdb_iter,
1055 				PM_COLUMN_ITDB, &itdb_model,
1056 				-1);
1057 	    g_return_val_if_fail (itdb_model, FALSE);
1058 	    if (itdb == itdb_model)
1059 	    {
1060 		return TRUE;
1061 	    }
1062 	} while (gtk_tree_model_iter_next (model, itdb_iter));
1063     }
1064     return FALSE;
1065 }
1066 
1067 
1068 /* Find the iter that contains Playlist @playlist
1069  *
1070  * Return TRUE if the playlist could be found. In that case @pl_iter
1071  * will be set to the corresponding iter. The value of @pl_iter is
1072  * undefined when the playlist couldn't be found, in which case FALSE
1073  * is returned. */
pm_get_iter_for_playlist(Playlist * playlist,GtkTreeIter * pl_iter)1074 static gboolean pm_get_iter_for_playlist (Playlist *playlist, GtkTreeIter *pl_iter)
1075 {
1076     GtkTreeIter itdb_iter;
1077 
1078     g_return_val_if_fail (playlist_treeview, FALSE);
1079     g_return_val_if_fail (playlist, FALSE);
1080     g_return_val_if_fail (pl_iter, FALSE);
1081 
1082     /* First get the iter with the itdb in it */
1083 
1084     if (pm_get_iter_for_itdb (playlist->itdb, &itdb_iter))
1085     {
1086 	GtkTreeModel *model;
1087 	Playlist *pl;
1088 
1089 	model = GTK_TREE_MODEL (gtk_tree_view_get_model (playlist_treeview));
1090 
1091 	/* Check if this is already the right iter */
1092 	gtk_tree_model_get (model, &itdb_iter,
1093 			    PM_COLUMN_PLAYLIST, &pl,
1094 			    -1);
1095 
1096 	if (pl == playlist)
1097 	{
1098 	    *pl_iter = itdb_iter;
1099 	    return TRUE;
1100 	}
1101 
1102 	/* no -- go down one hierarchy and try all other iters */
1103 	if (!gtk_tree_model_iter_children (model, pl_iter, &itdb_iter))
1104 	{   /* This indicates screwed up programming so we better cry
1105 	       out */
1106 	    g_return_val_if_reached (FALSE);
1107 	}
1108 
1109 	do
1110 	{
1111 	    gtk_tree_model_get (model, pl_iter,
1112 				PM_COLUMN_PLAYLIST, &pl,
1113 				-1);
1114 
1115 	    if (pl == playlist)
1116 	    {
1117 		return TRUE;
1118 	    }
1119 	} while (gtk_tree_model_iter_next (model, pl_iter));
1120     }
1121     return FALSE;
1122 }
1123 
1124 
1125 
1126 
1127 /* ---------------------------------------------------------------- */
1128 /* Section for playlist display                                     */
1129 /* ---------------------------------------------------------------- */
1130 
1131 
1132 /* remove a track from a current playlist (model) */
pm_remove_track(Playlist * playlist,Track * track)1133 void pm_remove_track (Playlist *playlist, Track *track)
1134 {
1135     g_return_if_fail (playlist);
1136     g_return_if_fail (track);
1137 
1138     /* notify sort tab if currently selected playlist is affected */
1139     if (current_playlist)
1140     {   /* only remove if selected playlist is in same itdb as track */
1141 	if (track->itdb == current_playlist->itdb)
1142 	{
1143 	    if ((playlist == current_playlist) ||
1144 		itdb_playlist_is_mpl (current_playlist))
1145 	    {
1146 	    	if (prefs_get_int (KEY_DISPLAY_COVERART))
1147 	    {
1148 	  		coverart_track_changed (track, COVERART_REMOVE_SIGNAL);
1149 	    }
1150 		st_remove_track (track, 0);
1151 	    }
1152 	}
1153     }
1154 }
1155 
1156 
1157 /* Add track to the display if it's in the currently displayed playlist.
1158  * @display: TRUE: add to track model (i.e. display it) */
pm_add_track(Playlist * playlist,Track * track,gboolean display)1159 void pm_add_track (Playlist *playlist, Track *track, gboolean display)
1160 {
1161     if (playlist == current_playlist)
1162     {
1163 	st_add_track (track, TRUE, display, 0); /* Add to first sort tab */
1164 
1165     	/* As with add_track above, only add to the playlist if it is the current one */
1166     	if (prefs_get_int (KEY_DISPLAY_COVERART))
1167 	    {
1168   			coverart_track_changed (track, COVERART_CREATE_SIGNAL);
1169 	    }
1170     }
1171 }
1172 
1173 /* One of the playlist names has changed (this happens when the
1174    iTunesDB is read */
pm_itdb_name_changed(iTunesDB * itdb)1175 void pm_itdb_name_changed (iTunesDB *itdb)
1176 {
1177   GtkTreeIter iter;
1178 
1179   g_return_if_fail (itdb);
1180 
1181   if (pm_get_iter_for_itdb (itdb, &iter))
1182   {
1183       GtkTreeModel *model;
1184       GtkTreePath *path;
1185       model = GTK_TREE_MODEL (gtk_tree_view_get_model (playlist_treeview));
1186       path = gtk_tree_model_get_path (model, &iter);
1187       gtk_tree_model_row_changed (model, path, &iter);
1188       gtk_tree_path_free (path);
1189   }
1190 }
1191 
1192 
1193 /* If a track got changed (i.e. it's ID3 entries have changed), we check
1194    if it's in the currently displayed playlist, and if yes, we notify the
1195    first sort tab of a change */
pm_track_changed(Track * track)1196 void pm_track_changed (Track *track)
1197 {
1198   if (!current_playlist) return;
1199 
1200  coverart_track_changed (track, COVERART_CHANGE_SIGNAL);
1201 
1202   /* Check if track is member of current playlist */
1203   if (g_list_find (current_playlist->members, track))
1204       st_track_changed (track, FALSE, 0);
1205 }
1206 
1207 /* Add playlist to the playlist model */
1208 /* If @position = -1: append to end */
1209 /* If @position >=0: insert at that position (count starts with MPL as
1210  * 0) */
pm_add_child(iTunesDB * itdb,PM_column_type type,gpointer item,gint pos)1211 void pm_add_child (iTunesDB *itdb, PM_column_type type, gpointer item, gint pos)
1212 {
1213   GtkTreeIter mpl_iter;
1214   GtkTreeIter *mpli = NULL;
1215   GtkTreeIter iter;
1216   GtkTreeModel *model;
1217 /*  GtkTreeSelection *selection;*/
1218 
1219   g_return_if_fail (playlist_treeview);
1220   g_return_if_fail (item);
1221   g_return_if_fail (itdb);
1222 
1223   model = GTK_TREE_MODEL (gtk_tree_view_get_model (playlist_treeview));
1224   g_return_if_fail (model);
1225 
1226   /* Find the iter with the mpl in it */
1227   if (pm_get_iter_for_itdb (itdb, &mpl_iter))
1228   {
1229       mpli = &mpl_iter;
1230   }
1231 
1232   switch (type)
1233   {
1234   case PM_COLUMN_PLAYLIST:
1235       if (itdb_playlist_is_mpl ((Playlist *)item))
1236       {   /* MPLs are always added top-level */
1237 	  mpli = NULL;
1238       }
1239       else
1240       {   /* Handle normal playlist */
1241 	  /* MPL must be set before calling this function */
1242 	  g_return_if_fail (mpli);
1243 	  if (pos == -1)
1244 	  {   /* just adding at the end will add behind the photo
1245 	       * item. Find out how many playlists there are and add
1246 	       * at the end. */
1247 	      GtkTreeIter pl_iter;
1248 	      Playlist *pl;
1249 	      pos = 0;
1250 	      	/* go down one hierarchy and try all other iters */
1251 	      if (gtk_tree_model_iter_children (model, &pl_iter, &mpl_iter))
1252 	      {
1253 		  do
1254 		  {
1255 		      gtk_tree_model_get (model, &pl_iter,
1256 					  PM_COLUMN_PLAYLIST, &pl,
1257 					  -1);
1258 		      if (pl != NULL)
1259 		      {
1260 			  ++pos;
1261 		      }
1262 		  } while (pl && gtk_tree_model_iter_next (model, &pl_iter));
1263 	      }
1264 	  }
1265 	  else
1266 	  {   /* reduce position by one because the MPL is not included in the
1267 		 tree model's count */
1268 	      --pos;
1269 	  }
1270       }
1271       break;
1272   case PM_COLUMN_PHOTOS:
1273       /* MPL must be set before calling this function */
1274       g_return_if_fail (mpli);
1275       /* always add at the end */
1276       pos = -1;
1277       break;
1278   case PM_COLUMN_ITDB:
1279   case PM_COLUMN_TYPE:
1280   case PM_NUM_COLUMNS:
1281       g_return_if_reached ();
1282   }
1283   gtk_tree_store_insert (GTK_TREE_STORE (model), &iter, mpli, pos);
1284 
1285   gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
1286 		      PM_COLUMN_ITDB, itdb,
1287 		      PM_COLUMN_TYPE, type,
1288 		      type, item,
1289 		      -1);
1290 
1291 #if 0
1292   /* If the current_playlist is "playlist", we select it. This can
1293      happen during a display_reset */
1294   if (current_playlist == playlist)
1295   {
1296       selection = gtk_tree_view_get_selection (playlist_treeview);
1297       gtk_tree_selection_select_iter (selection, &iter);
1298   }
1299 #endif
1300 /*  else if (current_playlist == NULL)
1301   {
1302       if (itdb_playlist_is_mpl(playlist) && prefs_get_int("mpl_autoselect"))
1303       {
1304 	  selection = gtk_tree_view_get_selection (playlist_treeview);
1305 	  gtk_tree_selection_select_iter (selection, &iter);
1306       }
1307   } */
1308 }
1309 
1310 
1311 
1312 /* Remove "playlist" from the display model.
1313    "select": TRUE: a new playlist is selected
1314              FALSE: no selection is taking place
1315                     (useful when quitting program) */
pm_remove_playlist(Playlist * playlist,gboolean select)1316 void pm_remove_playlist (Playlist *playlist, gboolean select)
1317 {
1318     GtkTreeModel *model;
1319     gboolean have_iter = FALSE;
1320     GtkTreeIter select_iter, delete_iter;
1321     GtkTreeSelection *ts = NULL;
1322 
1323   g_return_if_fail (playlist);
1324   model = gtk_tree_view_get_model (playlist_treeview);
1325   g_return_if_fail (model);
1326 
1327   ts = gtk_tree_view_get_selection (playlist_treeview);
1328 
1329   if (itdb_playlist_is_mpl (playlist) && (playlist->itdb == current_itdb))
1330   {   /* We are about to remove the entire itdb (playlist is MPL) and
1331        * a playlist of this itdb is selected --> clear display
1332        * (pm_unselect_playlist probably works as well, but the
1333        * unselect won't be done until later (callback)) */
1334       gphoto_change_to_photo_window (FALSE);
1335       st_init (-1, 0);
1336       current_playlist = NULL;
1337   }
1338 
1339   if (select && (current_playlist == playlist))
1340   {   /* We are about to delete the currently selected
1341 	 playlist. Try to select the next. */
1342       if (gtk_tree_selection_get_selected (ts, NULL, &select_iter))
1343       {
1344 	  GtkTreePath *path = gtk_tree_model_get_path (model, &select_iter);
1345 	  if(gtk_tree_model_iter_next (model, &select_iter))
1346 	  {
1347 	      have_iter = TRUE;
1348 	  }
1349 	  else
1350 	  {   /* no next iter -- try previous iter */
1351 	      if (gtk_tree_path_prev (path))
1352 	      {   /* OK -- make iter from it */
1353 		  gtk_tree_model_get_iter (model, &select_iter, path);
1354 		  have_iter = TRUE;
1355 	      }
1356 	  }
1357 	  gtk_tree_path_free (path);
1358       }
1359   }
1360 
1361   if (pm_get_iter_for_playlist (playlist, &delete_iter))
1362   {
1363       gtk_tree_store_remove (GTK_TREE_STORE (model), &delete_iter);
1364   }
1365 
1366   /* select our new iter !!! */
1367   if (have_iter && select)   gtk_tree_selection_select_iter(ts, &select_iter);
1368 }
1369 
1370 
1371 /* Remove all playlists from the display model */
1372 /* ATTENTION: the playlist_treeview and model might be changed by
1373    calling this function */
1374 /* @clear_sort: TRUE: clear "sortable" setting of treeview */
pm_remove_all_playlists(gboolean clear_sort)1375 void pm_remove_all_playlists (gboolean clear_sort)
1376 {
1377   GtkTreeModel *model;
1378   GtkTreeIter iter;
1379   gint column;
1380   GtkSortType order;
1381 
1382   g_return_if_fail (playlist_treeview);
1383   model = gtk_tree_view_get_model (playlist_treeview);
1384   g_return_if_fail (model);
1385 
1386   while (gtk_tree_model_get_iter_first (model, &iter))
1387   {
1388       gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
1389   }
1390   if(clear_sort &&
1391      gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (model),
1392 					   &column, &order))
1393   { /* recreate track treeview to unset sorted column */
1394       if (column >= 0)
1395       {
1396 	  pm_create_treeview ();
1397       }
1398   }
1399 }
1400 
1401 
1402 /* Select specified playlist */
pm_select_playlist(Playlist * playlist)1403 void pm_select_playlist (Playlist *playlist)
1404 {
1405     GtkTreeIter iter;
1406 
1407     g_return_if_fail (playlist_treeview);
1408     g_return_if_fail (playlist);
1409 
1410     if (pm_get_iter_for_playlist (playlist, &iter))
1411     {
1412 	GtkTreeSelection *ts;
1413 	ts = gtk_tree_view_get_selection (playlist_treeview);
1414 	gtk_tree_selection_select_iter (ts, &iter);
1415     }
1416 }
1417 
1418 
1419 /* Unselect specified playlist */
pm_unselect_playlist(Playlist * playlist)1420 void pm_unselect_playlist (Playlist *playlist)
1421 {
1422     GtkTreeIter iter;
1423 
1424     g_return_if_fail (playlist_treeview);
1425     g_return_if_fail (playlist);
1426 
1427     if (pm_get_iter_for_playlist (playlist, &iter))
1428     {
1429 	GtkTreeSelection *ts;
1430 	ts = gtk_tree_view_get_selection (playlist_treeview);
1431 	gtk_tree_selection_unselect_iter (ts, &iter);
1432     }
1433 }
1434 
1435 
1436 static gboolean
pm_selection_changed_cb(gpointer data)1437 pm_selection_changed_cb (gpointer data)
1438 {
1439 	GtkTreeModel *model;
1440 	GtkTreeIter  iter;
1441 	GtkTreeView *tree_view = GTK_TREE_VIEW (data);
1442 	GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view);
1443 
1444 #if DEBUG_TIMING
1445 	GTimeVal time;
1446 	g_get_current_time (&time);
1447 	printf ("pm_selection_changed_cb enter: %ld.%06ld sec\n",
1448 	  time.tv_sec % 3600, time.tv_usec);
1449 #endif
1450 
1451 	/* Avoid track selection errors on coverart while enacting a change
1452 	 * in playlist
1453 	 */
1454 	 coverart_block_change (TRUE);
1455 
1456 	if (gtk_tree_selection_get_selected (selection, &model, &iter) == FALSE)
1457 	{  /* no selection -> reset sort tabs */
1458 		gphoto_change_to_photo_window (FALSE);
1459 		st_init (-1, 0);
1460 		current_playlist = NULL;
1461 		current_itdb = NULL;
1462 	}
1463 	else
1464 	{
1465 	    Playlist *new_playlist = NULL;
1466 	    iTunesDB *itdb = NULL;
1467 	    PhotoDB *photodb = NULL;
1468 	    PM_column_type type=0;
1469 	    gchar *label_text;
1470 	    /* handle new selection */
1471 	    gtk_tree_model_get (model, &iter,
1472 				PM_COLUMN_TYPE, &type,
1473 				PM_COLUMN_ITDB, &itdb,
1474 				PM_COLUMN_PLAYLIST, &new_playlist,
1475 				PM_COLUMN_PHOTOS, &photodb,
1476 				-1);
1477 
1478 	    current_playlist = new_playlist;
1479 	    current_itdb = itdb;
1480 
1481 	    switch (type)
1482 	    {
1483 	    case PM_COLUMN_PLAYLIST:
1484 		g_return_val_if_fail (new_playlist, FALSE);
1485 		g_return_val_if_fail (itdb, FALSE);
1486 
1487 		gphoto_change_to_photo_window (FALSE);
1488 
1489 		/* If new playlist is in an iPod itdb, set the mountpoint for
1490 		 * the free space display to this iPod (there may be several
1491 		 * iPods connected */
1492 		label_text = g_markup_printf_escaped ("<span weight='bold' size='larger'>%s</span>",
1493 						      new_playlist->name);
1494 		gtk_label_set_markup (GTK_LABEL (gtkpod_xml_get_widget (
1495 						     main_window_xml, "current_playlist_label")),
1496 				      label_text);
1497 		g_free (label_text);
1498 
1499 		if (itdb->usertype & GP_ITDB_TYPE_IPOD)
1500 		{
1501 		    space_set_ipod_itdb (itdb);
1502 		}
1503 
1504 		/* remove all entries from sort tab 0 */
1505 		/* printf ("removing entries: %x\n", current_playlist);*/
1506 		st_init (-1, 0);
1507 
1508 		if (new_playlist->is_spl && new_playlist->splpref.liveupdate)
1509 		    itdb_spl_update (new_playlist);
1510 
1511 		if (new_playlist->members)
1512 		{
1513 		    GList *gl;
1514 
1515 		    st_enable_disable_view_sort (0, FALSE);
1516 
1517 		    for (gl=new_playlist->members; gl; gl=gl->next)
1518 		    {
1519 			/* add all tracks to sort tab 0 */
1520 			Track *track = gl->data;
1521 			st_add_track (track, FALSE, TRUE, 0);
1522 		    }
1523 
1524 		    st_enable_disable_view_sort (0, TRUE);
1525 		    st_add_track (NULL, TRUE, TRUE, 0);
1526 		}
1527 		gtkpod_tracks_statusbar_update();
1528 		break;
1529 	    case PM_COLUMN_PHOTOS:
1530 		g_return_val_if_fail (photodb, FALSE);
1531 		g_return_val_if_fail (itdb, FALSE);
1532 		gphoto_display_photo_window (itdb);
1533 		break;
1534 	    case PM_COLUMN_ITDB:
1535 	    case PM_COLUMN_TYPE:
1536 	    case PM_NUM_COLUMNS:
1537 		g_warn_if_reached ();
1538 	    }
1539 	}
1540 
1541 	/* Reallow the coverart selection update */
1542 	coverart_block_change (FALSE);
1543 	/* Set the coverart display based on the selected playlist */
1544 	coverart_display_update(TRUE);
1545 
1546 	space_data_update ();
1547 
1548 #if DEBUG_TIMING
1549 	g_get_current_time (&time);
1550 	printf ("pm_selection_changed_cb exit:  %ld.%06ld sec\n",
1551 		time.tv_sec % 3600, time.tv_usec);
1552 #endif
1553 	/* make only suitable delete menu items available */
1554 	display_adjust_menus ();
1555 
1556 	return FALSE;
1557 }
1558 
1559 /* Callback function called when the selection
1560    of the playlist view has changed */
pm_selection_changed(GtkTreeSelection * selection,gpointer user_data)1561 static void pm_selection_changed (GtkTreeSelection *selection,
1562 				  gpointer user_data)
1563 {
1564     if (!pm_selection_blocked)
1565     {
1566 	g_idle_add (pm_selection_changed_cb,
1567 		    gtk_tree_selection_get_tree_view (selection));
1568     }
1569 }
1570 
1571 
1572 /* Stop editing. If @cancel is TRUE, the edited value will be
1573    discarded (I have the feeling that the "discarding" part does not
1574    work quite the way intended). */
pm_stop_editing(gboolean cancel)1575 void pm_stop_editing (gboolean cancel)
1576 {
1577     GtkTreeViewColumn *col;
1578 
1579     g_return_if_fail (playlist_treeview);
1580 
1581     gtk_tree_view_get_cursor (playlist_treeview, NULL, &col);
1582     if (col)
1583     {
1584 	if (!cancel && col->editable_widget)
1585 	    gtk_cell_editable_editing_done (col->editable_widget);
1586 	if (col->editable_widget)
1587 	    gtk_cell_editable_remove_widget (col->editable_widget);
1588     }
1589 }
1590 
1591 
1592 /* set/read the counter used to remember how often the sort column has
1593    been clicked.
1594    @inc: negative: reset counter to 0
1595    @inc: positive or zero : add to counter
1596    return value: new value of the counter */
pm_sort_counter(gint inc)1597 static gint pm_sort_counter (gint inc)
1598 {
1599     static gint cnt = 0;
1600     if (inc <0) cnt = 0;
1601     else        cnt += inc;
1602     return cnt;
1603 }
1604 
1605 
1606 /* Add all playlists of @itdb at position @pos */
pm_add_itdb(iTunesDB * itdb,gint pos)1607 void pm_add_itdb (iTunesDB *itdb, gint pos)
1608 {
1609     GtkTreeIter mpl_iter;
1610     GList *gl_pl;
1611     ExtraiTunesDBData *eitdb;
1612 
1613     g_return_if_fail (itdb);
1614     eitdb=itdb->userdata;
1615     g_return_if_fail (eitdb);
1616 
1617     for (gl_pl=itdb->playlists; gl_pl; gl_pl=gl_pl->next)
1618     {
1619 	Playlist *pl = gl_pl->data;
1620 	g_return_if_fail (pl);
1621 	if (itdb_playlist_is_mpl (pl))
1622 	{
1623 	    pm_add_child (itdb, PM_COLUMN_PLAYLIST, pl, pos);
1624 	}
1625 	else
1626 	{
1627 	    pm_add_child (itdb, PM_COLUMN_PLAYLIST, pl, -1);
1628 	}
1629     }
1630     /* eitdb->photodb might be NULL: the itdb is added before the iPod
1631      * is parsed */
1632     if (itdb_device_supports_photo (itdb->device) && eitdb->photodb)
1633     {
1634 	pm_add_child (itdb, PM_COLUMN_PHOTOS, eitdb->photodb, -1);
1635     }
1636 
1637     /* expand the itdb */
1638     if (pm_get_iter_for_itdb (itdb, &mpl_iter))
1639     {
1640 	GtkTreeModel *model;
1641 	GtkTreePath *mpl_path;
1642 	model = GTK_TREE_MODEL (gtk_tree_view_get_model (playlist_treeview));
1643 	g_return_if_fail (model);
1644 	mpl_path = gtk_tree_model_get_path (model, &mpl_iter);
1645 	g_return_if_fail (mpl_path);
1646 	gtk_tree_view_expand_row (playlist_treeview, mpl_path, TRUE);
1647 	gtk_tree_path_free (mpl_path);
1648     }
1649 }
1650 
1651 
1652 /* Helper function: add all playlists to playlist model */
pm_add_all_itdbs(void)1653 void pm_add_all_itdbs (void)
1654 {
1655     GList *gl_itdb;
1656     struct itdbs_head *itdbs_head;
1657 
1658     g_return_if_fail (gtkpod_window);
1659     itdbs_head = g_object_get_data (G_OBJECT (gtkpod_window),
1660 				    "itdbs_head");
1661     g_return_if_fail (itdbs_head);
1662     for (gl_itdb=itdbs_head->itdbs; gl_itdb; gl_itdb=gl_itdb->next)
1663     {
1664 	iTunesDB *itdb = gl_itdb->data;
1665 	g_return_if_fail (itdb);
1666 	pm_add_itdb (itdb, -1);
1667     }
1668 }
1669 
1670 
1671 /* Return GtkTreePath for playlist @playlist. The returned path must be
1672    freed using gtk_tree_path_free() after it is no needed any more */
pm_get_path_for_playlist(Playlist * playlist)1673 static GtkTreePath *pm_get_path_for_playlist (Playlist *playlist)
1674 {
1675     GtkTreeIter iter;
1676 
1677     g_return_val_if_fail (playlist_treeview, NULL);
1678     g_return_val_if_fail (playlist, NULL);
1679 
1680     if (pm_get_iter_for_playlist (playlist, &iter))
1681     {
1682 	GtkTreeModel *model;
1683 	model = gtk_tree_view_get_model (playlist_treeview);
1684 	return gtk_tree_model_get_path (model, &iter);
1685     }
1686     return NULL;
1687 }
1688 
1689 
1690 /* Return GtkTreePath for repository @itdb. The returned path must be
1691    freed using gtk_tree_path_free() after it is no needed any more */
pm_get_path_for_itdb(iTunesDB * itdb)1692 GtkTreePath *pm_get_path_for_itdb (iTunesDB *itdb)
1693 {
1694     GtkTreeIter iter;
1695 
1696     g_return_val_if_fail (playlist_treeview, NULL);
1697     g_return_val_if_fail (itdb, NULL);
1698 
1699     if (pm_get_iter_for_itdb (itdb, &iter))
1700     {
1701 	GtkTreeModel *model;
1702 	model = gtk_tree_view_get_model (playlist_treeview);
1703 	return gtk_tree_model_get_path (model, &iter);
1704     }
1705     return NULL;
1706 }
1707 
1708 
1709 /* Return position of repository @itdb */
pm_get_position_for_itdb(iTunesDB * itdb)1710 gint pm_get_position_for_itdb (iTunesDB *itdb)
1711 {
1712     GtkTreePath *path;
1713     gint position = -1;
1714 
1715     g_return_val_if_fail (playlist_treeview, -1);
1716     g_return_val_if_fail (itdb, -1);
1717 
1718     path = pm_get_path_for_itdb (itdb);
1719 
1720     if (path)
1721     {
1722 	gint *indices = gtk_tree_path_get_indices (path);
1723 	if (indices)
1724 	{
1725 	    position = indices[0];
1726 	}
1727 	gtk_tree_path_free (path);
1728     }
1729     return position;
1730 }
1731 
1732 
1733 /* Return position of repository @itdb */
pm_get_position_for_playlist(Playlist * playlist)1734 static gint pm_get_position_for_playlist (Playlist *playlist)
1735 {
1736     GtkTreePath *path;
1737     gint position = -1;
1738 
1739     g_return_val_if_fail (playlist_treeview, -1);
1740     g_return_val_if_fail (playlist, -1);
1741 
1742     path = pm_get_path_for_playlist (playlist);
1743 
1744     if (path)
1745     {
1746 	/* get position of current path */
1747 	if (gtk_tree_path_get_depth (path) == 1)
1748 	{   /* MPL */
1749 	    position = 0;
1750 	}
1751 	else
1752 	{
1753 	    gint *indices = gtk_tree_path_get_indices (path);
1754 	    /* need to add 1 because MPL is one level higher and not
1755 	       counted */
1756 	    position = indices[1] + 1;
1757 	}
1758 	gtk_tree_path_free (path);
1759     }
1760     return position;
1761 }
1762 
1763 
1764 /* "unsort" the playlist view without causing the sort tabs to be
1765    touched. */
pm_unsort()1766 static void pm_unsort ()
1767 {
1768     Playlist *cur_pl;
1769 
1770     pm_selection_blocked = TRUE;
1771 
1772     /* remember */
1773     cur_pl = pm_get_selected_playlist ();
1774 
1775     pm_remove_all_playlists (TRUE);
1776 
1777     pm_set_selected_playlist (cur_pl);
1778 
1779     /* add playlists back to model (without selecting) */
1780     pm_add_all_itdbs ();
1781 
1782     pm_selection_blocked = FALSE;
1783     /* reset sort counter */
1784     pm_sort_counter (-1);
1785 }
1786 
1787 
1788 /* Set the sorting accordingly */
pm_sort(GtkSortType order)1789 void pm_sort (GtkSortType order)
1790 {
1791     GtkTreeModel *model= gtk_tree_view_get_model (playlist_treeview);
1792     g_return_if_fail (model);
1793     if (order != SORT_NONE)
1794     {
1795 	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
1796 					      PM_COLUMN_PLAYLIST, order);
1797     }
1798     else
1799     { /* only unsort if treeview is sorted */
1800 	gint column;
1801 	GtkSortType order;
1802 	if (gtk_tree_sortable_get_sort_column_id
1803 	    (GTK_TREE_SORTABLE (model), &column, &order))
1804 	    pm_unsort ();
1805     }
1806 }
1807 
1808 
1809 #if 0
1810 FIXME: see comments at pm_data_compare_func()
1811 /**
1812  * pm_track_column_button_clicked
1813  * @tvc - the tree view colum that was clicked
1814  * @data - ignored user data
1815  * When the sort button is clicked we want to update our internal playlist
1816  * representation to what's displayed on screen.
1817  * If the button was clicked three times, the sort order is undone.
1818  */
1819 static void
1820 pm_track_column_button_clicked(GtkTreeViewColumn *tvc, gpointer data)
1821 {
1822     gint cnt = pm_sort_counter (1);
1823     if (cnt >= 3)
1824     {
1825 	prefs_set_int("pm_sort", SORT_NONE);
1826 	pm_unsort (); /* also resets the sort_counter */
1827     }
1828     else
1829     {
1830 	prefs_set_int("pm_sort", gtk_tree_view_column_get_sort_order (tvc));
1831 	pm_rows_reordered ();
1832     }
1833 }
1834 #endif
1835 
1836 
1837 /**
1838  * Reorder playlists to match order of playlists displayed.
1839  * data_changed() is called when necessary.
1840  */
1841 void
pm_rows_reordered(void)1842 pm_rows_reordered (void)
1843 {
1844     GtkTreeModel *tm = NULL;
1845     GtkTreeIter parent;
1846     gboolean p_valid;
1847 
1848     g_return_if_fail (playlist_treeview);
1849     tm = gtk_tree_view_get_model (GTK_TREE_VIEW(playlist_treeview));
1850     g_return_if_fail (tm);
1851 
1852     p_valid = gtk_tree_model_get_iter_first(tm, &parent);
1853     while(p_valid)
1854     {
1855 	guint32 pos;
1856 	Playlist *pl;
1857 	iTunesDB *itdb;
1858 	GtkTreeIter child;
1859 	gboolean c_valid;
1860 
1861 	/* get master playlist */
1862 	gtk_tree_model_get (tm, &parent, PM_COLUMN_PLAYLIST, &pl, -1);
1863 	g_return_if_fail (pl);
1864 	g_return_if_fail (itdb_playlist_is_mpl (pl));
1865 	itdb = pl->itdb;
1866 	g_return_if_fail (itdb);
1867 
1868 	pos = 1;
1869 	/* get all children */
1870 	c_valid = gtk_tree_model_iter_children (tm, &child, &parent);
1871 	while (c_valid)
1872 	{
1873 	    gtk_tree_model_get (tm, &child,
1874 				PM_COLUMN_PLAYLIST, &pl, -1);
1875 	    g_return_if_fail (pl);
1876 	    if (itdb_playlist_by_nr (itdb, pos) != pl)
1877 	    {
1878 		/* move the playlist to indicated position */
1879 		g_return_if_fail (!itdb_playlist_is_mpl (pl));
1880 		itdb_playlist_move (pl, pos);
1881 		data_changed (itdb);
1882 	    }
1883 	    ++pos;
1884 	    c_valid = gtk_tree_model_iter_next (tm, &child);
1885 	}
1886 	p_valid = gtk_tree_model_iter_next (tm, &parent);
1887     }
1888 }
1889 
1890 
1891 /* Function used to compare two cells during sorting (playlist view) */
pm_data_compare_func(GtkTreeModel * model,GtkTreeIter * a,GtkTreeIter * b,gpointer user_data)1892 gint pm_data_compare_func (GtkTreeModel *model,
1893 			GtkTreeIter *a,
1894 			GtkTreeIter *b,
1895 			gpointer user_data)
1896 {
1897   Playlist *playlist1=NULL;
1898   Playlist *playlist2=NULL;
1899   GtkSortType order;
1900   gint corr, colid;
1901 
1902   return 0;  /* FIXME: see below -- deactivated for now */
1903 
1904   g_return_val_if_fail (model, 0);
1905   g_return_val_if_fail (a, 0);
1906   g_return_val_if_fail (b, 0);
1907 
1908   if (gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (model),
1909 					    &colid, &order) == FALSE)
1910       return 0;
1911 
1912   gtk_tree_model_get (model, a, colid, &playlist1, -1);
1913   gtk_tree_model_get (model, b, colid, &playlist2, -1);
1914 
1915 /* FIXME: this function crashes because it is provided illegal
1916  * GtkTreeIters. */
1917 /* FIXME: after the introduction of a GtkTreeView rather than a
1918  * ListView, sorting should be done by a customs sort mechanism
1919  * anyway. */
1920 
1921   g_return_val_if_fail (playlist1 && playlist2, 0);
1922 
1923   /* We make sure that the master playlist always stays on top */
1924   if (order == GTK_SORT_ASCENDING) corr = +1;
1925   else                             corr = -1;
1926   if (itdb_playlist_is_mpl (playlist1)) return (-corr);
1927   if (itdb_playlist_is_mpl (playlist2)) return (corr);
1928 
1929   /* compare the two entries */
1930   return compare_string (playlist1->name, playlist2->name);
1931 }
1932 
1933 
1934 /* Called when editable cell is being edited. Stores new data to
1935    the playlist list. */
1936 static void
pm_cell_edited(GtkCellRendererText * renderer,const gchar * path_string,const gchar * new_text,gpointer data)1937 pm_cell_edited (GtkCellRendererText *renderer,
1938 		const gchar         *path_string,
1939 		const gchar         *new_text,
1940 		gpointer             data)
1941 {
1942   GtkTreeModel *model = data;
1943   GtkTreeIter iter;
1944   Playlist *playlist = NULL;
1945 
1946   g_return_if_fail (model);
1947   g_return_if_fail (new_text);
1948   if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1949   {
1950       g_return_if_reached ();
1951   }
1952 
1953   gtk_tree_model_get (model, &iter, PM_COLUMN_PLAYLIST, &playlist, -1);
1954   g_return_if_fail (playlist);
1955 
1956   /*printf("pm_cell_edited: column: %d  track:%lx\n", PM_COLUMN_PLAYLIST, track);*/
1957 
1958   /* We only do something, if the name actually got changed */
1959   if (!playlist->name ||
1960       g_utf8_collate (playlist->name, new_text) != 0)
1961   {
1962       g_free (playlist->name);
1963       playlist->name = g_strdup (new_text);
1964       data_changed (playlist->itdb);
1965       if (itdb_playlist_is_mpl (playlist))
1966       {   /* Need to change name in prefs system */
1967 	  set_itdb_prefs_string (playlist->itdb, "name", new_text);
1968       }
1969   }
1970 }
1971 
1972 
1973 /**
1974  * pm_set_renderer_text
1975  *
1976  * Set the playlist name in appropriate style.
1977  *
1978  * @renderer: renderer to be set
1979  * @playlist: playlist to consider.
1980  */
pm_set_playlist_renderer_text(GtkCellRenderer * renderer,Playlist * playlist)1981 void pm_set_playlist_renderer_text (GtkCellRenderer *renderer,
1982 				    Playlist *playlist)
1983 {
1984     ExtraiTunesDBData *eitdb;
1985 
1986     g_return_if_fail (playlist);
1987     g_return_if_fail (playlist->itdb);
1988     eitdb = playlist->itdb->userdata;
1989     g_return_if_fail (eitdb);
1990 
1991     if (itdb_playlist_is_mpl (playlist))
1992     {   /* mark MPL */
1993 	g_object_set (G_OBJECT (renderer),
1994 		      "text", playlist->name,
1995 		      "weight", PANGO_WEIGHT_BOLD,
1996 		      NULL);
1997 	if (eitdb->data_changed)
1998 	{
1999 	    g_object_set (G_OBJECT (renderer),
2000 			  "style", PANGO_STYLE_ITALIC,
2001 			  NULL);
2002 	}
2003 	else
2004 	{
2005 	    g_object_set (G_OBJECT (renderer),
2006 			  "style", PANGO_STYLE_NORMAL,
2007 			  NULL);
2008 	}
2009     }
2010     else
2011     {
2012 	if (itdb_playlist_is_podcasts (playlist))
2013 	{
2014 	    g_object_set (G_OBJECT (renderer),
2015 			  "text", playlist->name,
2016 			  "weight", PANGO_WEIGHT_SEMIBOLD,
2017 			  "style", PANGO_STYLE_ITALIC,
2018 			  NULL);
2019 	}
2020 	else
2021 	{
2022 	    g_object_set (G_OBJECT (renderer),
2023 			  "text", playlist->name,
2024 			  "weight", PANGO_WEIGHT_NORMAL,
2025 			  "style", PANGO_STYLE_NORMAL,
2026 			  NULL);
2027 	}
2028     }
2029 }
2030 
2031 /**
2032  * pm_set_photodb_renderer_text
2033  *
2034  * Set the PhotoDB name in appropriate style.
2035  *
2036  * @renderer: renderer to be set
2037  * @PhotoDB: photodb to consider.
2038  */
pm_set_photodb_renderer_text(GtkCellRenderer * renderer,PhotoDB * photodb)2039 void pm_set_photodb_renderer_text (GtkCellRenderer *renderer,
2040 				   PhotoDB *photodb)
2041 {
2042     g_return_if_fail (photodb);
2043 
2044     /* bold face */
2045     g_object_set (G_OBJECT (renderer),
2046 		  "text", _("Photos"),
2047 		  "weight", PANGO_WEIGHT_BOLD,
2048 		  NULL);
2049 /* (example for italic style)
2050     if (eitdb->data_changed)
2051     {
2052 	g_object_set (G_OBJECT (renderer),
2053 		      "style", PANGO_STYLE_ITALIC,
2054 		      NULL);
2055     }
2056     else
2057     {
2058 	g_object_set (G_OBJECT (renderer),
2059 		      "style", PANGO_STYLE_NORMAL,
2060 		      NULL);
2061     }
2062 */
2063 }
2064 
2065 
2066 /**
2067  * pm_set_playlist_renderer_pix
2068  *
2069  * Set the appropriate playlist icon.
2070  *
2071  * @renderer: renderer to be set
2072  * @playlist: playlist to consider.
2073  */
pm_set_playlist_renderer_pix(GtkCellRenderer * renderer,Playlist * playlist)2074 void pm_set_playlist_renderer_pix (GtkCellRenderer *renderer,
2075 				   Playlist *playlist)
2076 {
2077     iTunesDB *itdb;
2078     ExtraiTunesDBData *eitdb;
2079 
2080     const gchar *stock_id=NULL;
2081 
2082     g_return_if_fail (renderer);
2083     g_return_if_fail (playlist);
2084     g_return_if_fail (playlist->itdb);
2085 
2086     itdb = playlist->itdb;
2087     g_return_if_fail (itdb->userdata);
2088     eitdb = itdb->userdata;
2089 
2090     if (playlist->is_spl)
2091     {
2092 	stock_id = GTK_STOCK_PROPERTIES;
2093     }
2094     else if (!itdb_playlist_is_mpl (playlist))
2095     {
2096 	stock_id = TUNES_PLAYLIST_ICON_STOCK_ID;
2097     }
2098     else
2099     {
2100 	if (itdb->usertype & GP_ITDB_TYPE_LOCAL)
2101 	{
2102 	    stock_id = GTK_STOCK_HARDDISK;
2103 	}
2104 	else
2105 	{
2106 	    if (eitdb->itdb_imported)
2107 	    {
2108 		stock_id = GTK_STOCK_CONNECT;
2109 	    }
2110 	    else
2111 	    {
2112 		stock_id = GTK_STOCK_DISCONNECT;
2113 	    }
2114 	}
2115     }
2116     g_object_set (G_OBJECT (renderer), "stock-id", stock_id, NULL);
2117     g_object_set (G_OBJECT (renderer), "stock-size", GTK_ICON_SIZE_LARGE_TOOLBAR, NULL);
2118 }
2119 
2120 /**
2121  * pm_set_photodb_renderer_pix
2122  *
2123  * Set the appropriate photodb icon.
2124  *
2125  * @renderer: renderer to be set
2126  * @photodb: photodb to consider.
2127  */
pm_set_photodb_renderer_pix(GtkCellRenderer * renderer,PhotoDB * photodb)2128 void pm_set_photodb_renderer_pix (GtkCellRenderer *renderer,
2129 				  PhotoDB *photodb)
2130 {
2131     const gchar *stock_id=NULL;
2132 
2133     g_return_if_fail (renderer);
2134     g_return_if_fail (photodb);
2135 
2136     stock_id = GPHOTO_PLAYLIST_ICON_STOCK_ID;
2137 
2138     g_object_set (G_OBJECT (renderer), "stock-id", stock_id, NULL);
2139     g_object_set (G_OBJECT (renderer), "stock-size", GTK_ICON_SIZE_LARGE_TOOLBAR, NULL);
2140 }
2141 
2142 
2143 
2144 /* The playlist data is stored in a separate list
2145    and only pointers to the corresponding playlist structure are placed
2146    into the model.
2147    This function reads the data for the given cell from the list and
2148    passes it to the renderer. */
pm_cell_data_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer data)2149 static void pm_cell_data_func (GtkTreeViewColumn *tree_column,
2150 			       GtkCellRenderer   *renderer,
2151 			       GtkTreeModel      *model,
2152 			       GtkTreeIter       *iter,
2153 			       gpointer           data)
2154 {
2155   Playlist *playlist = NULL;
2156   PhotoDB *photodb = NULL;
2157   PM_column_type type;
2158 
2159   g_return_if_fail (renderer);
2160   g_return_if_fail (model);
2161   g_return_if_fail (iter);
2162 
2163   gtk_tree_model_get (model, iter,
2164 		      PM_COLUMN_TYPE, &type,
2165 		      PM_COLUMN_PLAYLIST, &playlist,
2166 		      PM_COLUMN_PHOTOS, &photodb,
2167 		      -1);
2168   switch (type)
2169   {
2170   case PM_COLUMN_PLAYLIST:
2171       pm_set_playlist_renderer_text (renderer, playlist);
2172       break;
2173   case PM_COLUMN_PHOTOS:
2174       pm_set_photodb_renderer_text (renderer, photodb);
2175       break;
2176   case PM_COLUMN_ITDB:
2177   case PM_COLUMN_TYPE:
2178   case PM_NUM_COLUMNS:
2179       g_return_if_reached ();
2180   }
2181 }
2182 
2183 
2184 /* set graphic indicator for smart playlists */
pm_cell_data_func_pix(GtkTreeViewColumn * tree_column,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer data)2185 static void pm_cell_data_func_pix (GtkTreeViewColumn *tree_column,
2186 				   GtkCellRenderer   *renderer,
2187 				   GtkTreeModel      *model,
2188 				   GtkTreeIter       *iter,
2189 				   gpointer           data)
2190 {
2191   Playlist *playlist = NULL;
2192   PhotoDB *photodb = NULL;
2193   PM_column_type type;
2194 
2195   g_return_if_fail (renderer);
2196   g_return_if_fail (model);
2197   g_return_if_fail (iter);
2198 
2199   gtk_tree_model_get (model, iter,
2200 		      PM_COLUMN_TYPE, &type,
2201 		      PM_COLUMN_PLAYLIST, &playlist,
2202 		      PM_COLUMN_PHOTOS, &photodb,
2203 		      -1);
2204   switch (type)
2205   {
2206   case PM_COLUMN_PLAYLIST:
2207       pm_set_playlist_renderer_pix (renderer, playlist);
2208       break;
2209   case PM_COLUMN_PHOTOS:
2210       pm_set_photodb_renderer_pix (renderer, photodb);
2211       break;
2212   case PM_COLUMN_ITDB:
2213   case PM_COLUMN_TYPE:
2214   case PM_NUM_COLUMNS:
2215       g_return_if_reached ();
2216   }
2217 }
2218 
2219 
pm_select_current_position(gint x,gint y)2220 static void pm_select_current_position (gint x, gint y)
2221 {
2222     GtkTreePath *path;
2223 
2224     g_return_if_fail (playlist_treeview);
2225 
2226     gtk_tree_view_get_path_at_pos (playlist_treeview,
2227 				   x, y, &path, NULL, NULL, NULL);
2228     if (path)
2229     {
2230 	GtkTreeSelection *ts = gtk_tree_view_get_selection
2231 	    (playlist_treeview);
2232 	gtk_tree_selection_select_path (ts, path);
2233 	gtk_tree_path_free (path);
2234     }
2235 }
2236 
2237 
2238 
2239 /* Return the number (0...) of the renderer the click was in or -1 if
2240    no renderer was found. @cell (if != NULL) is filled with a pointer
2241    to the renderer. */
tree_view_get_cell_from_pos(GtkTreeView * view,guint x,guint y,GtkCellRenderer ** cell)2242 gint tree_view_get_cell_from_pos(GtkTreeView *view, guint x, guint y,
2243 				 GtkCellRenderer **cell)
2244 {
2245     GtkTreeViewColumn *col = NULL;
2246     GList             *node, *cells;
2247     gint               pos = 0;
2248     GdkRectangle       rect;
2249     GtkTreePath        *path = NULL;
2250     gint               cell_x, cell_y;
2251 
2252     g_return_val_if_fail ( view != NULL, -1 );
2253 
2254     if (cell)
2255 	*cell = NULL;
2256 
2257     gtk_tree_view_get_path_at_pos (view, x, y, &path, &col,
2258 				   &cell_x, &cell_y);
2259 
2260     if (col == NULL)
2261 	return -1; /* not found */
2262 
2263     cells = gtk_tree_view_column_get_cell_renderers(col);
2264 
2265     gtk_tree_view_get_cell_area (view, path, col, &rect);
2266     gtk_tree_path_free (path);
2267 
2268     /* gtk_tree_view_get_cell_area() should return the rectangle
2269        _excluding_ the expander arrow(s), but until 2.8.17 it forgets
2270        about the space occupied by the top level expander arrow. We
2271        therefore need to add the width of one expander arrow */
2272     if (!RUNTIME_GTK_CHECK_VERSION(2,8,18))
2273     {
2274 	if (col ==  gtk_tree_view_get_expander_column (view))
2275 	{
2276 	    GValue *es = g_malloc0 (sizeof (GValue));
2277 	    g_value_init (es, G_TYPE_INT);
2278 	    gtk_widget_style_get_property (GTK_WIDGET (view),
2279 					   "expander_size",
2280 					   es);
2281 	    rect.x += g_value_get_int (es);
2282 	    rect.width -= g_value_get_int (es);
2283 	    g_free (es);
2284 	}
2285     }
2286 
2287     for (node = cells;  node != NULL;  node = node->next)
2288     {
2289 	GtkCellRenderer *checkcell = (GtkCellRenderer*)node->data;
2290 	gint start_pos, width;
2291 
2292 	if (gtk_tree_view_column_cell_get_position (col, checkcell,
2293 						    &start_pos, &width))
2294 	{
2295 	    if (x >= (rect.x + start_pos) &&
2296 		x < (rect.x + start_pos + width))
2297 	    {
2298 		if (cell)
2299 		    *cell = checkcell;
2300 		g_list_free(cells);
2301 		return pos;
2302 	    }
2303 	}
2304 	++pos;
2305     }
2306 
2307     g_list_free(cells);
2308     return -1; /* not found */
2309 }
2310 
2311 
2312 
2313 static gboolean
pm_button_press(GtkWidget * w,GdkEventButton * e,gpointer data)2314 pm_button_press (GtkWidget *w, GdkEventButton *e, gpointer data)
2315 {
2316     gint cell_nr;
2317     GtkTreeModel *model;
2318     GtkTreePath *path;
2319     GtkTreeIter iter;
2320     Playlist *pl;
2321     ExtraiTunesDBData *eitdb;
2322 
2323     g_return_val_if_fail (w && e, FALSE);
2324     switch(e->button)
2325     {
2326     case 1:
2327 	cell_nr = tree_view_get_cell_from_pos (GTK_TREE_VIEW(w),
2328 					       e->x, e->y, NULL);
2329 	if (cell_nr == 0)
2330 	{
2331 	    /* don't accept clicks while widgets are blocked -- this
2332 	       might cause a crash (e.g. when we click the 'Eject
2333 	       iPod' symbol while we are ejecting it already) */
2334 	    if (widgets_blocked) return FALSE;
2335 	    /* */
2336 	    model= gtk_tree_view_get_model (GTK_TREE_VIEW (w));
2337 	    gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW(w),
2338 					   e->x, e->y,
2339 					   &path, NULL,
2340 					   NULL, NULL);
2341 	    gtk_tree_model_get_iter (model, &iter, path);
2342 	    gtk_tree_path_free (path);
2343 	    gtk_tree_model_get (model, &iter,
2344 				PM_COLUMN_PLAYLIST, &pl,
2345 				-1);
2346 	    if (pl == NULL)
2347 		break;
2348 
2349 	    g_return_val_if_fail (pl->itdb, FALSE);
2350 
2351 	    if (!itdb_playlist_is_mpl (pl))
2352 		break;
2353 
2354 	    if (pl->itdb->usertype & GP_ITDB_TYPE_IPOD)
2355 	    {
2356 
2357 		/* the user clicked on the connect/disconnect icon of
2358 		 * an iPod */
2359 		eitdb = pl->itdb->userdata;
2360 		g_return_val_if_fail (eitdb, FALSE);
2361 		block_widgets ();
2362 		if (!eitdb->itdb_imported)
2363 		{
2364 		    gp_load_ipod (pl->itdb);
2365 		}
2366 		else
2367 		{
2368 		    gp_eject_ipod (pl->itdb);
2369 		}
2370 		release_widgets ();
2371 		return TRUE;
2372 	    }
2373 	    if (pl->itdb->usertype & GP_ITDB_TYPE_LOCAL)
2374 	    {
2375 
2376 		/* the user clicked on the 'harddisk' icon of
2377 		 * a local repository */
2378 	    }
2379 	}
2380 	break;
2381     case 3:
2382 	pm_select_current_position (e->x, e->y);
2383 	pm_context_menu_init ();
2384 	return TRUE;
2385     default:
2386 	break;
2387     }
2388     return FALSE;
2389 }
2390 
2391 /* Adds the columns to our playlist_treeview */
pm_add_columns(void)2392 static void pm_add_columns (void)
2393 {
2394 	GtkTreeViewColumn *column;
2395 	GtkCellRenderer *renderer;
2396 	GtkTreeModel *model;
2397 
2398 	model = gtk_tree_view_get_model (playlist_treeview);
2399 	g_return_if_fail (model);
2400 
2401 
2402 	/* playlist column */
2403 	column = gtk_tree_view_column_new ();
2404 	gtk_tree_view_column_set_title (column, _("Playlists"));
2405 	/* FIXME: see comments at pm_data_compare_func() */
2406 	/*
2407 	gtk_tree_view_column_set_sort_column_id (column, PM_COLUMN_PLAYLIST);
2408 	gtk_tree_view_column_set_sort_order (column, GTK_SORT_ASCENDING);
2409 	gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model),
2410 				   PM_COLUMN_PLAYLIST,
2411 				   pm_data_compare_func, column, NULL);
2412 	gtk_tree_view_column_set_clickable(column, TRUE);
2413 	g_signal_connect (G_OBJECT (column), "clicked",
2414 			G_CALLBACK (pm_track_column_button_clicked),
2415 				(gpointer)PM_COLUMN_PLAYLIST);
2416 	*/
2417 
2418 	gtk_tree_view_append_column (playlist_treeview, column);
2419 
2420 	/* cell for graphic indicator */
2421 	renderer = gtk_cell_renderer_pixbuf_new ();
2422 
2423 	gtk_tree_view_column_pack_start (column, renderer, FALSE);
2424 	gtk_tree_view_column_set_cell_data_func (column, renderer,
2425 					   pm_cell_data_func_pix,
2426 					   NULL, NULL);
2427 	/* cell for playlist name */
2428 	renderer = gtk_cell_renderer_text_new ();
2429 	g_signal_connect (G_OBJECT (renderer), "edited",
2430 			G_CALLBACK (pm_cell_edited), model);
2431 	gtk_tree_view_column_pack_start (column, renderer, FALSE);
2432 	gtk_tree_view_column_set_cell_data_func (column, renderer,
2433 					   pm_cell_data_func,
2434 					   NULL, NULL);
2435 	g_object_set (G_OBJECT (renderer),
2436 		"editable", TRUE,
2437 		NULL);
2438 }
2439 
2440 
2441 /* Create playlist listview */
pm_create_treeview(void)2442 void pm_create_treeview (void)
2443 {
2444 	GtkTreeStore *model;
2445 	GtkTreeSelection *selection;
2446 	GtkWidget *playlist_window;
2447 	GtkWidget *tree;
2448 
2449 	playlist_window = gtkpod_xml_get_widget (main_window_xml, "playlist_window");
2450 	g_return_if_fail (playlist_window);
2451 
2452 	/* destroy old treeview */
2453 	if (playlist_treeview)
2454 	{
2455 	  model = GTK_TREE_STORE (gtk_tree_view_get_model(playlist_treeview));
2456 	  g_return_if_fail (model);
2457 	  g_object_unref (model);
2458 	  gtk_widget_destroy (GTK_WIDGET (playlist_treeview));
2459 	  playlist_treeview = NULL;
2460 	}
2461 	/* create new one */
2462 	tree = gtk_tree_view_new ();
2463 	gtk_widget_set_events (tree, GDK_KEY_RELEASE_MASK);
2464 	gtk_widget_show (tree);
2465 	playlist_treeview = GTK_TREE_VIEW (tree);
2466 	gtk_container_add (GTK_CONTAINER (playlist_window), tree);
2467 
2468 	/* create model */
2469 	model =   gtk_tree_store_new (PM_NUM_COLUMNS, G_TYPE_POINTER, G_TYPE_INT, G_TYPE_POINTER, G_TYPE_POINTER);
2470 
2471 	/* set tree model */
2472 	gtk_tree_view_set_model (playlist_treeview, GTK_TREE_MODEL (model));
2473 	/* gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (playlist_treeview), TRUE); */
2474 	gtk_tree_selection_set_mode (gtk_tree_view_get_selection (playlist_treeview),
2475 				   GTK_SELECTION_SINGLE);
2476 	selection = gtk_tree_view_get_selection (playlist_treeview);
2477 	g_signal_connect (G_OBJECT (selection), "changed",
2478 			G_CALLBACK (pm_selection_changed), NULL);
2479 	pm_add_columns ();
2480 
2481 	gtk_drag_source_set (GTK_WIDGET (playlist_treeview),
2482 			   GDK_BUTTON1_MASK,
2483 			   pm_drag_types, TGNR (pm_drag_types),
2484 			   GDK_ACTION_COPY|GDK_ACTION_MOVE);
2485 	gtk_drag_dest_set (GTK_WIDGET (playlist_treeview),
2486 			 GTK_DEST_DEFAULT_HIGHLIGHT,
2487 			 pm_drop_types, TGNR (pm_drop_types),
2488 			 GDK_ACTION_COPY|GDK_ACTION_MOVE);
2489 
2490 
2491 	/*   gtk_tree_view_enable_model_drag_dest (playlist_treeview, */
2492 	/* 					pm_drop_types, TGNR (pm_drop_types), */
2493 	/* 					GDK_ACTION_COPY); */
2494 	/* need the gtk_drag_dest_set() with no actions ("0") so that the
2495 	 data_received callback gets the correct info value. This is most
2496 	 likely a bug... */
2497 	/*   gtk_drag_dest_set_target_list (GTK_WIDGET (playlist_treeview), */
2498 	/* 				 gtk_target_list_new (pm_drop_types, */
2499 	/* 						      TGNR (pm_drop_types))); */
2500 
2501 	g_signal_connect ((gpointer) playlist_treeview, "drag-begin",
2502 			G_CALLBACK (pm_drag_begin),
2503 			NULL);
2504 
2505 	g_signal_connect ((gpointer) playlist_treeview, "drag-data-delete",
2506 			G_CALLBACK (pm_drag_data_delete),
2507 			NULL);
2508 
2509 	g_signal_connect ((gpointer) playlist_treeview, "drag-data-get",
2510 			G_CALLBACK (pm_drag_data_get),
2511 			NULL);
2512 
2513 	g_signal_connect ((gpointer) playlist_treeview, "drag-data-received",
2514 			G_CALLBACK (pm_drag_data_received),
2515 			NULL);
2516 
2517 	g_signal_connect ((gpointer) playlist_treeview, "drag-drop",
2518 			G_CALLBACK (pm_drag_drop),
2519 			NULL);
2520 
2521 	g_signal_connect ((gpointer) playlist_treeview, "drag-end",
2522 			G_CALLBACK (pm_drag_end),
2523 			NULL);
2524 
2525 	g_signal_connect ((gpointer) playlist_treeview, "drag-leave",
2526 			G_CALLBACK (pm_drag_leave),
2527 			NULL);
2528 
2529 	g_signal_connect ((gpointer) playlist_treeview, "drag-motion",
2530 			G_CALLBACK (pm_drag_motion),
2531 			NULL);
2532 
2533 	g_signal_connect_after ((gpointer) playlist_treeview, "key_release_event",
2534 			  G_CALLBACK (on_playlist_treeview_key_release_event),
2535 			  NULL);
2536 	g_signal_connect (G_OBJECT (playlist_treeview), "button-press-event",
2537 			G_CALLBACK (pm_button_press), model);
2538 }
2539 
2540 
2541 
2542 Playlist*
pm_get_selected_playlist(void)2543 pm_get_selected_playlist (void)
2544 {
2545 /* return(current_playlist);*/
2546 /* we can't just return the "current_playlist" because the context
2547    menus require the selection before "current_playlist" is updated */
2548 
2549     GtkTreeSelection *ts;
2550     GtkTreeIter iter;
2551     GtkTreeModel *model;
2552     Playlist *result = NULL;
2553 
2554     g_return_val_if_fail (playlist_treeview, NULL);
2555     ts = gtk_tree_view_get_selection (playlist_treeview);
2556     g_return_val_if_fail (ts, NULL);
2557 
2558     if (gtk_tree_selection_get_selected (ts, &model, &iter))
2559     {
2560 	gtk_tree_model_get (model, &iter,
2561 			    PM_COLUMN_PLAYLIST, &result, -1);
2562     }
2563 
2564     /* playlist was just changed -- wait until current_playlist is
2565        updated. */
2566     if (result != current_playlist)  result=NULL;
2567     return result;
2568 }
2569 
2570 iTunesDB*
pm_get_selected_itdb(void)2571 pm_get_selected_itdb (void)
2572 {
2573 /* return(current_playlist);*/
2574 /* we can't just return the "current_playlist" because the context
2575    menus require the selection before "current_playlist" is updated */
2576 
2577     GtkTreeSelection *ts;
2578     GtkTreeIter iter;
2579     GtkTreeModel *model;
2580     iTunesDB *result = NULL;
2581 
2582     g_return_val_if_fail (playlist_treeview, NULL);
2583     ts = gtk_tree_view_get_selection (playlist_treeview);
2584     g_return_val_if_fail (ts, NULL);
2585 
2586     if (gtk_tree_selection_get_selected (ts, &model, &iter))
2587     {
2588 	gtk_tree_model_get (model, &iter,
2589 			    PM_COLUMN_ITDB, &result, -1);
2590     }
2591 
2592     /* playlist was just changed -- wait until current_playlist is
2593        updated. */
2594     if (result != current_itdb)  result=NULL;
2595     return result;
2596 }
2597 
2598 /* use with care!! */
2599 void
pm_set_selected_playlist(Playlist * pl)2600 pm_set_selected_playlist (Playlist *pl)
2601 {
2602     current_playlist = pl;
2603 }
2604 
pm_show_all_playlists()2605 void pm_show_all_playlists ()
2606 {
2607 	gtk_tree_view_expand_all (playlist_treeview);
2608 }
2609