1214 lines
34 KiB
C
1214 lines
34 KiB
C
#include "private.h"
|
|
#include "mpris.h"
|
|
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
|
|
/*
|
|
* TODO:
|
|
* - create page_folder that contains something other than songs, click one
|
|
* should create a new page, stack it into the parent list and recurse.
|
|
* - add suffle action for page_songs
|
|
*/
|
|
|
|
/* number of songs to populate at once before going back to mainloop */
|
|
#define PAGE_SONGS_POPULATE_ITERATION_COUNT (64)
|
|
|
|
/* number of folders to populate at once before going back to mainloop */
|
|
#define PAGE_FOLDERS_POPULATE_ITERATION_COUNT (64)
|
|
|
|
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
|
|
|
|
struct Array_Iterator
|
|
{
|
|
Eina_Iterator base;
|
|
const void *container;
|
|
size_t current, count, item_size;
|
|
};
|
|
|
|
static void *
|
|
_array_iterator_container_get(Eina_Iterator *iterator)
|
|
{
|
|
struct Array_Iterator *it = (struct Array_Iterator *)iterator;
|
|
if (!EINA_MAGIC_CHECK(iterator, EINA_MAGIC_ITERATOR))
|
|
{
|
|
EINA_MAGIC_FAIL(iterator, EINA_MAGIC_ITERATOR);
|
|
return NULL;
|
|
}
|
|
return (void *)it->container;
|
|
}
|
|
|
|
static void
|
|
_array_iterator_free(Eina_Iterator *iterator)
|
|
{
|
|
struct Array_Iterator *it = (struct Array_Iterator *)iterator;
|
|
if (!EINA_MAGIC_CHECK(iterator, EINA_MAGIC_ITERATOR))
|
|
{
|
|
EINA_MAGIC_FAIL(iterator, EINA_MAGIC_ITERATOR);
|
|
return;
|
|
}
|
|
EINA_MAGIC_SET(&it->base, EINA_MAGIC_NONE);
|
|
free(it);
|
|
}
|
|
|
|
static Eina_Bool
|
|
_array_iterator_next(Eina_Iterator *iterator, void **data)
|
|
{
|
|
struct Array_Iterator *it = (struct Array_Iterator *)iterator;
|
|
if (!EINA_MAGIC_CHECK(iterator, EINA_MAGIC_ITERATOR))
|
|
{
|
|
EINA_MAGIC_FAIL(iterator, EINA_MAGIC_ITERATOR);
|
|
return EINA_FALSE;
|
|
}
|
|
EINA_SAFETY_ON_NULL_RETURN_VAL(data, EINA_FALSE);
|
|
*data = NULL;
|
|
if (it->current >= it->count) return EINA_FALSE;
|
|
*data = ((char *)it->container) + it->item_size * it->current;
|
|
it->current++;
|
|
return EINA_TRUE;
|
|
}
|
|
|
|
static Eina_Iterator *
|
|
_array_iterator_new(const void *array, size_t item_size, size_t count)
|
|
{
|
|
struct Array_Iterator *it = calloc(1, sizeof(*it));
|
|
EINA_SAFETY_ON_NULL_RETURN_VAL(it, NULL);
|
|
it->base.get_container = _array_iterator_container_get;
|
|
it->base.free = _array_iterator_free;
|
|
it->base.next = _array_iterator_next;
|
|
it->container = array;
|
|
it->item_size = item_size;
|
|
it->count = count;
|
|
EINA_MAGIC_SET(&it->base, EINA_MAGIC_ITERATOR);
|
|
return &it->base;
|
|
}
|
|
|
|
static void *
|
|
_data_from_itr_passthrough(const void *data)
|
|
{
|
|
return (void *)data;
|
|
}
|
|
|
|
typedef struct _Page Page;
|
|
|
|
typedef struct _Page_Class
|
|
{
|
|
const char *name;
|
|
const char *key;
|
|
const char *layout;
|
|
Eina_Bool (*init)(Page *page); /* optional extra initializer */
|
|
void (*after_populate)(Page *page); /* called after page has been populated */
|
|
Evas_Smart_Cb selected;
|
|
const Elm_Genlist_Item_Class *item_cls;
|
|
size_t populate_iteration_count;
|
|
void *(*data_from_itr)(const void *data);
|
|
size_t data_letter_offset;
|
|
unsigned short icon_size;
|
|
} Page_Class;
|
|
|
|
struct _Page
|
|
{
|
|
const Page_Class *cls;
|
|
Evas_Object *layout;
|
|
Evas_Object *edje;
|
|
Evas_Object *list;
|
|
Evas_Object *index;
|
|
Evas_Object *parent;
|
|
size_t num_elements;
|
|
const char *title;
|
|
void *container;
|
|
void *model;
|
|
Elm_Genlist_Item *selected;
|
|
Elm_Genlist_Item *first;
|
|
Eina_Iterator *iterator;
|
|
Ecore_Idler *populate;
|
|
char last_index_letter[2];
|
|
struct {
|
|
void *data;
|
|
void (*free)(void *data);
|
|
} item;
|
|
};
|
|
|
|
#define PAGE_GET_OR_RETURN(page, obj, ...) \
|
|
Page *page = evas_object_data_get(obj, "_enjoy_page"); \
|
|
do \
|
|
{ \
|
|
if (!page) \
|
|
{ \
|
|
CRITICAL("Not a page: "#obj": %p", obj); \
|
|
return __VA_ARGS__; \
|
|
} \
|
|
} \
|
|
while (0)
|
|
|
|
static DB *
|
|
_page_db_get(const Evas_Object *obj)
|
|
{
|
|
Page *page, *ppage;
|
|
|
|
page = evas_object_data_get(obj, "_enjoy_page");
|
|
EINA_SAFETY_ON_NULL_RETURN_VAL(page, NULL);
|
|
ppage = evas_object_data_get(page->parent, "_enjoy_page");
|
|
if (ppage) return _page_db_get(page->parent);
|
|
return list_db_get(page->parent);
|
|
}
|
|
|
|
static Eina_Bool
|
|
_page_populate(void *data)
|
|
{
|
|
Page *page = data;
|
|
const Page_Class *cls = page->cls;
|
|
unsigned int count;
|
|
|
|
for (count = 0; count < cls->populate_iteration_count; count++)
|
|
{
|
|
Elm_Genlist_Item *it;
|
|
char letter;
|
|
void *id, *od;
|
|
const char **letter_str;
|
|
|
|
if (!eina_iterator_next(page->iterator, &id)) goto end;
|
|
// TODO: evaluate if we should keep a full copy or just store
|
|
// fields of interest such as id, title, artist and album
|
|
od = cls->data_from_itr(id);
|
|
if (!od) goto end;
|
|
|
|
it = elm_genlist_item_append
|
|
(page->list, cls->item_cls, od,
|
|
NULL, ELM_GENLIST_ITEM_NONE, NULL, NULL);
|
|
|
|
letter_str = (const char **)(((char *)od) + cls->data_letter_offset);
|
|
|
|
letter = toupper((*letter_str)[0]);
|
|
if ((page->index) &&
|
|
(isalpha(letter) && (page->last_index_letter[0] != letter)))
|
|
{
|
|
if ((page->first) && (!page->last_index_letter[0]))
|
|
elm_index_item_append(page->index, "Special", page->first);
|
|
|
|
page->last_index_letter[0] = letter;
|
|
elm_index_item_append(page->index, page->last_index_letter, it);
|
|
}
|
|
if (!page->first) page->first = it;
|
|
page->num_elements++;
|
|
}
|
|
|
|
return EINA_TRUE;
|
|
|
|
end:
|
|
if (cls->after_populate)
|
|
cls->after_populate(page);
|
|
|
|
page->populate = NULL;
|
|
return EINA_FALSE;
|
|
}
|
|
|
|
static void
|
|
_page_selected(void *data, Evas_Object *o, void *event_info)
|
|
{
|
|
Page *page = data;
|
|
Elm_Genlist_Item *it = event_info;
|
|
if (page->selected == it) return;
|
|
page->selected = it;
|
|
page->cls->selected(data, o, event_info);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
_page_index_changed(void *data __UNUSED__, Evas_Object *o __UNUSED__, void *event_info)
|
|
{
|
|
Elm_Genlist_Item *it = event_info;
|
|
elm_genlist_item_top_bring_in(it);
|
|
}
|
|
|
|
static void
|
|
_page_del(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info __UNUSED__)
|
|
{
|
|
Page *page = data;
|
|
eina_stringshare_del(page->title);
|
|
if (page->iterator) eina_iterator_free(page->iterator);
|
|
if (page->populate) ecore_idler_del(page->populate);
|
|
if (page->item.free) page->item.free(page->item.data);
|
|
free(page);
|
|
}
|
|
|
|
static void
|
|
_page_back(void *data, Evas_Object *o __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
|
|
{
|
|
Page *page = data;
|
|
evas_object_smart_callback_call(page->layout, "back", NULL);
|
|
}
|
|
|
|
static void
|
|
_page_songs(void *data, Evas_Object *o __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
|
|
{
|
|
Page *page = data;
|
|
evas_object_smart_callback_call(page->layout, "songs", NULL);
|
|
}
|
|
|
|
static Evas_Object *
|
|
_page_add(Evas_Object *parent, void *model, Eina_Iterator *it, const char *title, const Page_Class *cls)
|
|
{
|
|
Evas_Object *obj;
|
|
Page *page;
|
|
const char *s;
|
|
|
|
EINA_SAFETY_ON_NULL_RETURN_VAL(it, NULL);
|
|
EINA_SAFETY_ON_NULL_RETURN_VAL(cls, NULL);
|
|
EINA_SAFETY_ON_NULL_RETURN_VAL(cls->name, NULL);
|
|
EINA_SAFETY_ON_NULL_RETURN_VAL(cls->key, NULL);
|
|
EINA_SAFETY_ON_NULL_RETURN_VAL(cls->layout, NULL);
|
|
EINA_SAFETY_ON_NULL_RETURN_VAL(cls->selected, NULL);
|
|
EINA_SAFETY_ON_NULL_RETURN_VAL(cls->item_cls, NULL);
|
|
EINA_SAFETY_ON_NULL_RETURN_VAL(cls->data_from_itr, NULL);
|
|
|
|
DBG("creating page %s with key %s, item style %s",
|
|
cls->name, cls->key, cls->item_cls->item_style);
|
|
|
|
obj = elm_layout_add(parent);
|
|
if (!obj)
|
|
{
|
|
eina_iterator_free(it);
|
|
return NULL;
|
|
}
|
|
|
|
page = calloc(1, sizeof(*page));
|
|
if (!page)
|
|
{
|
|
CRITICAL("could not allocate page memory!");
|
|
eina_iterator_free(it);
|
|
goto error;
|
|
}
|
|
evas_object_data_set(obj, "_enjoy_page", page);
|
|
evas_object_data_set(obj, cls->key, page);
|
|
evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL, _page_del, page);
|
|
page->layout = obj;
|
|
page->model = model;
|
|
page->iterator = it;
|
|
page->cls = cls;
|
|
page->parent = parent;
|
|
|
|
if (!elm_layout_file_set(obj, PACKAGE_DATA_DIR "/default.edj", cls->layout))
|
|
{
|
|
CRITICAL("no theme for '%s' at %s",
|
|
cls->layout, PACKAGE_DATA_DIR "/default.edj");
|
|
goto error;
|
|
}
|
|
|
|
page->title = eina_stringshare_add(title);
|
|
page->edje = elm_layout_edje_get(obj);
|
|
edje_object_part_text_set(page->edje, "ejy.text.title", page->title);
|
|
edje_object_signal_callback_add
|
|
(page->edje, "ejy,back,clicked", "ejy", _page_back, page);
|
|
edje_object_signal_callback_add
|
|
(page->edje, "ejy,songs,clicked", "ejy", _page_songs, page);
|
|
|
|
page->list = elm_genlist_add(obj);
|
|
elm_genlist_bounce_set(page->list, EINA_FALSE, EINA_TRUE);
|
|
elm_genlist_horizontal_mode_set(page->list, ELM_LIST_COMPRESS);
|
|
elm_genlist_compress_mode_set(page->list, EINA_TRUE);
|
|
elm_object_style_set(page->list, "enjoy");
|
|
|
|
s = edje_object_data_get(page->edje, "homogeneous");
|
|
elm_genlist_homogeneous_set(page->list, s ? !!atoi(s) : EINA_FALSE);
|
|
|
|
elm_layout_content_set(obj, "ejy.swallow.list", page->list);
|
|
|
|
if (edje_object_part_exists(page->edje, "ejy.swallow.index"))
|
|
{
|
|
page->index = elm_index_add(obj);
|
|
evas_object_smart_callback_add
|
|
(page->index, "delay,changed", _page_index_changed, page);
|
|
elm_layout_content_set(obj, "ejy.swallow.index", page->index);
|
|
}
|
|
|
|
page->container = eina_iterator_container_get(it);
|
|
evas_object_data_set(page->list, "_enjoy_container", page->container);
|
|
evas_object_data_set(page->list, "_enjoy_page", page);
|
|
evas_object_smart_callback_add(page->list, "selected", _page_selected, page);
|
|
|
|
if (cls->init)
|
|
{
|
|
if (!cls->init(page)) goto error;
|
|
}
|
|
|
|
page->populate = ecore_idler_add(_page_populate, page);
|
|
|
|
return obj;
|
|
|
|
error:
|
|
evas_object_del(obj); /* should delete everything */
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
page_songs_exists_changed(Evas_Object *obj, Eina_Bool exists)
|
|
{
|
|
PAGE_GET_OR_RETURN(page, obj);
|
|
if (exists)
|
|
edje_object_signal_emit(page->edje, "ejy,songs,show", "ejy");
|
|
else
|
|
edje_object_signal_emit(page->edje, "ejy,songs,hide", "ejy");
|
|
|
|
}
|
|
|
|
const char *
|
|
page_title_get(const Evas_Object *obj)
|
|
{
|
|
PAGE_GET_OR_RETURN(page, obj, NULL);
|
|
return page->title;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* SONGS
|
|
***********************************************************************/
|
|
|
|
#define PAGE_SONGS_GET_OR_RETURN(page, obj, ...) \
|
|
Page *page = evas_object_data_get(obj, "_enjoy_page_songs"); \
|
|
do \
|
|
{ \
|
|
if (!page) \
|
|
{ \
|
|
CRITICAL("Not a page_song: "#obj": %p", obj); \
|
|
return __VA_ARGS__; \
|
|
} \
|
|
} \
|
|
while (0)
|
|
|
|
|
|
static char *
|
|
_song_item_label_get(void *data, Evas_Object *list, const char *part)
|
|
{
|
|
Song *song = data;
|
|
|
|
/* check if matches protocol */
|
|
if (strncmp(part, "ejy.text.", sizeof("ejy.text.") - 1) != 0)
|
|
return NULL;
|
|
part += sizeof("ejy.text.") - 1;
|
|
|
|
if (!strcmp(part, "title"))
|
|
return strdup(song->title);
|
|
else if (!strcmp(part, "trackno-title"))
|
|
{
|
|
char *str;
|
|
if (song->trackno < 1) return strdup(song->title);
|
|
if (asprintf(&str, "%d - %s", song->trackno, song->title) > 0)
|
|
return str;
|
|
return NULL;
|
|
}
|
|
else if (!strcmp(part, "album-artist"))
|
|
{
|
|
char *str;
|
|
|
|
if ((!song->flags.fetched_album) || (!song->flags.fetched_artist))
|
|
{
|
|
DB *db = evas_object_data_get(list, "_enjoy_container");
|
|
db_song_album_fetch(db, song);
|
|
db_song_artist_fetch(db, song);
|
|
}
|
|
|
|
if ((!song->album) && (!song->artist)) return NULL;
|
|
else if (!song->album) return strdup(song->artist);
|
|
else if (!song->artist) return strdup(song->album);
|
|
|
|
if (asprintf(&str, "%s - %s", song->album, song->artist) > 0)
|
|
return str;
|
|
return NULL;
|
|
}
|
|
else if (!strcmp(part, "album"))
|
|
{
|
|
if (!song->flags.fetched_album)
|
|
{
|
|
DB *db = evas_object_data_get(list, "_enjoy_container");
|
|
db_song_album_fetch(db, song);
|
|
}
|
|
return song->album ? strdup(song->album) : NULL;
|
|
}
|
|
else if (!strcmp(part, "artist"))
|
|
{
|
|
if (!song->flags.fetched_artist)
|
|
{
|
|
DB *db = evas_object_data_get(list, "_enjoy_container");
|
|
db_song_artist_fetch(db, song);
|
|
}
|
|
return song->artist ? strdup(song->artist) : NULL;
|
|
}
|
|
else if (!strcmp(part, "genre"))
|
|
{
|
|
if (!song->flags.fetched_genre)
|
|
{
|
|
DB *db = evas_object_data_get(list, "_enjoy_container");
|
|
db_song_genre_fetch(db, song);
|
|
}
|
|
return song->genre ? strdup(song->genre) : NULL;
|
|
}
|
|
else if (!strcmp(part, "trackno"))
|
|
{
|
|
char *str;
|
|
if (song->trackno < 1) return NULL;
|
|
if (asprintf(&str, "%d", song->trackno) > 0)
|
|
return str;
|
|
return NULL;
|
|
}
|
|
else if (!strcmp(part, "playcnt"))
|
|
{
|
|
char *str;
|
|
if (song->playcnt < 1) return NULL;
|
|
if (asprintf(&str, "%d", song->playcnt) > 0)
|
|
return str;
|
|
return NULL;
|
|
}
|
|
else if (!strcmp(part, "rating"))
|
|
{
|
|
char *str;
|
|
if (song->rating < 1) return NULL;
|
|
if (asprintf(&str, "%d", song->rating) > 0)
|
|
return str;
|
|
return NULL;
|
|
}
|
|
else if (!strcmp(part, "length"))
|
|
{
|
|
char *str;
|
|
int len;
|
|
if (song->length < 1) return NULL;
|
|
if (song->length < 60)
|
|
len = asprintf(&str, "%d", song->length);
|
|
else if (song->length < 60 * 60)
|
|
len = asprintf(&str, "%d:%02d", song->length / 60, song->length % 60);
|
|
else
|
|
len = asprintf(&str, "%d:%02d:%02d",
|
|
song->length / (60 * 60),
|
|
(song->length / 60) % 60,
|
|
song->length % 60);
|
|
if (len > 0) return str;
|
|
return NULL;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static Eina_Bool
|
|
_song_item_state_get(void *data, Evas_Object *list, const char *part)
|
|
{
|
|
Song *song = data;
|
|
|
|
/* check if matches protocol */
|
|
if (strncmp(part, "ejy.state.", sizeof("ejy.state.") - 1) != 0)
|
|
return EINA_FALSE;
|
|
part += sizeof("ejy.state.") - 1;
|
|
|
|
if (!strcmp(part, "title")) return EINA_TRUE;
|
|
else if (!strcmp(part, "trackno")) return song->trackno > 0;
|
|
else if (!strcmp(part, "playcnt")) return song->playcnt > 0;
|
|
else if (!strcmp(part, "rating")) return song->rating > 0;
|
|
else if (!strcmp(part, "length")) return song->length > 0;
|
|
else if (!strcmp(part, "artist"))
|
|
{
|
|
if (!song->flags.fetched_artist)
|
|
{
|
|
DB *db = evas_object_data_get(list, "_enjoy_container");
|
|
db_song_artist_fetch(db, song);
|
|
}
|
|
|
|
return !!song->artist;
|
|
}
|
|
else if (!strcmp(part, "album"))
|
|
{
|
|
if (!song->flags.fetched_album)
|
|
{
|
|
DB *db = evas_object_data_get(list, "_enjoy_container");
|
|
db_song_album_fetch(db, song);
|
|
}
|
|
|
|
return !!song->album;
|
|
}
|
|
else if (!strcmp(part, "genre"))
|
|
{
|
|
if (!song->flags.fetched_genre)
|
|
{
|
|
DB *db = evas_object_data_get(list, "_enjoy_container");
|
|
db_song_genre_fetch(db, song);
|
|
}
|
|
return !!song->genre;
|
|
}
|
|
|
|
return EINA_FALSE;
|
|
}
|
|
|
|
|
|
static void
|
|
_song_item_del(void *data, Evas_Object *list __UNUSED__)
|
|
{
|
|
db_song_free(data);
|
|
}
|
|
|
|
static void
|
|
_song_item_selected(void *data, Evas_Object *list __UNUSED__, void *event_info)
|
|
{
|
|
Page *page = data;
|
|
Song *song = elm_genlist_item_data_get(event_info);
|
|
if (song) evas_object_smart_callback_call(page->layout, "song", song);
|
|
}
|
|
|
|
static void
|
|
_page_songs_after_populate(Page *page)
|
|
{
|
|
mpris_signal_tracklist_tracklist_change(page->num_elements);
|
|
}
|
|
|
|
static Evas_Object *
|
|
_page_songs_add(Evas_Object *parent, NameID *nameid, Eina_Iterator *it, const char *title)
|
|
{
|
|
static const Elm_Genlist_Item_Class song_item_cls = {
|
|
"song",
|
|
{
|
|
_song_item_label_get,
|
|
NULL,
|
|
_song_item_state_get,
|
|
_song_item_del
|
|
}
|
|
};
|
|
static const Page_Class song_cls = {
|
|
"song",
|
|
"_enjoy_page_songs",
|
|
"page/songs",
|
|
NULL,
|
|
_page_songs_after_populate,
|
|
_song_item_selected,
|
|
&song_item_cls,
|
|
PAGE_SONGS_POPULATE_ITERATION_COUNT,
|
|
(void *(*)(const void*))db_song_copy,
|
|
offsetof(Song, title),
|
|
0
|
|
};
|
|
return _page_add(parent, nameid, it, title, &song_cls);
|
|
}
|
|
|
|
static void
|
|
_song_album_cover_size_changed(void *data, Evas *e __UNUSED__, Evas_Object *part, void *event_info __UNUSED__)
|
|
{
|
|
Page *page = data;
|
|
Evas_Coord size, w, h;
|
|
Evas_Object *cover;
|
|
|
|
evas_object_geometry_get(part, NULL, NULL, &w, &h);
|
|
if (w < 32) w = 32;
|
|
if (h < 32) h = 32;
|
|
size = (w < h) ? w : h;
|
|
|
|
DBG("cover view changed size to %dx%d, query cover size %d", w, h, size);
|
|
cover = cover_album_fetch(page->layout, page->container, page->model, size);
|
|
elm_layout_content_set(page->layout, "ejy.swallow.cover", cover);
|
|
}
|
|
|
|
static Eina_Bool
|
|
_song_album_init(Page *page)
|
|
{
|
|
Album *album = page->model;
|
|
|
|
if (edje_object_part_exists(page->edje, "ejy.swallow.cover"))
|
|
{
|
|
DB *db = _page_db_get(page->layout);
|
|
Evas_Object *cover, *part;
|
|
const char *s;
|
|
int size = 0;
|
|
|
|
s = edje_object_data_get(page->edje, "cover_size");
|
|
if (s) size = atoi(s);
|
|
if (size < 32) size = 32;
|
|
|
|
cover = cover_album_fetch(page->layout, db, album, size);
|
|
elm_layout_content_set(page->layout, "ejy.swallow.cover", cover);
|
|
|
|
part = (Evas_Object *)
|
|
edje_object_part_object_get(page->edje, "ejy.swallow.cover");
|
|
evas_object_event_callback_add
|
|
(part, EVAS_CALLBACK_RESIZE, _song_album_cover_size_changed, page);
|
|
}
|
|
|
|
if (album->name)
|
|
edje_object_part_text_set(page->edje, "ejy.text.album", album->name);
|
|
else
|
|
edje_object_part_text_set(page->edje, "ejy.text.album", "");
|
|
|
|
if (album->artist)
|
|
edje_object_part_text_set(page->edje, "ejy.text.artist", album->artist);
|
|
else
|
|
edje_object_part_text_set(page->edje, "ejy.text.artist", "");
|
|
|
|
return EINA_TRUE;
|
|
}
|
|
|
|
static Evas_Object *
|
|
_page_album_songs_add(Evas_Object *parent, Album *album)
|
|
{
|
|
DB *db = _page_db_get(parent);
|
|
Eina_Iterator *it;
|
|
static const Elm_Genlist_Item_Class song_item_cls = {
|
|
"song-album",
|
|
{
|
|
_song_item_label_get,
|
|
NULL,
|
|
_song_item_state_get,
|
|
_song_item_del
|
|
}
|
|
};
|
|
static const Page_Class song_cls = {
|
|
"song",
|
|
"_enjoy_page_songs",
|
|
"page/songs-album",
|
|
_song_album_init,
|
|
_page_songs_after_populate,
|
|
_song_item_selected,
|
|
&song_item_cls,
|
|
PAGE_SONGS_POPULATE_ITERATION_COUNT,
|
|
(void *(*)(const void*))db_song_copy,
|
|
offsetof(Song, title),
|
|
0
|
|
};
|
|
|
|
it = db_album_songs_get(db, album->id);
|
|
|
|
if ((!album->artist) && (!album->flags.fetched_artist))
|
|
db_album_artist_fetch(db, album);
|
|
|
|
return _page_add(parent, album, it, "Album Songs", &song_cls);
|
|
}
|
|
|
|
Song *
|
|
page_songs_selected_get(const Evas_Object *obj)
|
|
{
|
|
PAGE_SONGS_GET_OR_RETURN(page, obj, NULL);
|
|
return page->selected ? elm_genlist_item_data_get(page->selected) : NULL;
|
|
}
|
|
|
|
Eina_Bool
|
|
page_songs_song_updated(Evas_Object *obj)
|
|
{
|
|
PAGE_SONGS_GET_OR_RETURN(page, obj, EINA_FALSE);
|
|
if (page->selected)
|
|
{
|
|
elm_genlist_item_update(page->selected);
|
|
return EINA_TRUE;
|
|
}
|
|
return EINA_FALSE;
|
|
}
|
|
|
|
Eina_Bool
|
|
page_songs_next_exists(const Evas_Object *obj)
|
|
{
|
|
PAGE_SONGS_GET_OR_RETURN(page, obj, EINA_FALSE);
|
|
Elm_Genlist_Item *it = page->selected;
|
|
if (!it) return EINA_FALSE;
|
|
it = elm_genlist_item_next_get(it);
|
|
return !!it;
|
|
}
|
|
|
|
Song *
|
|
page_songs_next_go(Evas_Object *obj)
|
|
{
|
|
PAGE_SONGS_GET_OR_RETURN(page, obj, NULL);
|
|
Elm_Genlist_Item *it = page->selected;
|
|
Song *song;
|
|
if (!it) return NULL;
|
|
it = elm_genlist_item_next_get(it);
|
|
if (!it) return NULL;
|
|
song = elm_genlist_item_data_get(it);
|
|
page->selected = it;
|
|
elm_genlist_item_selected_set(it, EINA_TRUE);
|
|
elm_genlist_item_bring_in(it);
|
|
mpris_signal_player_track_change(song);
|
|
return song;
|
|
}
|
|
|
|
int32_t
|
|
page_songs_selected_n_get(Evas_Object *obj)
|
|
{
|
|
PAGE_SONGS_GET_OR_RETURN(page, obj, 0);
|
|
Elm_Genlist_Item *it;
|
|
int n;
|
|
for (n = 0, it = page->first;
|
|
it && it != page->selected;
|
|
n++, it = elm_genlist_item_next_get(it));
|
|
return (it) ? n : 0;
|
|
}
|
|
|
|
Song *
|
|
page_songs_nth_get(Evas_Object *obj, int32_t n)
|
|
{
|
|
PAGE_SONGS_GET_OR_RETURN(page, obj, NULL);
|
|
Elm_Genlist_Item *it = page->first;
|
|
while (it && n--) it = elm_genlist_item_next_get(it);
|
|
if (!it) return NULL;
|
|
return elm_genlist_item_data_get(it);
|
|
}
|
|
|
|
Song *
|
|
page_songs_random_go(Evas_Object *obj)
|
|
{
|
|
Song *song;
|
|
int count, song_num;
|
|
|
|
PAGE_SONGS_GET_OR_RETURN(page, obj, NULL);
|
|
Elm_Genlist_Item *it = page->first;
|
|
if (!it) return NULL;
|
|
song_num = (rand() % page->num_elements);
|
|
for (count = 0; (it) && (count < song_num); count++)
|
|
it = elm_genlist_item_next_get(it);
|
|
if (!it) return NULL;
|
|
song = elm_genlist_item_data_get(it);
|
|
page->selected = it;
|
|
elm_genlist_item_selected_set(it, EINA_TRUE);
|
|
elm_genlist_item_bring_in(it);
|
|
mpris_signal_player_track_change(song);
|
|
return song;
|
|
}
|
|
|
|
Eina_Bool
|
|
page_songs_prev_exists(const Evas_Object *obj)
|
|
{
|
|
PAGE_SONGS_GET_OR_RETURN(page, obj, EINA_FALSE);
|
|
Elm_Genlist_Item *it = page->selected;
|
|
if (!it) return EINA_FALSE;
|
|
it = elm_genlist_item_prev_get(it);
|
|
return !!it;
|
|
}
|
|
|
|
Song *
|
|
page_songs_prev_go(Evas_Object *obj)
|
|
{
|
|
PAGE_SONGS_GET_OR_RETURN(page, obj, NULL);
|
|
Elm_Genlist_Item *it = page->selected;
|
|
Song *song;
|
|
if (!it) return NULL;
|
|
it = elm_genlist_item_prev_get(it);
|
|
if (!it) return NULL;
|
|
song = elm_genlist_item_data_get(it);
|
|
page->selected = it;
|
|
elm_genlist_item_selected_set(it, EINA_TRUE);
|
|
elm_genlist_item_bring_in(it);
|
|
mpris_signal_player_track_change(song);
|
|
return song;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* FOLDERS
|
|
**********************************************************************/
|
|
|
|
static char *
|
|
_album_item_label_get(void *data, Evas_Object *list, const char *part)
|
|
{
|
|
Album *album = data;
|
|
if (strcmp(part, "ejy.text.artist") == 0)
|
|
{
|
|
if (!album->flags.fetched_artist)
|
|
{
|
|
DB *db = evas_object_data_get(list, "_enjoy_container");
|
|
db_album_artist_fetch(db, album);
|
|
}
|
|
if (album->artist) return strdup(album->artist);
|
|
else return NULL;
|
|
}
|
|
|
|
return strdup(album->name);
|
|
}
|
|
|
|
static Evas_Object *
|
|
_album_item_icon_get(void *data, Evas_Object *list, const char *part __UNUSED__)
|
|
{
|
|
Page *page = evas_object_data_get(list, "_enjoy_page");
|
|
Album *album = data;
|
|
return cover_album_fetch(list, page->container, album, page->cls->icon_size);
|
|
}
|
|
|
|
static void
|
|
_album_item_del(void *data, Evas_Object *list __UNUSED__)
|
|
{
|
|
db_album_free(data);
|
|
}
|
|
|
|
static void
|
|
_album_item_selected(void *data, Evas_Object *list __UNUSED__, void *event_info)
|
|
{
|
|
Page *page = data;
|
|
Album *album = elm_genlist_item_data_get(event_info);
|
|
EINA_SAFETY_ON_NULL_RETURN(album);
|
|
Evas_Object *next = _page_album_songs_add(page->layout, album);
|
|
if (next)
|
|
evas_object_smart_callback_call(page->layout, "folder-songs", next);
|
|
elm_genlist_item_selected_set(event_info, EINA_FALSE);
|
|
page->selected = NULL;
|
|
}
|
|
|
|
static Evas_Object *
|
|
_page_albums_artist_add(Evas_Object *parent, NameID *nameid, Eina_Iterator *it, const char *title)
|
|
{
|
|
static const Elm_Genlist_Item_Class album_item_cls = {
|
|
"album-artist",
|
|
{
|
|
_album_item_label_get,
|
|
_album_item_icon_get,
|
|
NULL,
|
|
_album_item_del
|
|
}
|
|
};
|
|
static Page_Class album_cls = {
|
|
"album",
|
|
"_enjoy_page_albums",
|
|
"page/albums",
|
|
NULL,
|
|
NULL,
|
|
_album_item_selected,
|
|
&album_item_cls,
|
|
PAGE_FOLDERS_POPULATE_ITERATION_COUNT,
|
|
(void *(*)(const void*))db_album_copy,
|
|
offsetof(Album, name),
|
|
0
|
|
};
|
|
|
|
if (!album_cls.icon_size)
|
|
{
|
|
Evas_Object *ed = edje_object_add(evas_object_evas_get(parent));
|
|
if (edje_object_file_set
|
|
(ed, PACKAGE_DATA_DIR"/default.edj",
|
|
"elm/genlist/item_compress/album-artist/default"))
|
|
{
|
|
const char *s = edje_object_data_get(ed, "icon_size");
|
|
if (s) album_cls.icon_size = atoi(s);
|
|
}
|
|
evas_object_del(ed);
|
|
if (!album_cls.icon_size)
|
|
{
|
|
ERR("Could not get icon_size! assume 32");
|
|
album_cls.icon_size = 32;
|
|
}
|
|
}
|
|
|
|
return _page_add(parent, nameid, it, title, &album_cls);
|
|
}
|
|
|
|
static Evas_Object *
|
|
_page_albums_add(Evas_Object *parent, NameID *nameid, Eina_Iterator *it, const char *title)
|
|
{
|
|
static const Elm_Genlist_Item_Class album_item_cls = {
|
|
"album",
|
|
{
|
|
_album_item_label_get,
|
|
_album_item_icon_get,
|
|
NULL,
|
|
_album_item_del
|
|
}
|
|
};
|
|
static Page_Class album_cls = {
|
|
"album",
|
|
"_enjoy_page_albums",
|
|
"page/albums",
|
|
NULL,
|
|
NULL,
|
|
_album_item_selected,
|
|
&album_item_cls,
|
|
PAGE_FOLDERS_POPULATE_ITERATION_COUNT,
|
|
(void *(*)(const void*))db_album_copy,
|
|
offsetof(Album, name),
|
|
0
|
|
};
|
|
|
|
if (!album_cls.icon_size)
|
|
{
|
|
Evas_Object *ed = edje_object_add(evas_object_evas_get(parent));
|
|
if (edje_object_file_set
|
|
(ed, PACKAGE_DATA_DIR"/default.edj",
|
|
"elm/genlist/item_compress/album/default"))
|
|
{
|
|
const char *s = edje_object_data_get(ed, "icon_size");
|
|
if (s) album_cls.icon_size = atoi(s);
|
|
}
|
|
evas_object_del(ed);
|
|
if (!album_cls.icon_size)
|
|
{
|
|
ERR("Could not get icon_size! assume 32");
|
|
album_cls.icon_size = 32;
|
|
}
|
|
}
|
|
|
|
return _page_add(parent, nameid, it, title, &album_cls);
|
|
}
|
|
|
|
static char *
|
|
_item_all_songs_label_get(void *data __UNUSED__, Evas_Object *list __UNUSED__, const char *part __UNUSED__)
|
|
{
|
|
if (strcmp(part, "ejy.text.artist") == 0)
|
|
return NULL;
|
|
return strdup("All Songs");
|
|
}
|
|
|
|
static Evas_Object *
|
|
_item_all_songs_icon_get(void *data __UNUSED__, Evas_Object *list __UNUSED__, const char *part __UNUSED__)
|
|
{
|
|
Page *page = evas_object_data_get(list, "_enjoy_page");
|
|
return cover_allsongs_fetch(list, page->cls->icon_size);
|
|
}
|
|
|
|
|
|
static const Elm_Genlist_Item_Class _item_all_songs_cls = {
|
|
"all-song",
|
|
{
|
|
_item_all_songs_label_get,
|
|
_item_all_songs_icon_get,
|
|
NULL,
|
|
NULL
|
|
}
|
|
};
|
|
|
|
static char *
|
|
_nameid_item_label_get(void *data, Evas_Object *list __UNUSED__, const char *part __UNUSED__)
|
|
{
|
|
NameID *nameid = data;
|
|
return strdup(nameid->name);
|
|
}
|
|
|
|
static void
|
|
_nameid_item_del(void *data, Evas_Object *list __UNUSED__)
|
|
{
|
|
db_nameid_free(data);
|
|
}
|
|
|
|
static void
|
|
_artist_item_all_songs_selected(void *data, Evas_Object *list __UNUSED__, void *event_info)
|
|
{
|
|
Page *page = data;
|
|
NameID *nameid = page->item.data;
|
|
DB *db = _page_db_get(page->layout);
|
|
Eina_Iterator *it = db_artist_songs_get(db, nameid->id);
|
|
char buf[128];
|
|
snprintf(buf, sizeof(buf), "Songs by %s", nameid->name);
|
|
Evas_Object *next = _page_songs_add(page->layout, nameid, it, buf);
|
|
if (next)
|
|
evas_object_smart_callback_call(page->layout, "folder-songs", next);
|
|
elm_genlist_item_selected_set(event_info, EINA_FALSE);
|
|
page->selected = NULL;
|
|
}
|
|
|
|
static void
|
|
_artist_item_selected(void *data, Evas_Object *list __UNUSED__, void *event_info)
|
|
{
|
|
Page *page = data;
|
|
NameID *nameid = elm_genlist_item_data_get(event_info);
|
|
EINA_SAFETY_ON_NULL_RETURN(nameid);
|
|
DB *db = _page_db_get(page->layout);
|
|
Eina_Iterator *it = db_artist_albums_get(db, nameid->id);
|
|
char buf[128];
|
|
snprintf(buf, sizeof(buf), "Albums by %s", nameid->name);
|
|
Evas_Object *next = _page_albums_add(page->layout, nameid, it, buf);
|
|
if (next)
|
|
{
|
|
Page *next_page = evas_object_data_get(next, "_enjoy_page");
|
|
next_page->item.data = db_nameid_copy(nameid);
|
|
next_page->item.free = (void (*)(void *))db_nameid_free;
|
|
elm_genlist_item_append
|
|
(next_page->list, &_item_all_songs_cls, NULL, NULL,
|
|
ELM_GENLIST_ITEM_NONE, _artist_item_all_songs_selected, next_page);
|
|
|
|
evas_object_smart_callback_call(page->layout, "folder", next);
|
|
}
|
|
elm_genlist_item_selected_set(event_info, EINA_FALSE);
|
|
page->selected = NULL;
|
|
}
|
|
|
|
static Evas_Object *
|
|
_page_artists_add(Evas_Object *parent, NameID *nameid, Eina_Iterator *it, const char *title)
|
|
{
|
|
static const Elm_Genlist_Item_Class nameid_item_cls = {
|
|
"nameid",
|
|
{
|
|
_nameid_item_label_get,
|
|
NULL,
|
|
NULL,
|
|
_nameid_item_del
|
|
}
|
|
};
|
|
static const Page_Class nameid_cls = {
|
|
"artist",
|
|
"_enjoy_page_artists",
|
|
"page/nameids",
|
|
NULL,
|
|
NULL,
|
|
_artist_item_selected,
|
|
&nameid_item_cls,
|
|
PAGE_FOLDERS_POPULATE_ITERATION_COUNT,
|
|
(void *(*)(const void*))db_nameid_copy,
|
|
offsetof(NameID, name),
|
|
0
|
|
};
|
|
return _page_add(parent, nameid, it, title, &nameid_cls);
|
|
}
|
|
|
|
static void
|
|
_genre_item_all_songs_selected(void *data, Evas_Object *list __UNUSED__, void *event_info)
|
|
{
|
|
Page *page = data;
|
|
NameID *nameid = page->item.data;
|
|
DB *db = _page_db_get(page->layout);
|
|
Eina_Iterator *it = db_genre_songs_get(db, nameid->id);
|
|
char buf[128];
|
|
snprintf(buf, sizeof(buf), "Songs of %s", nameid->name);
|
|
Evas_Object *next = _page_songs_add(page->layout, nameid, it, buf);
|
|
if (next)
|
|
evas_object_smart_callback_call(page->layout, "folder-songs", next);
|
|
elm_genlist_item_selected_set(event_info, EINA_FALSE);
|
|
page->selected = NULL;
|
|
}
|
|
|
|
static void
|
|
_genre_item_selected(void *data, Evas_Object *list __UNUSED__, void *event_info)
|
|
{
|
|
Page *page = data;
|
|
NameID *nameid = elm_genlist_item_data_get(event_info);
|
|
EINA_SAFETY_ON_NULL_RETURN(nameid);
|
|
DB *db = _page_db_get(page->layout);
|
|
Eina_Iterator *it = db_genre_albums_get(db, nameid->id);
|
|
char buf[128];
|
|
snprintf(buf, sizeof(buf), "Albums of %s", nameid->name);
|
|
Evas_Object *next = _page_albums_artist_add(page->layout, nameid, it, buf);
|
|
if (next)
|
|
{
|
|
Page *next_page = evas_object_data_get(next, "_enjoy_page");
|
|
next_page->item.data = db_nameid_copy(nameid);
|
|
next_page->item.free = (void (*)(void *))db_nameid_free;
|
|
elm_genlist_item_append
|
|
(next_page->list, &_item_all_songs_cls, NULL, NULL,
|
|
ELM_GENLIST_ITEM_NONE, _genre_item_all_songs_selected, next_page);
|
|
|
|
evas_object_smart_callback_call(page->layout, "folder", next);
|
|
}
|
|
elm_genlist_item_selected_set(event_info, EINA_FALSE);
|
|
page->selected = NULL;
|
|
}
|
|
|
|
static Evas_Object *
|
|
_page_genres_add(Evas_Object *parent, Eina_Iterator *it, const char *title)
|
|
{
|
|
static const Elm_Genlist_Item_Class nameid_item_cls = {
|
|
"nameid",
|
|
{
|
|
_nameid_item_label_get,
|
|
NULL,
|
|
NULL,
|
|
_nameid_item_del
|
|
}
|
|
};
|
|
static const Page_Class nameid_cls = {
|
|
"genre",
|
|
"_enjoy_page_genres",
|
|
"page/nameids",
|
|
NULL,
|
|
NULL,
|
|
_genre_item_selected,
|
|
&nameid_item_cls,
|
|
PAGE_FOLDERS_POPULATE_ITERATION_COUNT,
|
|
(void *(*)(const void*))db_nameid_copy,
|
|
offsetof(NameID, name),
|
|
0
|
|
};
|
|
return _page_add(parent, NULL, it, title, &nameid_cls);
|
|
}
|
|
|
|
typedef struct _Static_Item
|
|
{
|
|
const char *label;
|
|
Evas_Object *(*action)(Evas_Object *parent, void *data);
|
|
const void *data;
|
|
const char *signal;
|
|
} Static_Item;
|
|
|
|
static char *
|
|
_static_item_label_get(void *data, Evas_Object *list __UNUSED__, const char *part __UNUSED__)
|
|
{
|
|
Static_Item *root = data;
|
|
return strdup(root->label);
|
|
}
|
|
|
|
static void
|
|
_static_item_selected(void *data, Evas_Object *list __UNUSED__, void *event_info)
|
|
{
|
|
Page *page = data;
|
|
const Static_Item *si = elm_genlist_item_data_get(event_info);
|
|
Evas_Object *next;
|
|
EINA_SAFETY_ON_NULL_RETURN(si);
|
|
EINA_SAFETY_ON_NULL_RETURN(si->action);
|
|
next = si->action(page->layout, (void *)si->data);
|
|
if (next)
|
|
evas_object_smart_callback_call(page->layout, si->signal, next);
|
|
elm_genlist_item_selected_set(event_info, EINA_FALSE);
|
|
page->selected = NULL;
|
|
}
|
|
|
|
static Evas_Object *
|
|
_page_all_songs_add(Evas_Object *parent, void *data)
|
|
{
|
|
PAGE_GET_OR_RETURN(page, parent, NULL);
|
|
DB *db = _page_db_get(parent);
|
|
Eina_Iterator *it = db_songs_get(db);
|
|
return _page_songs_add(parent, NULL, it, data);
|
|
}
|
|
|
|
static Evas_Object *
|
|
_page_all_albums_add(Evas_Object *parent, void *data)
|
|
{
|
|
PAGE_GET_OR_RETURN(page, parent, NULL);
|
|
DB *db = _page_db_get(parent);
|
|
Eina_Iterator *it = db_albums_get(db);
|
|
return _page_albums_artist_add(parent, NULL, it, data);
|
|
}
|
|
|
|
static Evas_Object *
|
|
_page_all_artists_add(Evas_Object *parent, void *data)
|
|
{
|
|
PAGE_GET_OR_RETURN(page, parent, NULL);
|
|
DB *db = _page_db_get(parent);
|
|
Eina_Iterator *it = db_artists_get(db);
|
|
return _page_artists_add(parent, NULL, it, data);
|
|
}
|
|
|
|
static Evas_Object *
|
|
_page_all_genres_add(Evas_Object *parent, void *data)
|
|
{
|
|
PAGE_GET_OR_RETURN(page, parent, NULL);
|
|
DB *db = _page_db_get(parent);
|
|
Eina_Iterator *it = db_genres_get(db);
|
|
return _page_genres_add(parent, it, data);
|
|
}
|
|
|
|
Evas_Object *
|
|
page_root_add(Evas_Object *parent)
|
|
{
|
|
static const Elm_Genlist_Item_Class root_item_cls = {
|
|
"root", { _static_item_label_get, NULL, NULL, NULL }
|
|
};
|
|
static const Page_Class root_cls = {
|
|
"root",
|
|
"_enjoy_page_roots",
|
|
"page/roots",
|
|
NULL,
|
|
NULL,
|
|
_static_item_selected,
|
|
&root_item_cls,
|
|
PAGE_FOLDERS_POPULATE_ITERATION_COUNT,
|
|
(void *(*)(const void*))_data_from_itr_passthrough,
|
|
offsetof(Static_Item, label),
|
|
0
|
|
};
|
|
static const Static_Item root_items[] = {
|
|
{"All Songs", _page_all_songs_add, "All Songs", "folder-songs"},
|
|
{"Albums", _page_all_albums_add, "All Albums", "folder"},
|
|
{"Artists", _page_all_artists_add, "All Artists", "folder"},
|
|
{"Genres", _page_all_genres_add, "All Genres", "folder"},
|
|
};
|
|
Eina_Iterator *it = _array_iterator_new
|
|
(root_items, sizeof(Static_Item), ARRAY_SIZE(root_items));
|
|
return _page_add(parent, NULL, it, "Enjoy your music!", &root_cls);
|
|
}
|