ocarina/gui/playlist.c

499 lines
14 KiB
C

/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <core/settings.h>
#include <core/string.h>
#include <gui/builder.h>
#include <gui/filter.h>
#include <gui/playlist.h>
#include <gui/queue.h>
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 = "<b>%s\n%d track%s</b>";
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, "<big>Playlists</big>", "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, "<big>Dynamic</big>", "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, "<big>Library</big>", "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,
};