/* * playlist.c -- GTK+ 2 omnplay * Copyright (C) 2011 Maksym Veremeyenko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include "playlist.h" #include "ui.h" #include "timecode.h" #include "library.h" extern GtkTargetEntry drag_targets[]; static void playlist_get_selected_items_idx_iter ( GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data ) { int idx, l; int **plist = (int**)data; int *list = *plist; gtk_tree_model_get(model, iter, 7, &idx, -1); if(!list) { list = (int*)malloc(sizeof(int)); list[0] = -1; }; /* find numbers of items in list */ for(l = 0; -1 != list[l]; l++); g_warning("playlist_get_selected_items_idx_iter: l=%d", l); /* realloc items */ list = (int*)realloc(list, (l + 2) * sizeof(int)); /* clean last item */ list[l + 1] = -1; /* setup items */ list[l] = idx; *plist = list; }; int* playlist_get_selected_items_idx(instance_t* app, int *count) { int* list = NULL, l = 0; GtkTreeSelection *selection; selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(app->playlist_grid)); if(selection) { gtk_tree_selection_selected_foreach( selection, playlist_get_selected_items_idx_iter, &list); if(list) for(l = 0; -1 != list[l]; l++); }; *count = l; return list; }; static void playlist_drag_data_get_cb(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data, guint info, guint time, gpointer userdata) { int *list, i, c; playlist_item_t* items; instance_t* app = (instance_t*)userdata; g_warning("playlist_drag_data_get_cb"); list = playlist_get_selected_items_idx(app, &c); if(!list) return; /* clear delete flag */ for(i = 0; i < app->playlist.count; i++) app->playlist.item[i].del = 0; items = (playlist_item_t*)malloc(sizeof(playlist_item_t) * c); for(i = 0; i < c; i++) { items[i] = app->playlist.item[list[i]]; if(context->action == GDK_ACTION_MOVE) app->playlist.item[list[i]].del = 1; } gtk_selection_data_set(selection_data, selection_data->target, 8, (const guchar *)items, sizeof(playlist_item_t) * c); free(items); free(list); }; static void playlist_drag_begin_cb(GtkWidget *widget, GdkDragContext *context, gpointer userdata) { g_warning("playlist_drag_begin_cb"); gtk_drag_source_set_icon_stock(widget, GTK_STOCK_DND); }; static void playlist_drag_data_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection_data, guint info, guint time, gpointer userdata) { int c, i, idx; playlist_item_type_t t; playlist_item_t* items; GtkTreePath *path = NULL; instance_t* app = (instance_t*)userdata; g_warning("playlist_drag_data_received: context->action=%d", context->action); items = (playlist_item_t*)gtk_selection_data_get_data(selection_data); c = gtk_selection_data_get_length(selection_data); if(c % sizeof(playlist_item_t)) { g_warning("playlist_drag_data_received: ODD ITEMS"); } else { c /= sizeof(playlist_item_t); if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), x, y, &path, NULL, NULL, NULL)) { idx = gtk_tree_path_get_indices(path)[0]; gtk_tree_path_free(path); g_warning("playlist_drag_data_received: gtk_tree_path_get_indice[0]=%d", idx); /* normalize, FIX ME */ idx--; if(idx < 0) idx = 0; } else idx = app->playlist.count; g_warning("playlist_drag_data_received: idx=%d", idx); if(playlist_insert_check(app, idx, &t)) { for(i = 0; i < c; i++) { items[i].type = t; items[i].error = 0; }; playlist_insert_items(app, idx, items, c); }; }; /* Finish the drag */ gtk_drag_finish(context, TRUE, FALSE, time); }; static void playlist_drag_data_delete(GtkWidget *widget, GdkDragContext *context, gpointer userdata) { int c, i, *list; instance_t* app = (instance_t*)userdata; g_warning("playlist_drag_data_delete"); list = (int*)malloc(sizeof(int) * MAX_PLAYLIST_ITEMS); for(i = 0, c = 0; i < app->playlist.count; i++) if(app->playlist.item[i].del) if(!playlist_idx_cued(app, i, NULL)) { /* save index */ list[c++] = i; g_warning("playlist_drag_data_delete: i=%d, c=%d", i, c); }; if(c) playlist_delete_items(app, list, c, 0); free(list); }; /* * http://www.mail-archive.com/mahogany-users@lists.sourceforge.net/msg00286.html */ static gboolean playlist_drag_motion(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer data) { gboolean same; GtkWidget *source_widget; g_warning("playlist_grid_drag_motion"); /* Get source widget and check if it is the same as the * destination widget. */ source_widget = gtk_drag_get_source_widget(context); same = ((source_widget == widget) ? TRUE : FALSE); /* Put additional checks here, perhaps if same is FALSE then * set the default drag to GDK_ACTION_COPY. */ /* Say we just want to allow GDK_ACTION_MOVE, first we check * if that is in the list of allowed actions on the dc. If * so then we set it to that. Note if the user holds down the * ctrl key then the only flag in dc->actions will be * GDK_ACTION_COPY. The constraint for dc->actions is that * specified from the given actions in gtk_drag_dest_set() and * gtk_drag_source_set(). */ if(same) { if(context->actions == GDK_ACTION_MOVE) gdk_drag_status(context, GDK_ACTION_COPY, time); else gdk_drag_status(context, GDK_ACTION_MOVE, time); } else gdk_drag_status(context, context->actions, time); return(TRUE); } int playlist_item_index(instance_t* app, int start, int int_idx) { if(start < 0 || start >= app->playlist.count) return -1; while(1) { if(app->playlist.item[start].int_idx == int_idx) return start; if(app->playlist.item[start].type & PLAYLIST_BLOCK_END) break; start++; }; return -1; }; void playlist_init(instance_t* app) { gtk_drag_source_set(app->playlist_grid, GDK_BUTTON1_MASK, drag_targets, 1, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_MOVE)); gtk_drag_dest_set(app->playlist_grid, (GtkDestDefaults)(GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP), drag_targets, 1, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_MOVE)); g_signal_connect (app->playlist_grid, "drag_data_get", G_CALLBACK(playlist_drag_data_get_cb), app); g_signal_connect (app->playlist_grid, "drag_begin", G_CALLBACK(playlist_drag_begin_cb), app); g_signal_connect (app->playlist_grid, "drag_data_received", G_CALLBACK (playlist_drag_data_received), app); g_signal_connect (app->playlist_grid, "drag_data_delete", G_CALLBACK (playlist_drag_data_delete), app); g_signal_connect (app->playlist_grid, "drag_motion", G_CALLBACK (playlist_drag_motion), app); }; void playlist_release(instance_t* app) { }; int playlist_idx_cued(instance_t* app, int idx, int* player_idx) { int i; for(i = 0; i < app->players.count; i++) { int a, b; a = app->players.item[i].playlist_start; b = app->players.item[i].playlist_length; if(b <= 0) continue; b = a + b - 1; if(idx >= a && idx <= b) { if(player_idx) *player_idx = i; return 1; }; }; return 0; }; int playlist_range_cued(instance_t* app, int start, int stop) { int i; for(i = start; i <= stop; i++) if(playlist_idx_cued(app, i, NULL)) return 1; return 0; }; void playlist_block(instance_t* app, int loop) { int start, stop, i, c; int* list = playlist_get_selected_items_idx(app, &c); if(!list) return; pthread_mutex_lock(&app->playlist.lock); pthread_mutex_lock(&app->players.lock); start = list[0]; stop = list[c - 1]; if(loop) loop = PLAYLIST_BLOCK_LOOP; if(!playlist_range_cued(app, start, stop)) { /* update selected item */ for(i = start; i <= stop; i++) { int t = PLAYLIST_BLOCK_BODY | loop; if(i == start) t |= PLAYLIST_BLOCK_BEGIN; if(i == stop) t |= PLAYLIST_BLOCK_END; app->playlist.item[i].type = (playlist_item_type_t)t; ui_playlist_draw_item(app, i); }; /* update border items */ if(start && !(app->playlist.item[start - 1].type & PLAYLIST_BLOCK_END)) { app->playlist.item[start - 1].type = (playlist_item_type_t)(PLAYLIST_BLOCK_END | app->playlist.item[start - 1].type); ui_playlist_draw_item(app, start - 1); }; if((stop + 1) < app->playlist.count && !(app->playlist.item[stop + 1].type & PLAYLIST_BLOCK_BEGIN)) { app->playlist.item[stop + 1].type = (playlist_item_type_t)(PLAYLIST_BLOCK_BEGIN | app->playlist.item[stop + 1].type); ui_playlist_draw_item(app, stop + 1); }; } else g_warning("omnplay_playlist_block: range [%d %d] do OVERLAP player\n", start, stop); pthread_mutex_unlock(&app->players.lock); pthread_mutex_unlock(&app->playlist.lock); free(list); }; int playlist_get_first_selected_item_idx(instance_t* app) { int idx, c; int* list = playlist_get_selected_items_idx(app, &c); if(!list) return -1; idx = list[0]; free(list); return idx; }; int playlist_get_block(instance_t* app, int idx, int* pstart, int* pstop) { int start, stop; for(start = idx; start >= 0; start--) if(app->playlist.item[start].type & PLAYLIST_BLOCK_BEGIN) break; for(stop = idx; stop < app->playlist.count; stop++) if(app->playlist.item[stop].type & PLAYLIST_BLOCK_END) break; g_warning("playlist_get_block: range %d -> %d\n", start, stop); /* check block range */ if(start >= 0 && stop < app->playlist.count) { *pstart = start; *pstop = stop; return (stop - start + 1); }; return -1; }; player_t *playlist_get_player_at_pos(instance_t* app, int pos) { /* check player range */ if(app->playlist.item[pos].player > -1 && app->playlist.item[pos].player < app->players.count) return &app->players.item[app->playlist.item[pos].player]; return NULL; }; void playlist_delete_items(instance_t* app, int* idxs, int count, int sel) { int i, j, idx; pthread_mutex_lock(&app->playlist.lock); pthread_mutex_lock(&app->players.lock); for(j = 0; j < count; j++) { idx = idxs[j] - j; /* fix block types */ if( app->playlist.item[idx].type != PLAYLIST_ITEM_BLOCK_BODY && app->playlist.item[idx].type != PLAYLIST_ITEM_LOOP_BODY) { if(idx) app->playlist.item[idx - 1].type = (playlist_item_type_t)(app->playlist.item[idx - 1].type | PLAYLIST_BLOCK_END); if(idx + 1 < app->playlist.count) app->playlist.item[idx + 1].type = (playlist_item_type_t)(app->playlist.item[idx + 1].type | PLAYLIST_BLOCK_BEGIN); }; /* shift playlist items */ memmove ( &app->playlist.item[idx], &app->playlist.item[idx + 1], (app->playlist.count - idx - 1) * sizeof(playlist_item_t) ); /* decrement items count */ app->playlist.count--; /* increment servers indexes */ for(i = 0; i < app->players.count; i++) if(app->players.item[i].playlist_start >= idx) app->players.item[i].playlist_start--; }; /* redraw playlist */ ui_playlist_draw(app); /* select */ if(sel) ui_playlist_select_item(app, idxs[0]); pthread_mutex_unlock(&app->players.lock); pthread_mutex_unlock(&app->playlist.lock); }; void playlist_delete_selected_items(instance_t* app) { int i, cnt1, cnt2; int *list1, *list2; list1 = playlist_get_selected_items_idx(app, &cnt1); if(!list1) return; list2 = (int*)malloc(sizeof(int) * cnt1); for(i = 0, cnt2 = 0; i < cnt1; i++) { /* check for playing block */ if(playlist_idx_cued(app, list1[i], NULL)) continue; /* save index */ list2[cnt2++] = list1[i]; }; if(cnt2) playlist_delete_items(app, list2, cnt2, 1); free(list2); free(list1); }; int playlist_insert_check(instance_t* app, int idx, playlist_item_type_t* t) { *t = PLAYLIST_ITEM_BLOCK_SINGLE; /* before or after playlist */ if(!idx || idx == app->playlist.count) return 1; /* check for block borders */ if( app->playlist.item[idx - 1].type & PLAYLIST_BLOCK_END && app->playlist.item[idx + 0].type & PLAYLIST_BLOCK_BEGIN) return 1; /* check for playing block */ if(playlist_idx_cued(app, idx, NULL)) return 0; if(app->playlist.item[idx].type & PLAYLIST_BLOCK_LOOP) *t = PLAYLIST_ITEM_LOOP_BODY; else *t = PLAYLIST_ITEM_BLOCK_BODY; return 1; }; void playlist_insert_items(instance_t* app, int idx, playlist_item_t* items, int count) { int i; pthread_mutex_lock(&app->playlist.lock); pthread_mutex_lock(&app->players.lock); /* shift playlist items */ memmove ( &app->playlist.item[idx + count], &app->playlist.item[idx], (app->playlist.count - idx) * sizeof(playlist_item_t) ); /* copy new items */ memcpy ( &app->playlist.item[idx], items, count * sizeof(playlist_item_t) ); /* increment servers indexes */ for(i = 0; i < app->players.count; i++) if(app->players.item[i].playlist_start >= idx) app->players.item[i].playlist_start += idx; /* increment items count */ app->playlist.count += count; /* redraw playlist */ ui_playlist_draw(app); /* select */ ui_playlist_select_item(app, idx); pthread_mutex_unlock(&app->players.lock); pthread_mutex_unlock(&app->playlist.lock); }; void playlist_item_copy(instance_t* app) { int *list, i, c; list = playlist_get_selected_items_idx(app, &c); if(!list) return; for(i = 0; i < c; i++) app->clipboard.item[i] = app->playlist.item[list[i]]; app->clipboard.count = c; free(list); }; void playlist_item_paste(instance_t* app, int after) { int idx, i; playlist_item_type_t t; /* find insert position */ idx = playlist_get_first_selected_item_idx(app); if(idx < 0) idx = 0; else idx += (after)?1:0; if(!playlist_insert_check(app, idx, &t)) return; /* clear item */ if(app->clipboard.count) { for(i = 0; i < app->clipboard.count; i++) { app->clipboard.item[i].type = t; app->clipboard.item[i].error = 0; }; playlist_insert_items(app, idx, app->clipboard.item, app->clipboard.count); }; }; void playlist_item_swap(instance_t* app, int dir) { int sel, a, b, e = 1; playlist_item_t item; /* find insert position */ sel = playlist_get_first_selected_item_idx(app); if(sel < 0) return; if(dir < 0) { a = sel - 1; b = sel; sel = a; } else { a = sel; b = sel + 1; sel = b; }; /* check for playing block */ if(playlist_idx_cued(app, a, NULL) || playlist_idx_cued(app, b, NULL)) return; pthread_mutex_lock(&app->playlist.lock); pthread_mutex_lock(&app->players.lock); /* swap */ item = app->playlist.item[a]; app->playlist.item[a] = app->playlist.item[b]; app->playlist.item[b] = item; /* rewite type */ if(app->playlist.item[a].type != app->playlist.item[b].type) { e = 0; app->playlist.item[a].type = PLAYLIST_ITEM_BLOCK_SINGLE; app->playlist.item[b].type = PLAYLIST_ITEM_BLOCK_SINGLE; }; /* redraw main items */ ui_playlist_draw_item(app, a); ui_playlist_draw_item(app, b); /* fix block types */ if(a && !e) { app->playlist.item[a - 1].type = (playlist_item_type_t)(app->playlist.item[a - 1].type | PLAYLIST_BLOCK_END); ui_playlist_draw_item(app, a - 1); }; if(b + 1 < app->playlist.count && !e) { app->playlist.item[b + 1].type = (playlist_item_type_t)(app->playlist.item[b + 1].type | PLAYLIST_BLOCK_BEGIN); ui_playlist_draw_item(app, b + 1); }; /* select */ ui_playlist_select_item(app, sel); pthread_mutex_unlock(&app->players.lock); pthread_mutex_unlock(&app->playlist.lock); }; static int playlist_load_plt(instance_t* app, char* filename, char* err_buf, int err_len) { FILE* f; char *line; int count = 0, i = 0; playlist_item_t* items; f = fopen(filename, "rt"); if(!f) { i = errno; snprintf(err_buf, err_len, "Failed to open file [%s], error: %s", filename, strerror(i)); return -i; }; /* allocate space for strings and items */ items = malloc(sizeof(playlist_item_t) * MAX_PLAYLIST_ITEMS); memset(items, 0, sizeof(playlist_item_t) * MAX_PLAYLIST_ITEMS); line = malloc(PATH_MAX); while(1) { char* s; if(feof(f)) break; /* load string */ memset(line, 0, PATH_MAX); fgets(line, PATH_MAX, f); /* remove newlines */ if( (s = strchr(line, '\n')) ) *s = 0; if( (s = strchr(line, '\r')) ) *s = 0; /* check for empty line */ if(!line[0] || line[0] == '#') continue; /* check item contine */ if(line[0] == '\t') { /* line without header */ if(!i) continue; /* load playlist item */ switch(i) { case 1: // title strncpy(items[count].title, line + 1, PATH_MAX); i++; break; case 2: // in tc2frames(line + 1, 25.0, &items[count].in); i++; break; case 3: // dur tc2frames(line + 1, 25.0, &items[count].dur); i++; break; case 4: // player items[count].player = atol(line + 1) - 1; i++; break; case 5: // type items[count].type = atol(line + 1) - 1024; /* last item - break a chain */ i = 0; count++; break; }; } else { strcpy(items[count].id, line); i = 1; }; }; fclose(f); /* add loaded items to playlist */ if(count) { pthread_mutex_lock(&app->playlist.lock); for(i = 0; i < count && app->playlist.count + 1 < MAX_PLAYLIST_ITEMS; i++) { // omnplay_library_normalize_item(app, &items[i]); app->playlist.item[app->playlist.count++] = items[i]; }; app->playlist.ver_curr++; pthread_mutex_unlock(&app->playlist.lock); } /* free items */ free(line); free(items); return 0; }; static int playlist_save_plt(instance_t* app, char* filename, char* err_buf, int err_len) { int i; FILE* f; char in[12], dur[12]; f = fopen(filename, "wt"); if(!f) { i = errno; strncpy(err_buf, strerror(i), err_len); return -i; }; for(i = 0; i < app->playlist.count; i++) fprintf ( f, "%s\n" // id "\t%s\n" // title "\t%s\n" // in "\t%s\n" // dur "\t%d\n" // player "\t%d\n", // type app->playlist.item[i].id, app->playlist.item[i].title, frames2tc(app->playlist.item[i].in, 25.0, in), frames2tc(app->playlist.item[i].dur, 25.0, dur), app->playlist.item[i].player + 1, app->playlist.item[i].type + 1024 ); fclose(f); return 0; }; static int playlist_load_plx(instance_t* app, char* filename, char* err_buf, int err_len) { strncpy(err_buf, "Method not implemented", err_len); return -1; }; static int playlist_save_plx(instance_t* app, char* filename, char* err_buf, int err_len) { strncpy(err_buf, "Method not implemented", err_len); return -1; }; static struct ui_playlist_io_funcs playlist_io[] = { { "Text formatted playlist (*.plt)", "*.plt", playlist_load_plt, playlist_save_plt }, { "Xml formatted playlist (*.plx)", "*.plx", playlist_load_plx, playlist_save_plx, }, { NULL, NULL, NULL, NULL } }; void playlist_load(instance_t* app) { ui_playlist_load(app, (app->playlist.path)?app->playlist.path:getenv("HOME"), playlist_io); }; void playlist_save(instance_t* app) { ui_playlist_save(app, (app->playlist.path)?app->playlist.path:getenv("HOME"), playlist_io); }; void playlist_relink(instance_t* app) { int i, cnt; int *list; pthread_mutex_lock(&app->playlist.lock); list = playlist_get_selected_items_idx(app, &cnt); if(list) { for(i = 0; i < cnt; i++) { /* check for playing block */ if(playlist_idx_cued(app, list[i], NULL)) continue; /* relink item */ library_relink_item(app, &app->playlist.item[list[i]]); }; free(list); }; pthread_mutex_unlock(&app->playlist.lock); /* redraw playlist */ ui_playlist_draw(app); }; void playlist_item_add(instance_t* app, int after) { int idx; playlist_item_t item; playlist_item_type_t t; /* find insert position */ idx = playlist_get_first_selected_item_idx(app); if(idx < 0) idx = 0; else idx += (after)?1:0; if(!playlist_insert_check(app, idx, &t)) return; g_warning("allowed insert into idx=%d\n", idx); /* clear item */ memset(&item, 0, sizeof(playlist_item_t)); if(ui_playlist_item_dialog(app, &item)) { library_normalize_item(app, &item); item.type = t; playlist_insert_items(app, idx, &item, 1); }; }; void playlist_item_edit(instance_t* app) { int idx; playlist_item_t item; /* find insert position */ idx = playlist_get_first_selected_item_idx(app); if(idx < 0) return; /* check for playing block */ if(playlist_idx_cued(app, idx, NULL)) return; item = app->playlist.item[idx]; if(ui_playlist_item_dialog(app, &item)) { library_normalize_item(app, &item); app->playlist.item[idx] = item; ui_playlist_draw_item(app, idx); }; }; void playlist_item_add_from_library(instance_t* app, int after) { int i, idx, cnt; playlist_item_t *items; playlist_item_type_t t; /* find insert position */ idx = playlist_get_first_selected_item_idx(app); if(idx < 0) idx = 0; else idx += (after)?1:0; if(!playlist_insert_check(app, idx, &t)) return; items = library_get_selected_items(app, &cnt); if(items) { for(i = 0; i < cnt; i++) { items[i].type = t; items[i].error = 0; }; playlist_insert_items(app, idx, items, cnt); free(items); }; }; void playlist_normalize(instance_t* app) { int i; /* normalize playlist */ for(i = 0; i < app->playlist.count; i++) if(library_normalize_item(app, &app->playlist.item[i])) ui_playlist_draw_item(app, i); };