2 * playlist.c -- GTK+ 2 omnplay
3 * Copyright (C) 2011 Maksym Veremeyenko <verem@m1stereo.tv>
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.
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.
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.
28 #include <gdk/gdkkeysyms.h>
38 extern GtkTargetEntry drag_targets
[];
40 static void playlist_get_selected_items_idx_iter
49 int **plist
= (int**)data
;
52 gtk_tree_model_get(model
, iter
, 7, &idx
, -1);
56 list
= (int*)malloc(sizeof(int));
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
);
65 list
= (int*)realloc(list
, (l
+ 2) * sizeof(int));
76 int* playlist_get_selected_items_idx(instance_t
* app
, int *count
)
78 int* list
= NULL
, l
= 0;
79 GtkTreeSelection
*selection
;
81 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(app
->playlist_grid
));
84 gtk_tree_selection_selected_foreach(
86 playlist_get_selected_items_idx_iter
,
90 for(l
= 0; -1 != list
[l
]; l
++);
98 static void playlist_drag_data_get_cb(GtkWidget
*widget
, GdkDragContext
*context
,
99 GtkSelectionData
*selection_data
, guint info
, guint time
, gpointer userdata
)
102 playlist_item_t
* items
;
103 instance_t
* app
= (instance_t
*)userdata
;
105 g_warning("playlist_drag_data_get_cb");
107 list
= playlist_get_selected_items_idx(app
, &c
);
110 /* clear delete flag */
111 for(i
= 0; i
< app
->playlist
.count
; i
++)
112 app
->playlist
.item
[i
].del
= 0;
114 items
= (playlist_item_t
*)malloc(sizeof(playlist_item_t
) * c
);
115 for(i
= 0; i
< c
; i
++)
117 items
[i
] = app
->playlist
.item
[list
[i
]];
118 if(context
->action
== GDK_ACTION_MOVE
)
119 app
->playlist
.item
[list
[i
]].del
= 1;
121 gtk_selection_data_set(selection_data
, selection_data
->target
, 8,
122 (const guchar
*)items
, sizeof(playlist_item_t
) * c
);
128 static void playlist_drag_begin_cb(GtkWidget
*widget
, GdkDragContext
*context
, gpointer userdata
)
130 g_warning("playlist_drag_begin_cb");
131 gtk_drag_source_set_icon_stock(widget
, GTK_STOCK_DND
);
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
)
138 playlist_item_type_t t
;
139 playlist_item_t
* items
;
140 GtkTreePath
*path
= NULL
;
141 instance_t
* app
= (instance_t
*)userdata
;
143 g_warning("playlist_drag_data_received: context->action=%d", context
->action
);
145 items
= (playlist_item_t
*)gtk_selection_data_get_data(selection_data
);
146 c
= gtk_selection_data_get_length(selection_data
);
148 if(c
% sizeof(playlist_item_t
))
150 g_warning("playlist_drag_data_received: ODD ITEMS");
154 c
/= sizeof(playlist_item_t
);
156 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget
), x
, y
, &path
, NULL
, NULL
, NULL
))
158 idx
= gtk_tree_path_get_indices(path
)[0];
159 gtk_tree_path_free(path
);
161 g_warning("playlist_drag_data_received: gtk_tree_path_get_indice[0]=%d", idx
);
163 /* normalize, FIX ME */
164 idx
--; if(idx
< 0) idx
= 0;
167 idx
= app
->playlist
.count
;
169 g_warning("playlist_drag_data_received: idx=%d", idx
);
171 if(playlist_insert_check(app
, idx
, &t
))
173 for(i
= 0; i
< c
; i
++)
178 playlist_insert_items(app
, idx
, items
, c
);
182 /* Finish the drag */
183 gtk_drag_finish(context
, TRUE
, FALSE
, time
);
186 static void playlist_drag_data_delete(GtkWidget
*widget
, GdkDragContext
*context
, gpointer userdata
)
189 instance_t
* app
= (instance_t
*)userdata
;
191 g_warning("playlist_drag_data_delete");
193 list
= (int*)malloc(sizeof(int) * MAX_PLAYLIST_ITEMS
);
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
))
201 g_warning("playlist_drag_data_delete: i=%d, c=%d", i
, c
);
205 playlist_delete_items(app
, list
, c
, 0);
211 * http://www.mail-archive.com/mahogany-users@lists.sourceforge.net/msg00286.html
213 static gboolean
playlist_drag_motion(GtkWidget
*widget
, GdkDragContext
*context
,
214 gint x
, gint y
, guint time
, gpointer data
)
217 GtkWidget
*source_widget
;
219 g_warning("playlist_grid_drag_motion");
221 /* Get source widget and check if it is the same as the
222 * destination widget.
224 source_widget
= gtk_drag_get_source_widget(context
);
225 same
= ((source_widget
== widget
) ? TRUE
: FALSE
);
227 /* Put additional checks here, perhaps if same is FALSE then
228 * set the default drag to GDK_ACTION_COPY.
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().
241 if(context
->actions
== GDK_ACTION_MOVE
)
242 gdk_drag_status(context
, GDK_ACTION_COPY
, time
);
244 gdk_drag_status(context
, GDK_ACTION_MOVE
, time
);
247 gdk_drag_status(context
, context
->actions
, time
);
253 int playlist_item_index(instance_t
* app
, int start
, int int_idx
)
255 if(start
< 0 || start
>= app
->playlist
.count
)
260 if(app
->playlist
.item
[start
].int_idx
== int_idx
)
263 if(app
->playlist
.item
[start
].type
& PLAYLIST_BLOCK_END
)
273 void playlist_init(instance_t
* app
)
275 gtk_drag_source_set(app
->playlist_grid
, GDK_BUTTON1_MASK
,
276 drag_targets
, 1, (GdkDragAction
)(GDK_ACTION_COPY
| GDK_ACTION_MOVE
));
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
));
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
);
288 void playlist_release(instance_t
* app
)
292 int playlist_idx_cued(instance_t
* app
, int idx
, int* player_idx
)
296 for(i
= 0; i
< app
->players
.count
; i
++)
300 a
= app
->players
.item
[i
].playlist_start
;
301 b
= app
->players
.item
[i
].playlist_length
;
308 if(idx
>= a
&& idx
<= b
)
319 int playlist_range_cued(instance_t
* app
, int start
, int stop
)
323 for(i
= start
; i
<= stop
; i
++)
324 if(playlist_idx_cued(app
, i
, NULL
))
330 void playlist_block(instance_t
* app
, int loop
)
332 int start
, stop
, i
, c
;
333 int* list
= playlist_get_selected_items_idx(app
, &c
);
338 pthread_mutex_lock(&app
->playlist
.lock
);
339 pthread_mutex_lock(&app
->players
.lock
);
345 loop
= PLAYLIST_BLOCK_LOOP
;
347 if(!playlist_range_cued(app
, start
, stop
))
349 /* update selected item */
350 for(i
= start
; i
<= stop
; i
++)
352 int t
= PLAYLIST_BLOCK_BODY
| loop
;
354 if(i
== start
) t
|= PLAYLIST_BLOCK_BEGIN
;
355 if(i
== stop
) t
|= PLAYLIST_BLOCK_END
;
357 app
->playlist
.item
[i
].type
= (playlist_item_type_t
)t
;
359 ui_playlist_draw_item(app
, i
);
362 /* update border items */
363 if(start
&& !(app
->playlist
.item
[start
- 1].type
& PLAYLIST_BLOCK_END
))
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);
369 if((stop
+ 1) < app
->playlist
.count
&& !(app
->playlist
.item
[stop
+ 1].type
& PLAYLIST_BLOCK_BEGIN
))
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);
377 g_warning("omnplay_playlist_block: range [%d %d] do OVERLAP player\n",
380 pthread_mutex_unlock(&app
->players
.lock
);
381 pthread_mutex_unlock(&app
->playlist
.lock
);
386 int playlist_get_first_selected_item_idx(instance_t
* app
)
389 int* list
= playlist_get_selected_items_idx(app
, &c
);
397 int playlist_get_block(instance_t
* app
, int idx
, int* pstart
, int* pstop
)
401 for(start
= idx
; start
>= 0; start
--)
402 if(app
->playlist
.item
[start
].type
& PLAYLIST_BLOCK_BEGIN
)
405 for(stop
= idx
; stop
< app
->playlist
.count
; stop
++)
406 if(app
->playlist
.item
[stop
].type
& PLAYLIST_BLOCK_END
)
409 g_warning("playlist_get_block: range %d -> %d\n", start
, stop
);
411 /* check block range */
412 if(start
>= 0 && stop
< app
->playlist
.count
)
416 return (stop
- start
+ 1);
422 player_t
*playlist_get_player_at_pos(instance_t
* app
, int pos
)
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
];
431 void playlist_delete_items(instance_t
* app
, int* idxs
, int count
, int sel
)
435 pthread_mutex_lock(&app
->playlist
.lock
);
436 pthread_mutex_lock(&app
->players
.lock
);
438 for(j
= 0; j
< count
; j
++)
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
)
447 app
->playlist
.item
[idx
- 1].type
= (playlist_item_type_t
)(app
->playlist
.item
[idx
- 1].type
|
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
);
454 /* shift playlist items */
457 &app
->playlist
.item
[idx
],
458 &app
->playlist
.item
[idx
+ 1],
459 (app
->playlist
.count
- idx
- 1) * sizeof(playlist_item_t
)
462 /* decrement items count */
463 app
->playlist
.count
--;
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
--;
471 /* redraw playlist */
472 ui_playlist_draw(app
);
476 ui_playlist_select_item(app
, idxs
[0]);
478 pthread_mutex_unlock(&app
->players
.lock
);
479 pthread_mutex_unlock(&app
->playlist
.lock
);
482 void playlist_delete_selected_items(instance_t
* app
)
487 list1
= playlist_get_selected_items_idx(app
, &cnt1
);
490 list2
= (int*)malloc(sizeof(int) * cnt1
);
492 for(i
= 0, cnt2
= 0; i
< cnt1
; i
++)
494 /* check for playing block */
495 if(playlist_idx_cued(app
, list1
[i
], NULL
))
499 list2
[cnt2
++] = list1
[i
];
503 playlist_delete_items(app
, list2
, cnt2
, 1);
509 int playlist_insert_check(instance_t
* app
, int idx
, playlist_item_type_t
* t
)
511 *t
= PLAYLIST_ITEM_BLOCK_SINGLE
;
513 /* before or after playlist */
514 if(!idx
|| idx
== app
->playlist
.count
)
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
)
522 /* check for playing block */
523 if(playlist_idx_cued(app
, idx
, NULL
))
526 if(app
->playlist
.item
[idx
].type
& PLAYLIST_BLOCK_LOOP
)
527 *t
= PLAYLIST_ITEM_LOOP_BODY
;
529 *t
= PLAYLIST_ITEM_BLOCK_BODY
;
534 void playlist_insert_items(instance_t
* app
, int idx
, playlist_item_t
* items
, int count
)
538 pthread_mutex_lock(&app
->playlist
.lock
);
539 pthread_mutex_lock(&app
->players
.lock
);
541 /* shift playlist items */
544 &app
->playlist
.item
[idx
+ count
],
545 &app
->playlist
.item
[idx
],
546 (app
->playlist
.count
- idx
) * sizeof(playlist_item_t
)
552 &app
->playlist
.item
[idx
],
554 count
* sizeof(playlist_item_t
)
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
;
562 /* increment items count */
563 app
->playlist
.count
+= count
;
565 /* redraw playlist */
566 ui_playlist_draw(app
);
569 ui_playlist_select_item(app
, idx
);
571 pthread_mutex_unlock(&app
->players
.lock
);
572 pthread_mutex_unlock(&app
->playlist
.lock
);
575 void playlist_item_copy(instance_t
* app
)
579 list
= playlist_get_selected_items_idx(app
, &c
);
582 for(i
= 0; i
< c
; i
++)
583 app
->clipboard
.item
[i
] = app
->playlist
.item
[list
[i
]];
584 app
->clipboard
.count
= c
;
589 void playlist_item_paste(instance_t
* app
, int after
)
592 playlist_item_type_t t
;
594 /* find insert position */
595 idx
= playlist_get_first_selected_item_idx(app
);
601 if(!playlist_insert_check(app
, idx
, &t
))
605 if(app
->clipboard
.count
)
607 for(i
= 0; i
< app
->clipboard
.count
; i
++)
609 app
->clipboard
.item
[i
].type
= t
;
610 app
->clipboard
.item
[i
].error
= 0;
612 playlist_insert_items(app
, idx
, app
->clipboard
.item
, app
->clipboard
.count
);
616 void playlist_item_swap(instance_t
* app
, int dir
)
618 int sel
, a
, b
, e
= 1;
619 playlist_item_t item
;
621 /* find insert position */
622 sel
= playlist_get_first_selected_item_idx(app
);
639 /* check for playing block */
640 if(playlist_idx_cued(app
, a
, NULL
) || playlist_idx_cued(app
, b
, NULL
))
643 pthread_mutex_lock(&app
->playlist
.lock
);
644 pthread_mutex_lock(&app
->players
.lock
);
647 item
= app
->playlist
.item
[a
];
648 app
->playlist
.item
[a
] = app
->playlist
.item
[b
];
649 app
->playlist
.item
[b
] = item
;
652 if(app
->playlist
.item
[a
].type
!= app
->playlist
.item
[b
].type
)
655 app
->playlist
.item
[a
].type
= PLAYLIST_ITEM_BLOCK_SINGLE
;
656 app
->playlist
.item
[b
].type
= PLAYLIST_ITEM_BLOCK_SINGLE
;
659 /* redraw main items */
660 ui_playlist_draw_item(app
, a
);
661 ui_playlist_draw_item(app
, b
);
663 /* fix block types */
666 app
->playlist
.item
[a
- 1].type
= (playlist_item_type_t
)(app
->playlist
.item
[a
- 1].type
|
668 ui_playlist_draw_item(app
, a
- 1);
670 if(b
+ 1 < app
->playlist
.count
&& !e
)
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);
678 ui_playlist_select_item(app
, sel
);
680 pthread_mutex_unlock(&app
->players
.lock
);
681 pthread_mutex_unlock(&app
->playlist
.lock
);
684 static int playlist_load_plt(instance_t
* app
, char* filename
, char* err_buf
, int err_len
)
688 int count
= 0, i
= 0;
689 playlist_item_t
* items
;
691 f
= fopen(filename
, "rt");
696 snprintf(err_buf
, err_len
, "Failed to open file [%s], error: %s",
697 filename
, strerror(i
));
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
);
714 memset(line
, 0, PATH_MAX
);
715 fgets(line
, PATH_MAX
, f
);
717 /* remove newlines */
718 if( (s
= strchr(line
, '\n')) ) *s
= 0;
719 if( (s
= strchr(line
, '\r')) ) *s
= 0;
721 /* check for empty line */
722 if(!line
[0] || line
[0] == '#')
725 /* check item contine */
728 /* line without header */
732 /* load playlist item */
736 strncpy(items
[count
].title
, line
+ 1, PATH_MAX
);
741 tc2frames(line
+ 1, 25.0, &items
[count
].in
);
746 tc2frames(line
+ 1, 25.0, &items
[count
].dur
);
751 items
[count
].player
= atol(line
+ 1) - 1;
756 items
[count
].type
= atol(line
+ 1) - 1024;
758 /* last item - break a chain */
766 strcpy(items
[count
].id
, line
);
774 /* add loaded items to playlist */
777 pthread_mutex_lock(&app
->playlist
.lock
);
778 for(i
= 0; i
< count
&& app
->playlist
.count
+ 1 < MAX_PLAYLIST_ITEMS
; i
++)
780 // omnplay_library_normalize_item(app, &items[i]);
781 app
->playlist
.item
[app
->playlist
.count
++] = items
[i
];
783 app
->playlist
.ver_curr
++;
784 pthread_mutex_unlock(&app
->playlist
.lock
);
794 static int playlist_save_plt(instance_t
* app
, char* filename
, char* err_buf
, int err_len
)
798 char in
[12], dur
[12];
800 f
= fopen(filename
, "wt");
805 strncpy(err_buf
, strerror(i
), err_len
);
809 for(i
= 0; i
< app
->playlist
.count
; i
++)
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
833 static int playlist_load_plx(instance_t
* app
, char* filename
, char* err_buf
, int err_len
)
835 strncpy(err_buf
, "Method not implemented", err_len
);
839 static int playlist_save_plx(instance_t
* app
, char* filename
, char* err_buf
, int err_len
)
841 strncpy(err_buf
, "Method not implemented", err_len
);
845 static struct ui_playlist_io_funcs playlist_io
[] =
848 "Text formatted playlist (*.plt)",
854 "Xml formatted playlist (*.plx)",
867 void playlist_load(instance_t
* app
)
869 ui_playlist_load(app
, (app
->playlist
.path
)?app
->playlist
.path
:getenv("HOME"), playlist_io
);
872 void playlist_save(instance_t
* app
)
874 ui_playlist_save(app
, (app
->playlist
.path
)?app
->playlist
.path
:getenv("HOME"), playlist_io
);
877 void playlist_relink(instance_t
* app
)
882 pthread_mutex_lock(&app
->playlist
.lock
);
883 list
= playlist_get_selected_items_idx(app
, &cnt
);
886 for(i
= 0; i
< cnt
; i
++)
888 /* check for playing block */
889 if(playlist_idx_cued(app
, list
[i
], NULL
))
893 library_relink_item(app
, &app
->playlist
.item
[list
[i
]]);
898 pthread_mutex_unlock(&app
->playlist
.lock
);
900 /* redraw playlist */
901 ui_playlist_draw(app
);
904 void playlist_item_add(instance_t
* app
, int after
)
907 playlist_item_t item
;
908 playlist_item_type_t t
;
910 /* find insert position */
911 idx
= playlist_get_first_selected_item_idx(app
);
917 if(!playlist_insert_check(app
, idx
, &t
))
920 g_warning("allowed insert into idx=%d\n", idx
);
923 memset(&item
, 0, sizeof(playlist_item_t
));
924 if(ui_playlist_item_dialog(app
, &item
))
926 library_normalize_item(app
, &item
);
928 playlist_insert_items(app
, idx
, &item
, 1);
932 void playlist_item_edit(instance_t
* app
)
935 playlist_item_t item
;
937 /* find insert position */
938 idx
= playlist_get_first_selected_item_idx(app
);
943 /* check for playing block */
944 if(playlist_idx_cued(app
, idx
, NULL
))
947 item
= app
->playlist
.item
[idx
];
949 if(ui_playlist_item_dialog(app
, &item
))
951 library_normalize_item(app
, &item
);
952 app
->playlist
.item
[idx
] = item
;
953 ui_playlist_draw_item(app
, idx
);
957 void playlist_item_add_from_library(instance_t
* app
, int after
)
961 void playlist_normalize(instance_t
* app
)
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
);