/* * library.c -- GTK+ 2 melted gui * Copyright (C) 2012 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 #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include "library.h" #include "ui.h" #include "timecode.h" #include "support.h" extern GtkTargetEntry drag_targets[]; void library_release(instance_t* app) { mvcp_close(app->library.handle[0]); mvcp_parser_close(app->library.handle[1]); }; static void library_add_fake(instance_t* app, GtkTreeStore *tree_store, GtkTreeIter* parent) { GtkTreeIter iter; gtk_tree_store_append(tree_store, &iter, parent); gtk_tree_store_set(tree_store, &iter, -1); }; static int library_init_load(instance_t* app) { GtkTreeIter iter; GtkTreeStore *tree_store; tree_store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(app->library_tree))); gtk_tree_store_clear(tree_store); gtk_tree_store_append(tree_store, &iter, NULL); gtk_tree_store_set(tree_store, &iter, 0, app->library.icons[0], 1, ">", 2, "LIBRARY", 3, NULL, 4, NULL, 5, FALSE, 6, "red", -1 ); library_add_fake(app, tree_store, &iter); gtk_tree_view_collapse_all(GTK_TREE_VIEW(app->library_tree)); return 0; }; static void library_add_item(instance_t* app, GtkTreeStore *treestore, GtkTreeIter *iter, mvcp_dir_entry e, mvcp_list_entry p) { GtkTreeIter this, child; if(e->dir) { gtk_tree_store_prepend(treestore, &this, iter); gtk_tree_store_set(treestore, &this, 0, app->library.icons[0], 1, "", 2, e->name, 3, e, 4, NULL, 5, FALSE, 6, "red", -1 ); gtk_tree_store_append(treestore, &child, &this); gtk_tree_store_set(treestore, &child, -1); } else { char dur[32]; if(p) frames2tc(p->size, p->fps, dur); else strcpy(dur, ""); gtk_tree_store_append(treestore, &this, iter); gtk_tree_store_set(treestore, &this, 0, app->library.icons[1], 1, dur, 2, e->name, 3, e, 4, p, 5, FALSE, 6, "red", -1 ); }; }; static void on_library_row_expanded ( GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data ) { int i; char* p; GtkTreeIter fake; GtkTreeModel *model; GdkCursor* cursor; mvcp_dir dir; mvcp_dir_entry_t *e; instance_t* app = (instance_t*)user_data; // g_warning("on_library_row_expanded: HERE"); /* Set busy cursor */ cursor = gdk_cursor_new(GDK_WATCH); gdk_window_set_cursor(gtk_widget_get_toplevel(GTK_WIDGET(treeview))->window, cursor); gdk_cursor_unref(cursor); gdk_flush(); model = gtk_tree_view_get_model(treeview); /* save fake item */ gtk_tree_model_iter_children(GTK_TREE_MODEL(model), &fake, iter); /* request mvcp entry */ gtk_tree_model_get(GTK_TREE_MODEL(model), iter, 3, &e, -1); /* setup root path */ if(!e) p = "/"; else p = e->full; /* read dir */ dir = mvcp_dir_init(app->library.handle[0], p); for (i = 0; i < mvcp_dir_count(dir); i++) { mvcp_dir_entry_t dir_entry; mvcp_list_entry_t *list_e = NULL, list_entry; if(mvcp_ok != mvcp_dir_get(dir, i, &dir_entry)) continue; // g_warning("on_library_row_expanded: path=[%s], entry.dur=[%d], entry.full=[%s], entry.name[%s]", // p, entry.dir, entry.full, entry.name); e = (mvcp_dir_entry_t*)malloc(sizeof(mvcp_dir_entry_t)); *e = dir_entry; if(!e->dir && mvcp_ok == mvcp_probe_clip( app->library.handle[0], e->full, &list_entry)) { list_e = (mvcp_list_entry_t*)malloc(sizeof(mvcp_list_entry_t)); *list_e = list_entry; }; library_add_item(app, GTK_TREE_STORE(model), iter, e, list_e); }; /* restore cursor */ gdk_window_set_cursor(gtk_widget_get_toplevel(GTK_WIDGET(treeview))->window, NULL); /* delete fake item */ gtk_tree_store_remove(GTK_TREE_STORE(model), &fake); }; static void on_library_row_collapsed ( GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data ) { GtkTreeModel *model; GtkTreeIter child; // g_warning("on_library_row_collapsed: HERE"); /* delete all items */ model = gtk_tree_view_get_model(treeview); while (gtk_tree_model_iter_children(GTK_TREE_MODEL(model), &child, iter)) { mvcp_dir_entry_t *e; mvcp_list_entry_t *l; /* request mvcp entry */ gtk_tree_model_get(GTK_TREE_MODEL(model), &child, 3, &e, 4, &l, -1); /* free entry */ if(e) free(e); if(l) free(l); gtk_tree_store_remove(GTK_TREE_STORE(model), &child); }; /* add a fake element */ library_add_fake(user_data, GTK_TREE_STORE(model), iter); }; static void library_drag_data_get_cb(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data, guint info, guint time, gpointer userdata) { int c; playlist_item_t* items; instance_t* app = (instance_t*)userdata; g_warning("library_drag_data_get_cb"); items = library_get_selected_items(app, &c); /* clear item */ if(items) { gtk_selection_data_set(selection_data, selection_data->target, 8, (const guchar *)items, sizeof(playlist_item_t) * c); free(items); }; }; static void library_drag_begin_cb(GtkWidget *widget, GdkDragContext *context, gpointer userdata) { g_warning("library_drag_begin_cb"); gtk_drag_source_set_icon_stock(widget, GTK_STOCK_DND); }; void library_init(instance_t* app) { /* connect to library */ app->library.handle[1] = mvcp_parser_init_remote(app->players.host, app->library.port); app->library.handle[0] = mvcp_init(app->library.handle[1]); if(mvcp_connect(app->library.handle[0]) != mvcp_ok) { g_warning("library_init: failed to connect to server %s", app->players.host); return; }; /* setup icons */ app->library.icons[0] = create_pixbuf("Axialis_Team_playlist_open_16x16.png"); app->library.icons[1] = create_pixbuf("Axialis_Team_playlist_save_16x16.png"); /* load lib */ library_init_load(app); /* allow drag source */ gtk_drag_source_set(app->library_tree, GDK_BUTTON1_MASK, drag_targets, 1, (GdkDragAction)(GDK_ACTION_COPY)); /* set handlers */ gtk_signal_connect(GTK_OBJECT(app->library_tree), "row-expanded", GTK_SIGNAL_FUNC(on_library_row_expanded), app); gtk_signal_connect(GTK_OBJECT(app->library_tree), "row-collapsed", GTK_SIGNAL_FUNC(on_library_row_collapsed), app); g_signal_connect(GTK_OBJECT(app->library_tree), "drag_data_get", G_CALLBACK(library_drag_data_get_cb), app); g_signal_connect(GTK_OBJECT(app->library_tree), "drag_begin", G_CALLBACK(library_drag_begin_cb), app); }; static void library_get_selected_items_iter ( GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data ) { int l; mvcp_dir_entry_t *dir; mvcp_list_entry_t *list; playlist_item_t** pitems = (playlist_item_t**)data; playlist_item_t* items = *pitems; /* request pointers to list and dir entries of library items */ gtk_tree_model_get(model, iter, 3, &dir, 4, &list, -1); /* check if defined */ if(dir && list) { /* allocate items */ if(!items) { items = (playlist_item_t*)malloc(sizeof(playlist_item_t)); memset(items, 0, sizeof(playlist_item_t)); }; /* find numbers of items in list */ for(l = 0; items[l].id[0]; l++); g_warning("library_get_selected_items_iter: l=%d", l); /* realloc items */ items = (playlist_item_t*)realloc(items, (l + 2) * sizeof(playlist_item_t)); /* clean last item */ memset(&items[l + 1], 0, sizeof(playlist_item_t)); /* setup items */ memset(&items[l + 0], 0, sizeof(playlist_item_t)); strncpy(items[l].id, dir->name, PATH_MAX); strncpy(items[l].title, dir->full, PATH_MAX); items[l].dur = list->size; }; *pitems = items; }; playlist_item_t* library_get_selected_items(instance_t* app, int *count) { int l = 0; playlist_item_t* items = NULL; GtkTreeSelection *selection; selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(app->library_tree)); if(selection) { gtk_tree_selection_selected_foreach( selection, library_get_selected_items_iter, &items); if(items) for(; items[l].id[0]; l++); }; *count = l; return items; }; int library_normalize_item(instance_t* app, playlist_item_t* item) { #if 0 int r = 0; playlist_item_t* lib; playlist_item_t prev; pthread_mutex_lock(&app->library.lock); prev = *item; lib = omnplay_library_find(app, item->id); item->error = 0; if(lib) { if(!item->title[0]) { strcpy(item->title, lib->title); r++; }; if(item->in < lib->in || item->in >= (lib->in + lib->dur)) { item->in = lib->in; r++; }; if(!item->dur || (item->in + item->dur) > (lib->in + lib->dur)) { item->dur = lib->in + lib->dur - item->in; r++; }; if(r) g_warning("omnplay_library_normalize_item: [%s,%d,%d]->[%s,%d,%d]\n", prev.title, prev.in, prev.dur, item->title, item->in, item->dur); } else { r = 1; item->error = PLAYLIST_ITEM_ERROR_LIB; }; pthread_mutex_unlock(&app->library.lock); return r; #else return 0; #endif }; int library_relink_item(instance_t* app, playlist_item_t* item) { #if 0 int r = 0; playlist_item_t* lib; pthread_mutex_lock(&app->library.lock); lib = omnplay_library_find(app, item->id); item->error = 0; if(lib) { r = 1; strcpy(item->title, lib->title); item->dur = lib->dur; item->in = lib->in; } else { r = 1; item->error = PLAYLIST_ITEM_ERROR_LIB; }; pthread_mutex_unlock(&app->library.lock); return r; #else return 0; #endif }; #if 0 playlist_item_t* omnplay_library_find(omnplay_instance_t* app, char* id) { int i; playlist_item_t* item = NULL; pthread_mutex_lock(&app->library.lock); for(i = 0; i < app->library.count && !item; i++) if(!strcasecmp(id, app->library.item[i].id)) item = &app->library.item[i]; pthread_mutex_unlock(&app->library.lock); return item; }; void omnplay_library_sort(omnplay_instance_t* app) { int i, j, m; playlist_item_t item; for(i = 0; i < app->library.count; i++) { /* find max */ for(j = i + 1, m = i; j < app->library.count; j++) if(strcasecmp(app->library.item[j].id, app->library.item[m].id) < 0) m = j; if(m != i) { item = app->library.item[i]; app->library.item[i] = app->library.item[m]; app->library.item[m] = item; }; }; }; int omnplay_library_load_file(playlist_item_t* items, int *pcount, char* filename) { int i, c = 0, r = 0; FILE* f; char *l; int limit = *pcount; playlist_item_t item; /* allocate space for strings and items */ l = malloc(PATH_MAX); *pcount = 0; /* open and process file */ if((f = fopen(filename, "rt"))) { while(!feof(f) && c < limit) { char *s, *sp_r, *sp_b; /* load string */ memset(l, 0, PATH_MAX); fgets(l, PATH_MAX, f); /* remove newlines */ if( (s = strchr(l, '\n')) ) *s = 0; if( (s = strchr(l, '\r')) ) *s = 0; /* check for empty line */ if(l[0] && l[0] != '#' && l[0] != '|') { memset(&item, 0, sizeof(playlist_item_t)); for(i = 0, sp_b = l; (NULL != (sp_r = strtok(sp_b, "\t"))); i++, sp_b = NULL) { switch(i) { case 0: strncpy(item.id, sp_r, PATH_MAX); break; case 1: tc2frames(sp_r, 25.0, &item.in); break; case 2: tc2frames(sp_r, 25.0, &item.dur); break; case 3: strncpy(item.title, sp_r, PATH_MAX); break; }; }; /* insert item */ items[c++] = item; } else g_warning("omnplay_library_load_file: ignored line [%s]\n", l); } fclose(f); } else r = -1; /* free data */ free(l); *pcount = c; g_warning("omnplay_library_load_file: loaded [%d] items from [%s] file, limit [%d]\n", c, filename, limit); return r; }; static void omnplay_library_save_file(playlist_item_t* item, int count, char* filename) { int i; FILE* f; if((f = fopen(filename, "wt"))) { char tc_in[32], tc_dur[32]; for(i = 0; i < count; i++) fprintf(f, "%s\t%s\t%s\t%s\n", item[i].id, frames2tc(item[i].in, 25.0, tc_in), frames2tc(item[i].dur, 25.0, tc_dur), item[i].title); fclose(f); g_warning("omnplay_library_save_file: written [%d] lines to file [%s]\n", count, filename); }; }; void omnplay_library_save(omnplay_instance_t* app) { pthread_mutex_lock(&app->library.lock); if(app->library.filename[0]) omnplay_library_save_file(app->library.item, app->library.count, app->library.filename); pthread_mutex_unlock(&app->library.lock); }; static void omnplay_get_content_cb(omnplay_instance_t* app, playlist_item_t* item, void* data) { if(!(app->library.id_display_idx % app->library.id_display_rate)) omnplay_set_status(app, item->id); app->library.id_display_idx++; }; static void* omnplay_library_refresh_proc(void* data) { omnplay_instance_t* app = (omnplay_instance_t*)data; int count, i; playlist_item_t* items; gdk_threads_enter(); gtk_widget_set_sensitive(app->window, FALSE); gdk_flush(); gdk_threads_leave(); omnplay_set_status(app, "Updating library..."); items = (playlist_item_t*)malloc(sizeof(playlist_item_t) * MAX_LIBRARY_ITEMS); count = omnplay_get_content(app, items, MAX_LIBRARY_ITEMS, omnplay_get_content_cb, NULL); if(count > 0) { omnplay_set_status(app, "Quering whois..."); if(app->library.whois[0]) omnplay_whois_list(app, items, &count); omnplay_set_status(app, "Setting library..."); pthread_mutex_lock(&app->library.lock); for(i = 0; i < count; i++) app->library.item[i] = items[i]; app->library.count = count; omnplay_library_sort(app); pthread_mutex_unlock(&app->library.lock); gdk_threads_enter(); omnplay_library_draw(app); gdk_flush(); gdk_threads_leave(); }; omnplay_set_status(app, "Normalizing playlist..."); free(items); gdk_threads_enter(); omnplay_playlist_normalize(app); gdk_flush(); gdk_threads_leave(); omnplay_set_status(app, ""); gdk_threads_enter(); gtk_widget_set_sensitive(app->window, TRUE); gdk_flush(); gdk_threads_leave(); return NULL; }; void omnplay_library_refresh(omnplay_instance_t* app) { if(app->library.refresh_thread) g_thread_join(app->library.refresh_thread); app->library.refresh_thread = g_thread_create( omnplay_library_refresh_proc, app, TRUE, NULL); }; void omnplay_library_draw(omnplay_instance_t* app) { int i; char tc[12]; GtkListStore *list_store; GtkTreeIter iter; list_store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(app->library_grid))); gtk_list_store_clear(list_store); pthread_mutex_lock(&app->library.lock); for(i = 0;i < app->library.count; i++) { gtk_list_store_append(list_store, &iter); gtk_list_store_set(list_store, &iter, 0, app->library.item[i].id, 1, frames2tc(app->library.item[i].dur, 25.0, tc), 2, app->library.item[i].title, 3, i, 4, FALSE, 5, "red", -1 ); } pthread_mutex_unlock(&app->library.lock); }; static void get_selected_idx_library_proc(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { int idx, *list = (int*)data; gtk_tree_model_get(model, iter, 3, &idx, -1); list[list[0] + 1] = idx; list[0] = list[0] + 1; }; static int* get_selected_idx_library(omnplay_instance_t* app) { int* list = NULL; GtkTreeSelection *selection; selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(app->library_grid)); if(selection) { list = (int*)malloc(sizeof(int) * (MAX_LIBRARY_ITEMS + 1)); memset(list, 0, sizeof(int) * (MAX_LIBRARY_ITEMS + 1)); gtk_tree_selection_selected_foreach( selection, get_selected_idx_library_proc, list); if(!list[0]) { free(list); list = NULL; }; }; return list; }; playlist_item_t* omnplay_library_get_selected(omnplay_instance_t* app, int *count) { int* idxs; playlist_item_t* items = NULL; pthread_mutex_lock(&app->library.lock); *count = 0; idxs = get_selected_idx_library(app); if(idxs) { int i; /* alloc items */ items = (playlist_item_t*)malloc(sizeof(playlist_item_t) * (idxs[0] + 1)); /* clear last item */ memset(&items[idxs[0]], 0, sizeof(playlist_item_t)); /* copy items */ for(i = 0; i < idxs[0]; i++) items[i] = app->library.item[idxs[i + 1]]; *count = idxs[0]; free(idxs); }; pthread_mutex_unlock(&app->library.lock); return items; }; void omnplay_library_search(omnplay_instance_t* app, int next) { int idx = 0, i; int* idxs; const char *search; GtkTreePath* path; pthread_mutex_lock(&app->library.lock); idxs = get_selected_idx_library(app); if(idxs) idx = idxs[1]; free(idxs); if(!next) idx = 0; else idx++; search = gtk_entry_get_text(GTK_ENTRY(app->library.search)); if(search[0]) { for(i = idx; i < app->library.count; i++) if( strcasestr(app->library.item[i].id, search) || strcasestr(app->library.item[i].title, search)) break; if(i < app->library.count) { g_warning("found at pos=%d\n", i); /* select */ path = gtk_tree_path_new_from_indices(i, -1); gtk_tree_selection_select_path(gtk_tree_view_get_selection( GTK_TREE_VIEW(app->library_grid)), path); gtk_tree_view_set_cursor(GTK_TREE_VIEW(app->library_grid), path, NULL, FALSE); gtk_tree_path_free(path); }; }; pthread_mutex_unlock(&app->library.lock); }; #endif