/* * Copyright 2016 (c) Anna Schumaker. */ #include #include #include #include #include #include #include enum playlist_sidebar_columns { P_SB_IMAGE, P_SB_NAME, P_SB_TYPE, }; static GtkTreeStore *p_store; static gchar *p_name = NULL; static bool p_filter_enable = true; static bool p_init_done = false; static void __playlist_update_sizes(struct queue *); static inline void __playlist_filter_get_iter(GtkTreeIter *iter, GtkTreeIter *child) { gtk_tree_model_filter_convert_iter_to_child_iter( GTK_TREE_MODEL_FILTER(gui_builder_object("o_playlist_filter")), child, iter); } static inline enum playlist_type_t __playlist_type(GtkTreeIter *iter) { enum playlist_type_t type; gtk_tree_model_get(GTK_TREE_MODEL(p_store), iter, P_SB_TYPE, &type, -1); return (type < PL_MAX_TYPE) ? type : PL_SYSTEM; } static void __playlist_set(GtkTreeIter *iter, const gchar *name, const gchar *image, enum playlist_type_t type) { gtk_tree_store_set(p_store, iter, P_SB_NAME, name, P_SB_IMAGE, image, P_SB_TYPE, type, -1); } static void __playlist_set_size(GtkTreeIter *iter, const gchar *name) { GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(p_store), iter); enum playlist_type_t type = __playlist_type(iter); unsigned int size = playlist_size(type, name); const gchar *fmt = "%s\n%d track%s"; gchar *text; if (settings_get("core.playlist.cur.type") == type && settings_get("core.playlist.cur.id") == playlist_get_id(type, name)) fmt = "%s\n%d track%s"; text = g_markup_printf_escaped(fmt, name, size, (size == 1) ? "" : "s"); gtk_tree_store_set(p_store, iter, P_SB_NAME, text, -1); gtk_tree_path_free(path); g_free(text); } static void __playlist_add(GtkTreeIter *parent, const gchar *name, const gchar *image, enum playlist_type_t type) { GtkTreeIter iter; gtk_tree_store_insert(p_store, &iter, parent, -1); __playlist_set(&iter, name, image, type); __playlist_set_size(&iter, name); } static gchar *__playlist_name(GtkTreeIter *iter) { GtkTreeModel *model = GTK_TREE_MODEL(p_store); gchar *text, *parsed, *name, **split; gtk_tree_model_get(model, iter, P_SB_NAME, &text, -1); if (!text) return NULL; pango_parse_markup(text, -1, 0, NULL, &parsed, NULL, NULL); split = g_strsplit(parsed, "\n", 2); name = g_strdup(split[0]); g_strfreev(split); g_free(text); g_free(parsed); return name; } void __playlist_selection_changed(GtkTreeSelection *selection, gpointer data) { GtkTreeModel *model = GTK_TREE_MODEL(p_store); GtkTreeIter iter, child; struct queue *queue; if (gtk_tree_selection_get_selected(selection, &model, &iter)) { __playlist_filter_get_iter(&iter, &child); if (p_name) g_free(p_name); p_name = __playlist_name(&child); queue = playlist_get_queue(__playlist_type(&child), p_name); gui_queue_show(gui_queue(queue)); } } gboolean __playlist_on_select(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean selected, gpointer data) { struct queue *queue = NULL; GtkTreeIter iter, child; gchar *name; gtk_tree_model_get_iter(model, &iter, path); __playlist_filter_get_iter(&iter, &child); name = __playlist_name(&child); queue = playlist_get_queue(__playlist_type(&child), name); g_free(name); return queue != NULL; } bool __playlist_keypress(GtkTreeView *treeview, GdkEventKey *event, gpointer data) { GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview); GtkTreeModel *model = GTK_TREE_MODEL(p_store); GtkTreeIter iter, child; gchar *name = NULL; GtkTreePath *path; GList *rows; if (event->keyval != GDK_KEY_Delete) return false; rows = gtk_tree_selection_get_selected_rows(selection, &model); path = rows->data; if (!gtk_tree_model_get_iter(model, &iter, path)) return false; __playlist_filter_get_iter(&iter, &child); name = __playlist_name(&child); if (!name) goto out; if (playlist_delete(__playlist_type(&child), name)) gtk_tree_store_remove(p_store, &child); g_free(name); out: g_list_free_full(rows, (GDestroyNotify)gtk_tree_path_free); return true; } void __playlist_row_activated(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data) { enum playlist_type_t type; GtkTreeIter iter, child; GtkTreeModel *model; struct queue *queue; unsigned int id; gchar *name; type = settings_get("core.playlist.cur.type"); id = settings_get("core.playlist.cur.id"); model = GTK_TREE_MODEL(gui_builder_object("o_playlist_filter")); gtk_tree_model_get_iter(model, &iter, path); __playlist_filter_get_iter(&iter, &child); name = __playlist_name(&child); playlist_select(__playlist_type(&child), name); __playlist_set_size(&child, name); g_free(name); name = playlist_get_name(type, id); queue = playlist_get_queue(type, name); __playlist_update_sizes(queue); g_free(name); } static gboolean __playlist_visible_func(GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { gchar *name; bool show; if (!p_filter_enable) return true; name = __playlist_name(iter); show = __playlist_type(iter) == PL_USER || !playlist_get_queue(__playlist_type(iter), name) || (playlist_size(__playlist_type(iter), name) > 0); g_free(name); return show; } static bool __playlist_queue_set_size(struct queue *queue, GtkTreeIter *iter) { struct gui_queue *gq = gui_queue(queue); bool match = (__playlist_type(iter) == gq->gq_playlist->pl_type); gchar *name = __playlist_name(iter); if (match) match = string_match(name, gq->gq_text); if (match) __playlist_set_size(iter, name); g_free(name); return match; } static void __playlist_update_sizes(struct queue *queue) { GtkTreeModel *model = p_store ? GTK_TREE_MODEL(p_store) : NULL; GtkTreeModelFilter *filter; GtkTreeIter parent, iter; if (!model || !gtk_tree_model_get_iter_first(model, &parent)) return; do { if (__playlist_queue_set_size(queue, &parent)) goto out; if (gtk_tree_model_iter_children(model, &iter, &parent)) { do { if (__playlist_queue_set_size(queue, &iter)) goto out; } while (gtk_tree_model_iter_next(model, &iter)); } } while (gtk_tree_model_iter_next(model, &parent)); out: filter = GTK_TREE_MODEL_FILTER(gui_builder_object("o_playlist_filter")); gtk_tree_model_filter_refilter(filter); } static void *__playlist_init(struct queue *queue, void *data) { struct playlist *playlist = (struct playlist *)data; unsigned int flags = 0; if (!string_match(playlist->pl_name, "History")) flags = GQ_CAN_RANDOM; if (p_init_done && playlist->pl_type == PL_ARTIST) { p_filter_enable = false; gui_playlist_add_artist(artist_find(playlist->pl_name)); p_filter_enable = true; } if (p_init_done && playlist->pl_type == PL_USER) gui_playlist_add_user(playlist); return gui_queue_alloc(playlist, queue, playlist->pl_name, flags); } static void __playlist_deinit(struct queue *queue) { gui_filter_clear_search(gui_queue(queue)->gq_playlist); gui_queue_free(queue); } static void __playlist_added(struct queue *queue, unsigned int row) { gui_model_add(gui_queue(queue)->gq_playlist, row); __playlist_update_sizes(queue); } static void __playlist_removed(struct queue *queue, unsigned int row) { gui_model_remove(gui_queue(queue)->gq_playlist, row); __playlist_update_sizes(queue); } static void __playlist_cleared(struct queue *queue, unsigned int n) { gui_model_clear(gui_queue(queue)->gq_playlist, n); __playlist_update_sizes(queue); } static void __playlist_updated(struct queue *queue, unsigned int n) { gui_model_update(gui_queue(queue)->gq_playlist, n); } static bool __playlist_erase(struct queue *queue, struct track *track) { enum playlist_type_t type = gui_queue(queue)->gq_playlist->pl_type; const gchar *name = gui_queue(queue)->gq_playlist->pl_name; switch (type) { case PL_SYSTEM: if (string_match(name, "Collection")) name = "Hidden"; else if (!string_match(name, "Favorites") && !string_match(name, "Hidden") && !string_match(name, "Queued Tracks")) break; case PL_USER: playlist_remove(type, name, track); default: break; }; return false; } bool __gui_playlist_init_idle() { struct db_entry *dbe, *next; GtkTreeSelection *selection; GtkTreeView *treeview; GtkTreeModel *filter; GtkTreeIter iter; filter = GTK_TREE_MODEL(gui_builder_object("o_playlist_filter")); treeview = GTK_TREE_VIEW(gui_builder_widget("o_playlist_view")); selection = gtk_tree_view_get_selection(treeview); gtk_tree_model_get_iter_first(filter, &iter); gtk_tree_selection_select_iter(selection, &iter); p_filter_enable = false; db_for_each(dbe, next, pl_user_db_get()) gui_playlist_add_user(&USER_PLAYLIST(dbe)->pl_playlist); gtk_tree_view_expand_all(treeview); db_for_each(dbe, next, artist_db_get()) gui_playlist_add_artist(ARTIST(dbe)); p_filter_enable = true; p_init_done = true; return true; } void gui_playlist_init() { GtkTreeModelFilter *filter; GtkTreeView *treeview; GtkTreeIter parent; filter = GTK_TREE_MODEL_FILTER(gui_builder_object("o_playlist_filter")); p_store = GTK_TREE_STORE(gui_builder_object("o_playlist_store")); treeview = GTK_TREE_VIEW(gui_builder_widget("o_playlist_view")); gtk_tree_model_filter_set_visible_func(filter, __playlist_visible_func, NULL, NULL); gtk_tree_store_insert(p_store, &parent, NULL, -1); __playlist_set(&parent, "Queued Tracks", "audio-x-generic", PL_SYSTEM); __playlist_set_size(&parent, "Queued Tracks"); gtk_tree_store_insert(p_store, &parent, NULL, -1); __playlist_set(&parent, "Collection", "media-optical", PL_SYSTEM); __playlist_set_size(&parent, "Collection"); gtk_tree_store_insert(p_store, &parent, NULL, -1); __playlist_set(&parent, "History", "document-open-recent", PL_SYSTEM); __playlist_set_size(&parent, "History"); /* Add "Playlists" header and playlists. */ gtk_tree_store_insert(p_store, &parent, NULL, -1); gtk_tree_store_insert(p_store, &parent, NULL, -1); __playlist_set(&parent, "Playlists", "emblem-documents", 0); __playlist_add(&parent, "Favorites", "emblem-favorite", PL_SYSTEM); __playlist_add(&parent, "Hidden", "window-close", PL_SYSTEM); /* Add "Dynamic" header. */ gtk_tree_store_insert(p_store, &parent, NULL, -1); gtk_tree_store_insert(p_store, &parent, NULL, -1); __playlist_set(&parent, "Dynamic", "emblem-system", 0); /* Add playlists. */ __playlist_add(&parent, "Most Played", "go-up", PL_SYSTEM); __playlist_add(&parent, "Least Played", "go-down", PL_SYSTEM); __playlist_add(&parent, "Unplayed", "audio-x-generic", PL_SYSTEM); /* Add "Library" header. */ gtk_tree_store_insert(p_store, &parent, NULL, -1); gtk_tree_store_insert(p_store, &parent, NULL, -1); __playlist_set(&parent, "Library", "system-file-manager", 0); gtk_tree_selection_set_select_function( gtk_tree_view_get_selection(treeview), __playlist_on_select, NULL, NULL); idle_schedule(IDLE_SYNC, __gui_playlist_init_idle, NULL); } gchar *gui_playlist_cur() { return p_name; } void gui_playlist_add_library(struct library *library) { GtkTreeIter parent; gchar *name; gtk_tree_model_get_iter_first(GTK_TREE_MODEL(p_store), &parent); while (gtk_tree_model_iter_next(GTK_TREE_MODEL(p_store), &parent)) { name = __playlist_name(&parent); if (string_match(name, "Library")) __playlist_add(&parent, library->li_path, "folder", PL_LIBRARY); g_free(name); } gtk_tree_view_expand_all( GTK_TREE_VIEW(gui_builder_widget("o_playlist_view"))); } void gui_playlist_add_artist(struct artist *artist) { GtkTreeIter parent, sibling, iter; gchar *name; bool match; gtk_tree_model_get_iter_first(GTK_TREE_MODEL(p_store), &parent); do { name = __playlist_name(&parent); match = string_match(name, "Collection"); g_free(name); if (match) break; } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(p_store), &parent)); if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(p_store), &sibling, &parent)) { __playlist_add(&parent, artist->ar_name, "system-users", PL_ARTIST); return; } do { name = __playlist_name(&sibling); match = g_utf8_collate(name, artist->ar_name) >= 0; g_free(name); if (match) { gtk_tree_store_insert_before(p_store, &iter, &parent, &sibling); __playlist_set(&iter, artist->ar_name, "system-users", PL_ARTIST); __playlist_set_size(&iter, artist->ar_name); return; } } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(p_store), &sibling)); __playlist_add(&parent, artist->ar_name, "system-users", PL_ARTIST); } void gui_playlist_add_user(struct playlist *playlist) { GtkTreeIter parent, sibling, iter; gchar *name; bool match; gtk_tree_model_get_iter_first(GTK_TREE_MODEL(p_store), &parent); do { name = __playlist_name(&parent); match = string_match(name, "Playlists"); g_free(name); if (match) break; } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(p_store), &parent)); if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(p_store), &sibling, &parent)) { __playlist_add(&parent, playlist->pl_name, "text-x-generic", PL_USER); return; } do { name = __playlist_name(&sibling); match = g_utf8_collate(name, playlist->pl_name) >= 0; match = match && !(__playlist_type(&sibling) == PL_SYSTEM); g_free(name); if (match) { gtk_tree_store_insert_before(p_store, &iter, &parent, &sibling); __playlist_set(&iter, playlist->pl_name, "text-x-generic", PL_USER); __playlist_set_size(&iter, playlist->pl_name); return; } } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(p_store), &sibling)); __playlist_add(&parent, playlist->pl_name, "text-x-generic", PL_USER); } struct queue_ops playlist_ops = { .qop_init = __playlist_init, .qop_deinit = __playlist_deinit, .qop_added = __playlist_added, .qop_erase = __playlist_erase, .qop_removed = __playlist_removed, .qop_cleared = __playlist_cleared, .qop_updated = __playlist_updated, };