ocarina/gui/view.c
Anna Schumaker f50c1541a1 gui/view: Scrolling improvements
This patch fixes two issues.  First, I disable scrolling when the user
has manually selected a track.  I've found that extra scrolling in this
case can be disorienting, and frequently ends up with the wrong track
selected due to how queue iterators are set so it's probably best just
to disable scrolling in this case.

This patch also changes the order of calls to set_cursor() and
scroll_to_cell().  I've found that scrolling before setting the cursor
causes the GtkTreeView to ignore alignment settings unless the row is
already on the screen.  Switching the order makes everything work
properly.

Fixes #57: Wrong track is sometimes scrolled to
Signed-off-by: Anna Schumaker <Anna@OcarinaProject.net>
2016-06-08 08:23:53 -04:00

371 lines
9.5 KiB
C

/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <core/collection.h>
#include <core/playlist.h>
#include <core/tempq.h>
#include <gui/builder.h>
#include <gui/model.h>
#include <gui/queue.h>
#include <gui/settings.h>
#include <gui/view.h>
#include <stdlib.h>
static const gchar *QUEUE_SETTINGS[Q_MODEL_N_COLUMNS] = {
[Q_MODEL_TRACK_NR] = "gui.queue.track",
[Q_MODEL_TITLE] = "gui.queue.title",
[Q_MODEL_LENGTH] = "gui.queue.length",
[Q_MODEL_ARTIST] = "gui.queue.artist",
[Q_MODEL_ALBUM] = "gui.queue.album",
[Q_MODEL_YEAR] = "gui.queue.year",
[Q_MODEL_GENRE] = "gui.queue.genre",
[Q_MODEL_COUNT] = "gui.queue.count",
[Q_MODEL_LAST_PLAY] = "gui.queue.played",
[Q_MODEL_FILE_PATH] = "gui.queue.filepath",
[Q_MODEL_FONT] = "gui.queue.font",
};
static const enum compare_t QUEUE_SORT[Q_MODEL_N_COLUMNS] = {
[Q_MODEL_TRACK_NR] = COMPARE_TRACK,
[Q_MODEL_TITLE] = COMPARE_TITLE,
[Q_MODEL_LENGTH] = COMPARE_LENGTH,
[Q_MODEL_ARTIST] = COMPARE_ARTIST,
[Q_MODEL_ALBUM] = COMPARE_ALBUM,
[Q_MODEL_YEAR] = COMPARE_YEAR,
[Q_MODEL_GENRE] = COMPARE_GENRE,
[Q_MODEL_COUNT] = COMPARE_COUNT,
[Q_MODEL_LAST_PLAY] = COMPARE_PLAYED,
};
static GtkTreeView *view_treeview = NULL;
static GtkTreeModelFilter *view_filter = NULL;
static unsigned int view_sort_count = 0;
static bool view_no_scroll = false;
static inline GuiQueueModel *__view_filter_get_model()
{
if (view_filter == NULL)
return NULL;
return GUI_QUEUE_MODEL(gtk_tree_model_filter_get_model(view_filter));
}
static inline struct queue *__view_filter_get_queue()
{
if (view_filter == NULL)
return NULL;
return __view_filter_get_model()->gqm_queue;
}
static inline GtkTreePath *__view_filter_convert_path(GtkTreePath *orig)
{
return gtk_tree_model_filter_convert_path_to_child_path(view_filter, orig);
}
static struct track *__view_filter_get_track(GtkTreePath *orig)
{
GuiQueueModel *model = __view_filter_get_model();
GtkTreePath *real = __view_filter_convert_path(orig);
struct track *track = gui_queue_model_path_get_track(model, real);
gtk_tree_path_free(real);
return track;
}
static unsigned int __view_filter_get_index(GtkTreePath *orig)
{
GtkTreePath *real = __view_filter_convert_path(orig);
unsigned int ret = gtk_tree_path_get_indices(real)[0];
gtk_tree_path_free(real);
return ret;
}
static unsigned int __view_get_column_index(GtkTreeViewColumn *col)
{
unsigned int i;
for (i = 0; i < Q_MODEL_N_COLUMNS; i++) {
if (col == gtk_tree_view_get_column(view_treeview, i))
return i;
}
return Q_MODEL_N_COLUMNS;
}
static inline void __view_display_sorting(gchar *text)
{
gtk_label_set_text(GTK_LABEL(gui_builder_widget("o_sorting")), text);
}
static int __view_dec_sort(gpointer data)
{
if (view_sort_count > 0)
view_sort_count--;
if (view_sort_count == 0)
__view_display_sorting("");
return FALSE;
}
static void __view_set_column_sort_indicator(GtkTreeViewColumn *col,
unsigned int index)
{
struct queue *queue = __view_filter_get_queue();
GSList *cur = queue ? queue->q_sort : NULL;
unsigned int order = GTK_SORT_ASCENDING;
bool show = false;
int field;
while (cur) {
order = GTK_SORT_ASCENDING;
field = GPOINTER_TO_INT(cur->data);
if (abs(field) == QUEUE_SORT[index]) {
show = true;
if (field < 0)
order = GTK_SORT_DESCENDING;
break;
}
cur = g_slist_next(cur);
}
gtk_tree_view_column_set_sort_indicator(col, show);
gtk_tree_view_column_set_sort_order(col, order);
}
static void __view_set_sort_indicators()
{
GtkTreeViewColumn *col;
for (unsigned int i = 0; i < Q_MODEL_N_COLUMNS; i++) {
col = gtk_tree_view_get_column(view_treeview, i);
if (col)
__view_set_column_sort_indicator(col, i);
}
}
void __view_row_activated(GtkTreeView *treeview, GtkTreePath *path,
GtkTreeViewColumn *col, gpointer data)
{
view_no_scroll = true;
audio_load(__view_filter_get_track(path));
queue_selected(__view_filter_get_model()->gqm_queue,
gtk_tree_path_get_indices(path)[0]);
view_no_scroll = false;
}
void __view_column_resized(GtkTreeViewColumn *col, GParamSpec *pspec,
gpointer data)
{
unsigned int index = __view_get_column_index(col);
gui_settings_set(QUEUE_SETTINGS[index],
gtk_tree_view_column_get_width(col));
}
void __view_column_clicked(GtkTreeViewColumn *col, gpointer data)
{
struct queue *queue = __view_filter_get_queue();
unsigned int index = __view_get_column_index(col);
gchar *text;
if (!queue || queue_has_flag(queue, Q_NO_SORT))
return;
queue_sort(queue, QUEUE_SORT[index], view_sort_count == 0);
if (view_sort_count == 0) {
text = g_strdup_printf("Sorting within %s",
gtk_tree_view_column_get_title(col));
__view_display_sorting(text);
g_free(text);
}
__view_set_sort_indicators();
view_sort_count++;
g_timeout_add_seconds(3, __view_dec_sort, NULL);
}
static void __view_add_to_queue(GtkTreeModel *model, GtkTreePath *path,
GtkTreeIter *iter, gpointer data)
{
queue_add(data, __view_filter_get_track(path));
}
static void __view_add_to_playlist(GtkTreeModel *model, GtkTreePath *path,
GtkTreeIter *iter, gpointer data)
{
playlist_add(GPOINTER_TO_UINT(data), __view_filter_get_track(path));
}
static void __view_delete_selection(GtkTreeSelection *selection)
{
struct queue *queue = __view_filter_get_queue();
GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
GList *cur = g_list_reverse(rows);
while (cur) {
queue_erase(queue, __view_filter_get_index(cur->data));
cur = g_list_next(cur);
}
g_list_free_full(rows, (GDestroyNotify) gtk_tree_path_free);
}
static void __view_process_selection(GtkTreeView *treeview, unsigned int keyval)
{
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
unsigned int flags = 0;
struct queue *queue;
switch (keyval) {
case GDK_KEY_KP_0 ... GDK_KEY_KP_9:
keyval = (keyval - GDK_KEY_KP_0) + GDK_KEY_0;
case GDK_KEY_0 ... GDK_KEY_9:
queue = tempq_get(keyval - GDK_KEY_0);
if (queue)
gtk_tree_selection_selected_foreach(selection,
__view_add_to_queue, queue);
break;
case GDK_KEY_f:
gtk_tree_selection_selected_foreach(selection,
__view_add_to_playlist,
GUINT_TO_POINTER(PL_FAVORITED));
break;
case GDK_KEY_r:
flags = Q_RANDOM;
case GDK_KEY_q:
queue = tempq_alloc(flags);
gtk_tree_selection_selected_foreach(selection,
__view_add_to_queue, queue);
break;
case GDK_KEY_Delete:
__view_delete_selection(selection);
break;
}
}
void __view_keypress(GtkTreeView *treeview, GdkEventKey *event, gpointer data)
{
__view_process_selection(treeview, event->keyval);
}
void __view_rc_new_queue(GtkMenuItem *item, gpointer data)
{
__view_process_selection(view_treeview, GDK_KEY_q);
}
void __view_rc_add_to_queue(GtkMenuItem *item, gpointer data)
{
unsigned int i;
gchar *name;
for (i = 0; i < 10; i++) {
name = g_strdup_printf("o_queue_%d", i);
if (GTK_WIDGET(item) == gui_builder_widget(name))
__view_process_selection(view_treeview, GDK_KEY_0 + i);
g_free(name);
}
}
void __view_rc_add_favorites(GtkMenuItem *item, gpointer data)
{
__view_process_selection(view_treeview, GDK_KEY_f);
}
void __view_rc_add_hidden(GtkMenuItem *item, gpointer data)
{
GtkTreeSelection *selection = gtk_tree_view_get_selection(view_treeview);
GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
GList *cur = g_list_reverse(rows);
while (cur) {
collection_ban(__view_filter_get_track(cur->data));
cur = g_list_next(cur);
}
g_list_free_full(rows, (GDestroyNotify) gtk_tree_path_free);
}
bool __view_button_press(GtkTreeView *treeview, GdkEventButton *event,
gpointer data)
{
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
GtkMenu *menu = GTK_MENU(gui_builder_widget("o_menu"));
unsigned int i, size = tempq_count();
GtkTreePath *path;
gchar *name;
if (event->button != 3)
return false;
/* Select path if it isn't already selected */
if (gtk_tree_view_get_path_at_pos(treeview, event->x, event->y,
&path, NULL, NULL, NULL))
{
gtk_tree_selection_select_path(selection, path);
gtk_tree_path_free(path);
}
/* Determine which menu items can be shown */
gtk_widget_set_visible(gui_builder_widget("o_new_queue"), size < 10);
gtk_widget_set_visible(gui_builder_widget("o_add_to_queue"), size > 0);
for (i = 0; i < 10; i++) {
name = g_strdup_printf("o_queue_%d", i);
gtk_widget_set_visible(gui_builder_widget(name), i < size);
g_free(name);
}
gtk_menu_popup(menu, NULL, NULL, NULL, NULL, event->button, event->time);
return true;
}
void gui_view_init()
{
GtkTreeViewColumn *col;
int i, pos;
view_treeview = GTK_TREE_VIEW(gui_builder_widget("o_treeview"));
for (i = 0; i < Q_MODEL_N_COLUMNS; i++) {
col = gtk_tree_view_get_column(view_treeview, i);
pos = gui_settings_get(QUEUE_SETTINGS[i]);
if (col && pos > 0)
gtk_tree_view_column_set_fixed_width(col, pos);
}
}
void gui_view_set_model(GtkTreeModelFilter *filter)
{
view_filter = filter;
gtk_tree_view_set_model(view_treeview, GTK_TREE_MODEL(filter));
view_sort_count = 0;
__view_display_sorting("");
__view_set_sort_indicators();
gui_view_scroll();
}
void gui_view_scroll()
{
struct queue *queue = __view_filter_get_queue();
GtkTreePath *real, *path;
if (!queue || (int)queue->q_cur.it_pos < 0 || view_no_scroll)
return;
real = gtk_tree_path_new_from_indices(queue->q_cur.it_pos, -1);
path = gtk_tree_model_filter_convert_child_path_to_path(view_filter, real);
if (!path)
goto out;
gtk_tree_view_set_cursor(view_treeview, path, NULL, false);
gtk_tree_view_scroll_to_cell(view_treeview, path, NULL, TRUE, 0.5, 0.5);
gtk_tree_path_free(path);
out:
gtk_tree_path_free(real);
}