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