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