6d03b08e4d3b9e1276a29341bd5c11b5346596d2
[melted_gui] / src / playlist.c
1 /*
2 * playlist.c -- GTK+ 2 omnplay
3 * Copyright (C) 2011 Maksym Veremeyenko <verem@m1stereo.tv>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software Foundation,
17 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <gtk/gtk.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <pthread.h>
30
31 #include <errno.h>
32
33 #include "playlist.h"
34 #include "ui.h"
35 #include "timecode.h"
36 #include "library.h"
37
38 extern GtkTargetEntry drag_targets[];
39
40 static void playlist_get_selected_items_idx_iter
41 (
42 GtkTreeModel *model,
43 GtkTreePath *path,
44 GtkTreeIter *iter,
45 gpointer data
46 )
47 {
48 int idx, l;
49 int **plist = (int**)data;
50 int *list = *plist;
51
52 gtk_tree_model_get(model, iter, 7, &idx, -1);
53
54 if(!list)
55 {
56 list = (int*)malloc(sizeof(int));
57 list[0] = -1;
58 };
59
60 /* find numbers of items in list */
61 for(l = 0; -1 != list[l]; l++);
62 g_warning("playlist_get_selected_items_idx_iter: l=%d", l);
63
64 /* realloc items */
65 list = (int*)realloc(list, (l + 2) * sizeof(int));
66
67 /* clean last item */
68 list[l + 1] = -1;
69
70 /* setup items */
71 list[l] = idx;
72
73 *plist = list;
74 };
75
76 int* playlist_get_selected_items_idx(instance_t* app, int *count)
77 {
78 int* list = NULL, l = 0;
79 GtkTreeSelection *selection;
80
81 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(app->playlist_grid));
82 if(selection)
83 {
84 gtk_tree_selection_selected_foreach(
85 selection,
86 playlist_get_selected_items_idx_iter,
87 &list);
88
89 if(list)
90 for(l = 0; -1 != list[l]; l++);
91 };
92
93 *count = l;
94 return list;
95 };
96
97
98 static void playlist_drag_data_get_cb(GtkWidget *widget, GdkDragContext *context,
99 GtkSelectionData *selection_data, guint info, guint time, gpointer userdata)
100 {
101 int *list, i, c;
102 playlist_item_t* items;
103 instance_t* app = (instance_t*)userdata;
104
105 g_warning("playlist_drag_data_get_cb");
106
107 list = playlist_get_selected_items_idx(app, &c);
108 if(!list) return;
109
110 /* clear delete flag */
111 for(i = 0; i < app->playlist.count; i++)
112 app->playlist.item[i].del = 0;
113
114 items = (playlist_item_t*)malloc(sizeof(playlist_item_t) * c);
115 for(i = 0; i < c; i++)
116 {
117 items[i] = app->playlist.item[list[i]];
118 if(context->action == GDK_ACTION_MOVE)
119 app->playlist.item[list[i]].del = 1;
120 }
121 gtk_selection_data_set(selection_data, selection_data->target, 8,
122 (const guchar *)items, sizeof(playlist_item_t) * c);
123
124 free(items);
125 free(list);
126 };
127
128 static void playlist_drag_begin_cb(GtkWidget *widget, GdkDragContext *context, gpointer userdata)
129 {
130 g_warning("playlist_drag_begin_cb");
131 gtk_drag_source_set_icon_stock(widget, GTK_STOCK_DND);
132 };
133
134 static void playlist_drag_data_received(GtkWidget *widget, GdkDragContext *context,
135 gint x, gint y, GtkSelectionData *selection_data, guint info, guint time, gpointer userdata)
136 {
137 int c, i, idx;
138 playlist_item_type_t t;
139 playlist_item_t* items;
140 GtkTreePath *path = NULL;
141 instance_t* app = (instance_t*)userdata;
142
143 g_warning("playlist_drag_data_received: context->action=%d", context->action);
144
145 items = (playlist_item_t*)gtk_selection_data_get_data(selection_data);
146 c = gtk_selection_data_get_length(selection_data);
147
148 if(c % sizeof(playlist_item_t))
149 {
150 g_warning("playlist_drag_data_received: ODD ITEMS");
151 }
152 else
153 {
154 c /= sizeof(playlist_item_t);
155
156 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), x, y, &path, NULL, NULL, NULL))
157 {
158 idx = gtk_tree_path_get_indices(path)[0];
159 gtk_tree_path_free(path);
160
161 g_warning("playlist_drag_data_received: gtk_tree_path_get_indice[0]=%d", idx);
162
163 /* normalize, FIX ME */
164 idx--; if(idx < 0) idx = 0;
165 }
166 else
167 idx = app->playlist.count;
168
169 g_warning("playlist_drag_data_received: idx=%d", idx);
170
171 if(playlist_insert_check(app, idx, &t))
172 {
173 for(i = 0; i < c; i++)
174 {
175 items[i].type = t;
176 items[i].error = 0;
177 };
178 playlist_insert_items(app, idx, items, c);
179 };
180 };
181
182 /* Finish the drag */
183 gtk_drag_finish(context, TRUE, FALSE, time);
184 };
185
186 static void playlist_drag_data_delete(GtkWidget *widget, GdkDragContext *context, gpointer userdata)
187 {
188 int c, i, *list;
189 instance_t* app = (instance_t*)userdata;
190
191 g_warning("playlist_drag_data_delete");
192
193 list = (int*)malloc(sizeof(int) * MAX_PLAYLIST_ITEMS);
194
195 for(i = 0, c = 0; i < app->playlist.count; i++)
196 if(app->playlist.item[i].del)
197 if(!playlist_idx_cued(app, i, NULL))
198 {
199 /* save index */
200 list[c++] = i;
201 g_warning("playlist_drag_data_delete: i=%d, c=%d", i, c);
202 };
203
204 if(c)
205 playlist_delete_items(app, list, c, 0);
206
207 free(list);
208 };
209
210 /*
211 * http://www.mail-archive.com/mahogany-users@lists.sourceforge.net/msg00286.html
212 */
213 static gboolean playlist_drag_motion(GtkWidget *widget, GdkDragContext *context,
214 gint x, gint y, guint time, gpointer data)
215 {
216 gboolean same;
217 GtkWidget *source_widget;
218
219 g_warning("playlist_grid_drag_motion");
220
221 /* Get source widget and check if it is the same as the
222 * destination widget.
223 */
224 source_widget = gtk_drag_get_source_widget(context);
225 same = ((source_widget == widget) ? TRUE : FALSE);
226
227 /* Put additional checks here, perhaps if same is FALSE then
228 * set the default drag to GDK_ACTION_COPY.
229 */
230
231 /* Say we just want to allow GDK_ACTION_MOVE, first we check
232 * if that is in the list of allowed actions on the dc. If
233 * so then we set it to that. Note if the user holds down the
234 * ctrl key then the only flag in dc->actions will be
235 * GDK_ACTION_COPY. The constraint for dc->actions is that
236 * specified from the given actions in gtk_drag_dest_set() and
237 * gtk_drag_source_set().
238 */
239 if(same)
240 {
241 if(context->actions == GDK_ACTION_MOVE)
242 gdk_drag_status(context, GDK_ACTION_COPY, time);
243 else
244 gdk_drag_status(context, GDK_ACTION_MOVE, time);
245 }
246 else
247 gdk_drag_status(context, context->actions, time);
248
249 return(TRUE);
250 }
251
252
253 int playlist_item_index(instance_t* app, int start, int int_idx)
254 {
255 if(start < 0 || start >= app->playlist.count)
256 return -1;
257
258 while(1)
259 {
260 if(app->playlist.item[start].int_idx == int_idx)
261 return start;
262
263 if(app->playlist.item[start].type & PLAYLIST_BLOCK_END)
264 break;
265
266 start++;
267 };
268
269 return -1;
270 };
271
272
273 void playlist_init(instance_t* app)
274 {
275 gtk_drag_source_set(app->playlist_grid, GDK_BUTTON1_MASK,
276 drag_targets, 1, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_MOVE));
277
278 gtk_drag_dest_set(app->playlist_grid, (GtkDestDefaults)(GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP),
279 drag_targets, 1, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_MOVE));
280
281 g_signal_connect (app->playlist_grid, "drag_data_get", G_CALLBACK(playlist_drag_data_get_cb), app);
282 g_signal_connect (app->playlist_grid, "drag_begin", G_CALLBACK(playlist_drag_begin_cb), app);
283 g_signal_connect (app->playlist_grid, "drag_data_received", G_CALLBACK (playlist_drag_data_received), app);
284 g_signal_connect (app->playlist_grid, "drag_data_delete", G_CALLBACK (playlist_drag_data_delete), app);
285 g_signal_connect (app->playlist_grid, "drag_motion", G_CALLBACK (playlist_drag_motion), app);
286 };
287
288 void playlist_release(instance_t* app)
289 {
290 };
291
292 int playlist_idx_cued(instance_t* app, int idx, int* player_idx)
293 {
294 int i;
295
296 for(i = 0; i < app->players.count; i++)
297 {
298 int a, b;
299
300 a = app->players.item[i].playlist_start;
301 b = app->players.item[i].playlist_length;
302
303 if(b <= 0)
304 continue;
305
306 b = a + b - 1;
307
308 if(idx >= a && idx <= b)
309 {
310 if(player_idx)
311 *player_idx = i;
312 return 1;
313 };
314 };
315
316 return 0;
317 };
318
319 int playlist_range_cued(instance_t* app, int start, int stop)
320 {
321 int i;
322
323 for(i = start; i <= stop; i++)
324 if(playlist_idx_cued(app, i, NULL))
325 return 1;
326
327 return 0;
328 };
329
330 void playlist_block(instance_t* app, int loop)
331 {
332 int start, stop, i, c;
333 int* list = playlist_get_selected_items_idx(app, &c);
334
335 if(!list)
336 return;
337
338 pthread_mutex_lock(&app->playlist.lock);
339 pthread_mutex_lock(&app->players.lock);
340
341 start = list[0];
342 stop = list[c - 1];
343
344 if(loop)
345 loop = PLAYLIST_BLOCK_LOOP;
346
347 if(!playlist_range_cued(app, start, stop))
348 {
349 /* update selected item */
350 for(i = start; i <= stop; i++)
351 {
352 int t = PLAYLIST_BLOCK_BODY | loop;
353
354 if(i == start) t |= PLAYLIST_BLOCK_BEGIN;
355 if(i == stop) t |= PLAYLIST_BLOCK_END;
356
357 app->playlist.item[i].type = (playlist_item_type_t)t;
358
359 ui_playlist_draw_item(app, i);
360 };
361
362 /* update border items */
363 if(start && !(app->playlist.item[start - 1].type & PLAYLIST_BLOCK_END))
364 {
365 app->playlist.item[start - 1].type = (playlist_item_type_t)(PLAYLIST_BLOCK_END
366 | app->playlist.item[start - 1].type);
367 ui_playlist_draw_item(app, start - 1);
368 };
369 if((stop + 1) < app->playlist.count && !(app->playlist.item[stop + 1].type & PLAYLIST_BLOCK_BEGIN))
370 {
371 app->playlist.item[stop + 1].type = (playlist_item_type_t)(PLAYLIST_BLOCK_BEGIN
372 | app->playlist.item[stop + 1].type);
373 ui_playlist_draw_item(app, stop + 1);
374 };
375 }
376 else
377 g_warning("omnplay_playlist_block: range [%d %d] do OVERLAP player\n",
378 start, stop);
379
380 pthread_mutex_unlock(&app->players.lock);
381 pthread_mutex_unlock(&app->playlist.lock);
382
383 free(list);
384 };
385
386 int playlist_get_first_selected_item_idx(instance_t* app)
387 {
388 int idx, c;
389 int* list = playlist_get_selected_items_idx(app, &c);
390 if(!list)
391 return -1;
392 idx = list[0];
393 free(list);
394 return idx;
395 };
396
397 int playlist_get_block(instance_t* app, int idx, int* pstart, int* pstop)
398 {
399 int start, stop;
400
401 for(start = idx; start >= 0; start--)
402 if(app->playlist.item[start].type & PLAYLIST_BLOCK_BEGIN)
403 break;
404
405 for(stop = idx; stop < app->playlist.count; stop++)
406 if(app->playlist.item[stop].type & PLAYLIST_BLOCK_END)
407 break;
408
409 g_warning("playlist_get_block: range %d -> %d\n", start, stop);
410
411 /* check block range */
412 if(start >= 0 && stop < app->playlist.count)
413 {
414 *pstart = start;
415 *pstop = stop;
416 return (stop - start + 1);
417 };
418
419 return -1;
420 };
421
422 player_t *playlist_get_player_at_pos(instance_t* app, int pos)
423 {
424 /* check player range */
425 if(app->playlist.item[pos].player > -1 && app->playlist.item[pos].player < app->players.count)
426 return &app->players.item[app->playlist.item[pos].player];
427
428 return NULL;
429 };
430
431 void playlist_delete_items(instance_t* app, int* idxs, int count, int sel)
432 {
433 int i, j, idx;
434
435 pthread_mutex_lock(&app->playlist.lock);
436 pthread_mutex_lock(&app->players.lock);
437
438 for(j = 0; j < count; j++)
439 {
440 idx = idxs[j] - j;
441
442 /* fix block types */
443 if( app->playlist.item[idx].type != PLAYLIST_ITEM_BLOCK_BODY &&
444 app->playlist.item[idx].type != PLAYLIST_ITEM_LOOP_BODY)
445 {
446 if(idx)
447 app->playlist.item[idx - 1].type = (playlist_item_type_t)(app->playlist.item[idx - 1].type |
448 PLAYLIST_BLOCK_END);
449 if(idx + 1 < app->playlist.count)
450 app->playlist.item[idx + 1].type = (playlist_item_type_t)(app->playlist.item[idx + 1].type |
451 PLAYLIST_BLOCK_BEGIN);
452 };
453
454 /* shift playlist items */
455 memmove
456 (
457 &app->playlist.item[idx],
458 &app->playlist.item[idx + 1],
459 (app->playlist.count - idx - 1) * sizeof(playlist_item_t)
460 );
461
462 /* decrement items count */
463 app->playlist.count--;
464
465 /* increment servers indexes */
466 for(i = 0; i < app->players.count; i++)
467 if(app->players.item[i].playlist_start >= idx)
468 app->players.item[i].playlist_start--;
469 };
470
471 /* redraw playlist */
472 ui_playlist_draw(app);
473
474 /* select */
475 if(sel)
476 ui_playlist_select_item(app, idxs[0]);
477
478 pthread_mutex_unlock(&app->players.lock);
479 pthread_mutex_unlock(&app->playlist.lock);
480 };
481
482 void playlist_delete_selected_items(instance_t* app)
483 {
484 int i, cnt1, cnt2;
485 int *list1, *list2;
486
487 list1 = playlist_get_selected_items_idx(app, &cnt1);
488 if(!list1) return;
489
490 list2 = (int*)malloc(sizeof(int) * cnt1);
491
492 for(i = 0, cnt2 = 0; i < cnt1; i++)
493 {
494 /* check for playing block */
495 if(playlist_idx_cued(app, list1[i], NULL))
496 continue;
497
498 /* save index */
499 list2[cnt2++] = list1[i];
500 };
501
502 if(cnt2)
503 playlist_delete_items(app, list2, cnt2, 1);
504
505 free(list2);
506 free(list1);
507 };
508
509 int playlist_insert_check(instance_t* app, int idx, playlist_item_type_t* t)
510 {
511 *t = PLAYLIST_ITEM_BLOCK_SINGLE;
512
513 /* before or after playlist */
514 if(!idx || idx == app->playlist.count)
515 return 1;
516
517 /* check for block borders */
518 if( app->playlist.item[idx - 1].type & PLAYLIST_BLOCK_END &&
519 app->playlist.item[idx + 0].type & PLAYLIST_BLOCK_BEGIN)
520 return 1;
521
522 /* check for playing block */
523 if(playlist_idx_cued(app, idx, NULL))
524 return 0;
525
526 if(app->playlist.item[idx].type & PLAYLIST_BLOCK_LOOP)
527 *t = PLAYLIST_ITEM_LOOP_BODY;
528 else
529 *t = PLAYLIST_ITEM_BLOCK_BODY;
530
531 return 1;
532 };
533
534 void playlist_insert_items(instance_t* app, int idx, playlist_item_t* items, int count)
535 {
536 int i;
537
538 pthread_mutex_lock(&app->playlist.lock);
539 pthread_mutex_lock(&app->players.lock);
540
541 /* shift playlist items */
542 memmove
543 (
544 &app->playlist.item[idx + count],
545 &app->playlist.item[idx],
546 (app->playlist.count - idx) * sizeof(playlist_item_t)
547 );
548
549 /* copy new items */
550 memcpy
551 (
552 &app->playlist.item[idx],
553 items,
554 count * sizeof(playlist_item_t)
555 );
556
557 /* increment servers indexes */
558 for(i = 0; i < app->players.count; i++)
559 if(app->players.item[i].playlist_start >= idx)
560 app->players.item[i].playlist_start += idx;
561
562 /* increment items count */
563 app->playlist.count += count;
564
565 /* redraw playlist */
566 ui_playlist_draw(app);
567
568 /* select */
569 ui_playlist_select_item(app, idx);
570
571 pthread_mutex_unlock(&app->players.lock);
572 pthread_mutex_unlock(&app->playlist.lock);
573 };
574
575 void playlist_item_copy(instance_t* app)
576 {
577 int *list, i, c;
578
579 list = playlist_get_selected_items_idx(app, &c);
580 if(!list) return;
581
582 for(i = 0; i < c; i++)
583 app->clipboard.item[i] = app->playlist.item[list[i]];
584 app->clipboard.count = c;
585
586 free(list);
587 };
588
589 void playlist_item_paste(instance_t* app, int after)
590 {
591 int idx, i;
592 playlist_item_type_t t;
593
594 /* find insert position */
595 idx = playlist_get_first_selected_item_idx(app);
596 if(idx < 0)
597 idx = 0;
598 else
599 idx += (after)?1:0;
600
601 if(!playlist_insert_check(app, idx, &t))
602 return;
603
604 /* clear item */
605 if(app->clipboard.count)
606 {
607 for(i = 0; i < app->clipboard.count; i++)
608 {
609 app->clipboard.item[i].type = t;
610 app->clipboard.item[i].error = 0;
611 };
612 playlist_insert_items(app, idx, app->clipboard.item, app->clipboard.count);
613 };
614 };
615
616 void playlist_item_swap(instance_t* app, int dir)
617 {
618 int sel, a, b, e = 1;
619 playlist_item_t item;
620
621 /* find insert position */
622 sel = playlist_get_first_selected_item_idx(app);
623 if(sel < 0)
624 return;
625
626 if(dir < 0)
627 {
628 a = sel - 1;
629 b = sel;
630 sel = a;
631 }
632 else
633 {
634 a = sel;
635 b = sel + 1;
636 sel = b;
637 };
638
639 /* check for playing block */
640 if(playlist_idx_cued(app, a, NULL) || playlist_idx_cued(app, b, NULL))
641 return;
642
643 pthread_mutex_lock(&app->playlist.lock);
644 pthread_mutex_lock(&app->players.lock);
645
646 /* swap */
647 item = app->playlist.item[a];
648 app->playlist.item[a] = app->playlist.item[b];
649 app->playlist.item[b] = item;
650
651 /* rewite type */
652 if(app->playlist.item[a].type != app->playlist.item[b].type)
653 {
654 e = 0;
655 app->playlist.item[a].type = PLAYLIST_ITEM_BLOCK_SINGLE;
656 app->playlist.item[b].type = PLAYLIST_ITEM_BLOCK_SINGLE;
657 };
658
659 /* redraw main items */
660 ui_playlist_draw_item(app, a);
661 ui_playlist_draw_item(app, b);
662
663 /* fix block types */
664 if(a && !e)
665 {
666 app->playlist.item[a - 1].type = (playlist_item_type_t)(app->playlist.item[a - 1].type |
667 PLAYLIST_BLOCK_END);
668 ui_playlist_draw_item(app, a - 1);
669 };
670 if(b + 1 < app->playlist.count && !e)
671 {
672 app->playlist.item[b + 1].type = (playlist_item_type_t)(app->playlist.item[b + 1].type |
673 PLAYLIST_BLOCK_BEGIN);
674 ui_playlist_draw_item(app, b + 1);
675 };
676
677 /* select */
678 ui_playlist_select_item(app, sel);
679
680 pthread_mutex_unlock(&app->players.lock);
681 pthread_mutex_unlock(&app->playlist.lock);
682 };
683
684 static int playlist_load_plt(instance_t* app, char* filename, char* err_buf, int err_len)
685 {
686 FILE* f;
687 char *line;
688 int count = 0, i = 0;
689 playlist_item_t* items;
690
691 f = fopen(filename, "rt");
692
693 if(!f)
694 {
695 i = errno;
696 snprintf(err_buf, err_len, "Failed to open file [%s], error: %s",
697 filename, strerror(i));
698 return -i;
699 };
700
701 /* allocate space for strings and items */
702 items = malloc(sizeof(playlist_item_t) * MAX_PLAYLIST_ITEMS);
703 memset(items, 0, sizeof(playlist_item_t) * MAX_PLAYLIST_ITEMS);
704 line = malloc(PATH_MAX);
705
706 while(1)
707 {
708 char* s;
709
710 if(feof(f))
711 break;
712
713 /* load string */
714 memset(line, 0, PATH_MAX);
715 fgets(line, PATH_MAX, f);
716
717 /* remove newlines */
718 if( (s = strchr(line, '\n')) ) *s = 0;
719 if( (s = strchr(line, '\r')) ) *s = 0;
720
721 /* check for empty line */
722 if(!line[0] || line[0] == '#')
723 continue;
724
725 /* check item contine */
726 if(line[0] == '\t')
727 {
728 /* line without header */
729 if(!i)
730 continue;
731
732 /* load playlist item */
733 switch(i)
734 {
735 case 1: // title
736 strncpy(items[count].title, line + 1, PATH_MAX);
737 i++;
738 break;
739
740 case 2: // in
741 tc2frames(line + 1, 25.0, &items[count].in);
742 i++;
743 break;
744
745 case 3: // dur
746 tc2frames(line + 1, 25.0, &items[count].dur);
747 i++;
748 break;
749
750 case 4: // player
751 items[count].player = atol(line + 1) - 1;
752 i++;
753 break;
754
755 case 5: // type
756 items[count].type = atol(line + 1) - 1024;
757
758 /* last item - break a chain */
759 i = 0;
760 count++;
761 break;
762 };
763 }
764 else
765 {
766 strcpy(items[count].id, line);
767 i = 1;
768 };
769
770 };
771
772 fclose(f);
773
774 /* add loaded items to playlist */
775 if(count)
776 {
777 pthread_mutex_lock(&app->playlist.lock);
778 for(i = 0; i < count && app->playlist.count + 1 < MAX_PLAYLIST_ITEMS; i++)
779 {
780 // omnplay_library_normalize_item(app, &items[i]);
781 app->playlist.item[app->playlist.count++] = items[i];
782 };
783 app->playlist.ver_curr++;
784 pthread_mutex_unlock(&app->playlist.lock);
785 }
786
787 /* free items */
788 free(line);
789 free(items);
790
791 return 0;
792 };
793
794 static int playlist_save_plt(instance_t* app, char* filename, char* err_buf, int err_len)
795 {
796 int i;
797 FILE* f;
798 char in[12], dur[12];
799
800 f = fopen(filename, "wt");
801
802 if(!f)
803 {
804 i = errno;
805 strncpy(err_buf, strerror(i), err_len);
806 return -i;
807 };
808
809 for(i = 0; i < app->playlist.count; i++)
810 fprintf
811 (
812 f,
813 "%s\n" // id
814 "\t%s\n" // title
815 "\t%s\n" // in
816 "\t%s\n" // dur
817 "\t%d\n" // player
818 "\t%d\n", // type
819
820 app->playlist.item[i].id,
821 app->playlist.item[i].title,
822 frames2tc(app->playlist.item[i].in, 25.0, in),
823 frames2tc(app->playlist.item[i].dur, 25.0, dur),
824 app->playlist.item[i].player + 1,
825 app->playlist.item[i].type + 1024
826 );
827
828 fclose(f);
829
830 return 0;
831 };
832
833 static int playlist_load_plx(instance_t* app, char* filename, char* err_buf, int err_len)
834 {
835 strncpy(err_buf, "Method not implemented", err_len);
836 return -1;
837 };
838
839 static int playlist_save_plx(instance_t* app, char* filename, char* err_buf, int err_len)
840 {
841 strncpy(err_buf, "Method not implemented", err_len);
842 return -1;
843 };
844
845 static struct ui_playlist_io_funcs playlist_io[] =
846 {
847 {
848 "Text formatted playlist (*.plt)",
849 "*.plt",
850 playlist_load_plt,
851 playlist_save_plt
852 },
853 {
854 "Xml formatted playlist (*.plx)",
855 "*.plx",
856 playlist_load_plx,
857 playlist_save_plx,
858 },
859 {
860 NULL,
861 NULL,
862 NULL,
863 NULL
864 }
865 };
866
867 void playlist_load(instance_t* app)
868 {
869 ui_playlist_load(app, (app->playlist.path)?app->playlist.path:getenv("HOME"), playlist_io);
870 };
871
872 void playlist_save(instance_t* app)
873 {
874 ui_playlist_save(app, (app->playlist.path)?app->playlist.path:getenv("HOME"), playlist_io);
875 };
876
877 void playlist_relink(instance_t* app)
878 {
879 int i, cnt;
880 int *list;
881
882 pthread_mutex_lock(&app->playlist.lock);
883 list = playlist_get_selected_items_idx(app, &cnt);
884 if(list)
885 {
886 for(i = 0; i < cnt; i++)
887 {
888 /* check for playing block */
889 if(playlist_idx_cued(app, list[i], NULL))
890 continue;
891
892 /* relink item */
893 library_relink_item(app, &app->playlist.item[list[i]]);
894 };
895
896 free(list);
897 };
898 pthread_mutex_unlock(&app->playlist.lock);
899
900 /* redraw playlist */
901 ui_playlist_draw(app);
902 };
903
904 void playlist_item_add(instance_t* app, int after)
905 {
906 int idx;
907 playlist_item_t item;
908 playlist_item_type_t t;
909
910 /* find insert position */
911 idx = playlist_get_first_selected_item_idx(app);
912 if(idx < 0)
913 idx = 0;
914 else
915 idx += (after)?1:0;
916
917 if(!playlist_insert_check(app, idx, &t))
918 return;
919
920 g_warning("allowed insert into idx=%d\n", idx);
921
922 /* clear item */
923 memset(&item, 0, sizeof(playlist_item_t));
924 if(ui_playlist_item_dialog(app, &item))
925 {
926 library_normalize_item(app, &item);
927 item.type = t;
928 playlist_insert_items(app, idx, &item, 1);
929 };
930 };
931
932 void playlist_item_edit(instance_t* app)
933 {
934 int idx;
935 playlist_item_t item;
936
937 /* find insert position */
938 idx = playlist_get_first_selected_item_idx(app);
939
940 if(idx < 0)
941 return;
942
943 /* check for playing block */
944 if(playlist_idx_cued(app, idx, NULL))
945 return;
946
947 item = app->playlist.item[idx];
948
949 if(ui_playlist_item_dialog(app, &item))
950 {
951 library_normalize_item(app, &item);
952 app->playlist.item[idx] = item;
953 ui_playlist_draw_item(app, idx);
954 };
955 };
956
957 void playlist_item_add_from_library(instance_t* app, int after)
958 {
959 };
960
961 void playlist_normalize(instance_t* app)
962 {
963 int i;
964
965 /* normalize playlist */
966 for(i = 0; i < app->playlist.count; i++)
967 if(library_normalize_item(app, &app->playlist.item[i]))
968 ui_playlist_draw_item(app, i);
969 };