From cf81682f8cad8579fa9063107e1fea6274705a1b Mon Sep 17 00:00:00 2001 From: Joshua Strobl Date: Wed, 10 Mar 2021 13:44:08 +0200 Subject: [PATCH] Start implementing Playlist functionality via KotoCurrentPlaylist and KotoPlaylist. Implement functionality for gracefully showing the play button on top of KotoAlbumArt images. Implemented a new koto_indexed_album_set_as_current_playlist function for setting an album as the current playlist, useful when clicking to play an album via its artwork. --- src/indexer/album.c | 20 +++ src/indexer/structs.h | 1 + src/koto-playerbar.c | 2 +- src/koto-utils.c | 2 +- src/koto-window.c | 6 + src/meson.build | 2 + src/pages/music/album-view.c | 63 +++++++- src/pages/music/album-view.h | 3 + src/playlist/current.c | 60 +++++++ src/playlist/current.h | 40 +++++ src/playlist/playlist.c | 294 ++++++++++++++++++++++++++++++++++ src/playlist/playlist.h | 59 +++++++ theme/pages/_music-local.scss | 6 +- 13 files changed, 551 insertions(+), 7 deletions(-) create mode 100644 src/playlist/current.c create mode 100644 src/playlist/current.h create mode 100644 src/playlist/playlist.c create mode 100644 src/playlist/playlist.h diff --git a/src/indexer/album.c b/src/indexer/album.c index 02a72c9..5ced014 100644 --- a/src/indexer/album.c +++ b/src/indexer/album.c @@ -19,9 +19,12 @@ #include #include #include +#include "../playlist/current.h" +#include "../playlist/playlist.h" #include "structs.h" #include "koto-utils.h" +extern KotoCurrentPlaylist *current_playlist; extern sqlite3 *koto_db; struct _KotoIndexedAlbum { @@ -408,6 +411,23 @@ void koto_indexed_album_set_album_name(KotoIndexedAlbum *self, const gchar *albu self->name = g_strdup(album_name); } +void koto_indexed_album_set_as_current_playlist(KotoIndexedAlbum *self) { + if (self->files == NULL) { // No files to add to the playlist + return; + } + + KotoPlaylist *new_album_playlist = koto_playlist_new(); // Create a new playlist + + GList *tracks_list_uuids = g_hash_table_get_keys(self->files); // Get the UUIDs + for (guint i = 0; i < g_list_length(tracks_list_uuids); i++) { // For each of the tracks + koto_playlist_add_track_by_uuid(new_album_playlist, (gchar*) g_list_nth_data(tracks_list_uuids, i)); // Add the UUID + } + + g_list_free(tracks_list_uuids); // Free the uuids + + koto_current_playlist_set_playlist(current_playlist, new_album_playlist); // Set our new current playlist +} + static void koto_indexed_album_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec){ KotoIndexedAlbum *self = KOTO_INDEXED_ALBUM(obj); diff --git a/src/indexer/structs.h b/src/indexer/structs.h index 321d896..a5ca9e2 100644 --- a/src/indexer/structs.h +++ b/src/indexer/structs.h @@ -90,6 +90,7 @@ void koto_indexed_album_remove_file(KotoIndexedAlbum *self, KotoIndexedFile *fil void koto_indexed_album_remove_file_by_name(KotoIndexedAlbum *self, const gchar *file_name); void koto_indexed_album_set_album_art(KotoIndexedAlbum *self, const gchar *album_art); void koto_indexed_album_set_album_name(KotoIndexedAlbum *self, const gchar *album_name); +void koto_indexed_album_set_as_current_playlist(KotoIndexedAlbum *self); void koto_indexed_album_update_path(KotoIndexedAlbum *self, const gchar *path); /** diff --git a/src/koto-playerbar.c b/src/koto-playerbar.c index 12523b9..d2524dd 100644 --- a/src/koto-playerbar.c +++ b/src/koto-playerbar.c @@ -125,7 +125,7 @@ void koto_playerbar_create_playback_details(KotoPlayerBar* bar) { void koto_playerbar_create_primary_controls(KotoPlayerBar* bar) { bar->back_button = koto_button_new_with_icon("", "media-skip-backward-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL); - bar->play_pause_button = koto_button_new_with_icon("", "media-playback-start-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_LARGE); // TODO: Have this take in a state and switch to a different icon if necessary + bar->play_pause_button = koto_button_new_with_icon("", "media-playback-start-symbolic", "media-playback-pause-symbolic", KOTO_BUTTON_PIXBUF_SIZE_LARGE); // TODO: Have this take in a state and switch to a different icon if necessary bar->forward_button = koto_button_new_with_icon("", "media-skip-forward-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL); if (bar->back_button != NULL) { diff --git a/src/koto-utils.c b/src/koto-utils.c index 3999b79..0e325cb 100644 --- a/src/koto-utils.c +++ b/src/koto-utils.c @@ -21,7 +21,7 @@ GtkWidget* koto_utils_create_image_from_filepath(gchar *filepath, gchar *fallback_icon, guint width, guint height) { GtkWidget* image = NULL; - if (strcmp(filepath, "") != 0) { // If we have a filepath + if ((filepath != NULL) && (strcmp(filepath, "") != 0)) { // If we have a filepath if (g_file_test(filepath, G_FILE_TEST_EXISTS)) { // File exists image = gtk_image_new_from_file(filepath); // Load from the filepath } diff --git a/src/koto-window.c b/src/koto-window.c index 36b4659..38914ad 100644 --- a/src/koto-window.c +++ b/src/koto-window.c @@ -18,14 +18,18 @@ #include #include "indexer/structs.h" #include "pages/music/music-local.h" +#include "playlist/current.h" #include "koto-config.h" #include "koto-nav.h" #include "koto-playerbar.h" #include "koto-window.h" +extern KotoCurrentPlaylist *current_playlist; + struct _KotoWindow { GtkApplicationWindow parent_instance; KotoIndexedLibrary *library; + KotoCurrentPlaylist *current_playlist; GtkWidget *header_bar; GtkWidget *menu_button; @@ -46,6 +50,8 @@ static void koto_window_class_init (KotoWindowClass *klass) { } static void koto_window_init (KotoWindow *self) { + current_playlist = koto_current_playlist_new(); + GtkCssProvider* provider = gtk_css_provider_new(); gtk_css_provider_load_from_resource(provider, "/com/github/joshstrobl/koto/style.css"); gtk_style_context_add_provider_for_display(gdk_display_get_default(), GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); diff --git a/src/meson.build b/src/meson.build index aa802e8..417e11f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -10,6 +10,8 @@ koto_sources = [ 'pages/music/artist-view.c', 'pages/music/disc-view.c', 'pages/music/music-local.c', + 'playlist/current.c', + 'playlist/playlist.c', 'main.c', 'koto-button.c', 'koto-expander.c', diff --git a/src/pages/music/album-view.c b/src/pages/music/album-view.c index 449e962..b7e4e8e 100644 --- a/src/pages/music/album-view.c +++ b/src/pages/music/album-view.c @@ -17,6 +17,7 @@ #include #include +#include "../../koto-button.h" #include "../../indexer/structs.h" #include "album-view.h" #include "disc-view.h" @@ -30,6 +31,12 @@ struct _KotoAlbumView { GtkWidget *album_tracks_box; GtkWidget *discs; + GtkWidget *album_overlay_art; + GtkWidget *album_overlay_container; + GtkWidget *album_overlay_controls; + GtkWidget *album_overlay_revealer; + KotoButton *play_pause_button; + GtkWidget *album_label; GHashTable *cd_to_track_listbox; }; @@ -79,6 +86,36 @@ static void koto_album_view_init(KotoAlbumView *self) { gtk_box_append(GTK_BOX(self->main), self->album_tracks_box); // Add the tracks box to the art info combo box gtk_box_append(GTK_BOX(self->album_tracks_box), self->discs); // Add the discs list box to the albums tracks box + + self->album_overlay_container = gtk_overlay_new(); // Create our overlay container + gtk_widget_set_valign(self->album_overlay_container, GTK_ALIGN_START); // Align to top of list for album + + self->album_overlay_art = koto_utils_create_image_from_filepath(NULL, "audio-x-generic-symbolic", 220, 220); + gtk_overlay_set_child(GTK_OVERLAY(self->album_overlay_container), self->album_overlay_art); // Add our art as the "child" for the overlay + + self->album_overlay_revealer = gtk_revealer_new(); // Create a new revealer + gtk_revealer_set_transition_type(GTK_REVEALER(self->album_overlay_revealer), GTK_REVEALER_TRANSITION_TYPE_CROSSFADE); + gtk_revealer_set_transition_duration(GTK_REVEALER(self->album_overlay_revealer), 400); + + self->album_overlay_controls = gtk_center_box_new(); // Create a center box for the controls + + self->play_pause_button = koto_button_new_with_icon("", "media-playback-start-symbolic", "media-playback-pause-symbolic", KOTO_BUTTON_PIXBUF_SIZE_NORMAL); + gtk_center_box_set_center_widget(GTK_CENTER_BOX(self->album_overlay_controls), GTK_WIDGET(self->play_pause_button)); + + gtk_revealer_set_child(GTK_REVEALER(self->album_overlay_revealer), self->album_overlay_controls); + koto_album_view_hide_overlay_controls(NULL, self); // Hide by default + + gtk_overlay_add_overlay(GTK_OVERLAY(self->album_overlay_container), self->album_overlay_revealer); // Add our revealer as the overlay + gtk_box_prepend(GTK_BOX(self->main), self->album_overlay_container); // Add our album overlay container + + GtkEventController *motion_controller = gtk_event_controller_motion_new(); // Create our new motion event controller to track mouse leave and enter + g_signal_connect(motion_controller, "enter", G_CALLBACK(koto_album_view_show_overlay_controls), self); + g_signal_connect(motion_controller, "leave", G_CALLBACK(koto_album_view_hide_overlay_controls), self); + gtk_widget_add_controller(self->album_overlay_container, motion_controller); + + GtkGesture *controller = gtk_gesture_click_new(); // Create a new GtkGestureClick + g_signal_connect(controller, "pressed", G_CALLBACK(koto_album_view_toggle_album_playback), self); + gtk_widget_add_controller(GTK_WIDGET(self->play_pause_button), GTK_EVENT_CONTROLLER(controller)); } GtkWidget* koto_album_view_get_main(KotoAlbumView *self) { @@ -115,6 +152,12 @@ void koto_album_view_add_track_to_listbox(KotoIndexedAlbum *self, KotoIndexedFil (void) self; (void) file; } +void koto_album_view_hide_overlay_controls(GtkEventControllerFocus *controller, gpointer data) { + (void) controller; + KotoAlbumView* self = data; + gtk_revealer_set_reveal_child(GTK_REVEALER(self->album_overlay_revealer), FALSE); +} + void koto_album_view_set_album(KotoAlbumView *self, KotoIndexedAlbum *album) { if (album == NULL) { return; @@ -123,10 +166,7 @@ void koto_album_view_set_album(KotoAlbumView *self, KotoIndexedAlbum *album) { self->album = album; gchar *album_art = koto_indexed_album_get_album_art(self->album); // Get the art for the album - GtkWidget *art_image = koto_utils_create_image_from_filepath(album_art, "audio-x-generic-symbolic", 220, 220); - gtk_widget_set_valign(art_image, GTK_ALIGN_START); // Align to top of list for album - - gtk_box_prepend(GTK_BOX(self->main), art_image); // Prepend the image to the art info box + gtk_image_set_from_file(GTK_IMAGE(self->album_overlay_art), album_art); gchar *album_name; g_object_get(album, "name", &album_name, NULL); // Get the album name @@ -164,6 +204,13 @@ void koto_album_view_set_album(KotoAlbumView *self, KotoIndexedAlbum *album) { g_hash_table_destroy(discs); } +void koto_album_view_show_overlay_controls(GtkEventControllerFocus *controller, gpointer data) { + (void) controller; + KotoAlbumView* self = data; + + gtk_revealer_set_reveal_child(GTK_REVEALER(self->album_overlay_revealer), TRUE); +} + int koto_album_view_sort_discs(GtkListBoxRow *disc1, GtkListBoxRow *disc2, gpointer user_data) { (void) user_data; KotoDiscView *disc1_item = KOTO_DISC_VIEW(gtk_list_box_row_get_child(disc1)); @@ -184,6 +231,14 @@ int koto_album_view_sort_discs(GtkListBoxRow *disc1, GtkListBoxRow *disc2, gpoin } } +void koto_album_view_toggle_album_playback(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data) { + (void) gesture; (void) n_press; (void) x; (void) y; + KotoAlbumView* self = data; + + koto_button_show_image(KOTO_BUTTON(self->play_pause_button), TRUE); + koto_indexed_album_set_as_current_playlist(self->album); // Set as the current playlist +} + KotoAlbumView* koto_album_view_new(KotoIndexedAlbum *album) { return g_object_new(KOTO_TYPE_ALBUM_VIEW, "album", album, NULL); } diff --git a/src/pages/music/album-view.h b/src/pages/music/album-view.h index c43b617..f3679d0 100644 --- a/src/pages/music/album-view.h +++ b/src/pages/music/album-view.h @@ -30,7 +30,10 @@ G_DECLARE_FINAL_TYPE(KotoAlbumView, koto_album_view, KOTO, ALBUM_VIEW, GObject) KotoAlbumView* koto_album_view_new(KotoIndexedAlbum *album); GtkWidget* koto_album_view_get_main(KotoAlbumView *self); void koto_album_view_add_track_to_listbox(KotoIndexedAlbum *self, KotoIndexedFile *file); +void koto_album_view_hide_overlay_controls(GtkEventControllerFocus *controller, gpointer data); void koto_album_view_set_album(KotoAlbumView *self, KotoIndexedAlbum *album); +void koto_album_view_show_overlay_controls(GtkEventControllerFocus *controller, gpointer data); +void koto_album_view_toggle_album_playback(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data); int koto_album_view_sort_discs(GtkListBoxRow *track1, GtkListBoxRow *track2, gpointer user_data); G_END_DECLS diff --git a/src/playlist/current.c b/src/playlist/current.c new file mode 100644 index 0000000..bbf640b --- /dev/null +++ b/src/playlist/current.c @@ -0,0 +1,60 @@ +/* current.c + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "current.h" + +KotoCurrentPlaylist *current_playlist = NULL; + +struct _KotoCurrentPlaylist { + GObject parent_class; + KotoPlaylist *current_playlist; +}; + +G_DEFINE_TYPE(KotoCurrentPlaylist, koto_current_playlist, G_TYPE_OBJECT); + +static void koto_current_playlist_class_init(KotoCurrentPlaylistClass *c) { + (void) c; +} + +static void koto_current_playlist_init(KotoCurrentPlaylist *self) { + self->current_playlist = NULL; +} + +KotoPlaylist* koto_current_playlist_get_playlist(KotoCurrentPlaylist *self) { + return self->current_playlist; +} + +void koto_current_playlist_set_playlist(KotoCurrentPlaylist *self, KotoPlaylist *playlist) { + if (!KOTO_IS_CURRENT_PLAYLIST(self)) { + return; + } + + if (KOTO_IS_PLAYLIST(self->current_playlist)) { + // TODO: Save current playlist state if needed + g_object_unref(self->current_playlist); // Unreference + } + + self->current_playlist = playlist; + g_object_ref(playlist); // Increment the reference + g_object_notify(G_OBJECT(self), "current-playlist-changed"); + koto_playlist_debug(self->current_playlist); +} + +KotoCurrentPlaylist* koto_current_playlist_new() { + return g_object_new(KOTO_TYPE_CURRENT_PLAYLIST, NULL); +} diff --git a/src/playlist/current.h b/src/playlist/current.h new file mode 100644 index 0000000..96be2f5 --- /dev/null +++ b/src/playlist/current.h @@ -0,0 +1,40 @@ +/* current.h + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include "playlist.h" + +G_BEGIN_DECLS + +/** + * Type Definition +**/ + +#define KOTO_TYPE_CURRENT_PLAYLIST koto_current_playlist_get_type() +G_DECLARE_FINAL_TYPE(KotoCurrentPlaylist, koto_current_playlist, KOTO, CURRENT_PLAYLIST, GObject); +#define KOTO_IS_CURRENT_PLAYLIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_CURRENT_PLAYLIST)) + +/** + * Current Playlist Functions +**/ + +KotoCurrentPlaylist* koto_current_playlist_new(); +KotoPlaylist* koto_current_playlist_get_playlist(KotoCurrentPlaylist *self); +void koto_current_playlist_set_playlist(KotoCurrentPlaylist *self, KotoPlaylist *playlist); + +G_END_DECLS diff --git a/src/playlist/playlist.c b/src/playlist/playlist.c new file mode 100644 index 0000000..3609c89 --- /dev/null +++ b/src/playlist/playlist.c @@ -0,0 +1,294 @@ +/* playlist.c + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include "playlist.h" + +extern sqlite3 *koto_db; + +struct _KotoPlaylist { + GObject parent_instance; + gchar *uuid; + gchar *name; + gchar *art_path; + guint current_position; + + GQueue *tracks; +}; + +G_DEFINE_TYPE(KotoPlaylist, koto_playlist, G_TYPE_OBJECT); + +enum { + PROP_0, + PROP_UUID, + PROP_NAME, + PROP_ART_PATH, + N_PROPERTIES, +}; + +static GParamSpec *props[N_PROPERTIES] = { NULL }; +static void koto_playlist_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec);; +static void koto_playlist_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec); + +static void koto_playlist_class_init(KotoPlaylistClass *c) { + GObjectClass *gobject_class; + gobject_class = G_OBJECT_CLASS(c); + gobject_class->set_property = koto_playlist_set_property; + gobject_class->get_property = koto_playlist_get_property; + + props[PROP_UUID] = g_param_spec_string( + "uuid", + "UUID of the Playlist", + "UUID of the Playlist", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + props[PROP_NAME] = g_param_spec_string( + "name", + "Name of the Playlist", + "Name of the Playlist", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + props[PROP_ART_PATH] = g_param_spec_string( + "art-path", + "Path to any associated artwork of the Playlist", + "Path to any associated artwork of the Playlist", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + g_object_class_install_properties(gobject_class, N_PROPERTIES, props); +} + +static void koto_playlist_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) { + KotoPlaylist *self = KOTO_PLAYLIST(obj); + + switch (prop_id) { + case PROP_UUID: + g_value_set_string(val, self->uuid); + break; + case PROP_NAME: + g_value_set_string(val, self->name); + break; + case PROP_ART_PATH: + g_value_set_string(val, self->art_path); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); + break; + } +} + +static void koto_playlist_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec) { + KotoPlaylist *self = KOTO_PLAYLIST(obj); + + switch (prop_id) { + case PROP_UUID: + koto_playlist_set_uuid(self, g_value_get_string(val)); + break; + case PROP_NAME: + koto_playlist_set_name(self, g_value_get_string(val)); + break; + case PROP_ART_PATH: + koto_playlist_set_artwork(self, g_value_get_string(val)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); + break; + } +} + +static void koto_playlist_init(KotoPlaylist *self) { + self->current_position = 0; // Default to 0 + self->tracks = g_queue_new(); // Set as an empty GQueue +} + +void koto_playlist_add_track(KotoPlaylist *self, KotoIndexedFile *file) { + gchar *uuid = NULL; + g_object_get(file, "uuid", &uuid, NULL); // Get the UUID + koto_playlist_add_track_by_uuid(self, uuid); // Add by the file's UUID +} + +void koto_playlist_add_track_by_uuid(KotoPlaylist *self, const gchar *uuid) { + gchar *dup_uuid = g_strdup(uuid); + + g_queue_push_tail(self->tracks, dup_uuid); // Append the UUID to the tracks + // TODO: Add to table +} + +void koto_playlist_debug(KotoPlaylist *self) { + g_queue_foreach(self->tracks, koto_playlist_debug_foreach, NULL); +} + +void koto_playlist_debug_foreach(gpointer data, gpointer user_data) { + (void) user_data; + gchar *uuid = data; + g_message("UUID in Playlist: %s", uuid); +} + +gchar* koto_playlist_get_artwork(KotoPlaylist *self) { + return g_strdup(self->art_path); // Return a duplicate of our art path +} + +guint koto_playlist_get_current_position(KotoPlaylist *self) { + return self->current_position; +} + +gchar* koto_playlist_get_current_uuid(KotoPlaylist *self) { + return g_queue_peek_nth(self->tracks, self->current_position); +} + +guint koto_playlist_get_length(KotoPlaylist *self) { + return g_queue_get_length(self->tracks); // Get the length of the tracks +} + +gchar* koto_playlist_get_name(KotoPlaylist *self) { + return (self->name == NULL) ? NULL : g_strdup(self->name); +} + +GQueue* koto_playlist_get_tracks(KotoPlaylist *self) { + return self->tracks; +} + +gchar* koto_playlist_get_uuid(KotoPlaylist *self) { + return g_strdup(self->uuid); +} + +gchar* koto_playlist_go_to_next(KotoPlaylist *self) { + gchar *current_uuid = koto_playlist_get_current_uuid(self); // Get the current UUID + + if (current_uuid == self->tracks->tail->data) { // Current UUID matches the last item in the playlist + return NULL; + } + + self->current_position++; // Increment our position + return koto_playlist_get_current_uuid(self); // Return the new UUID +} + +gchar* koto_playlist_go_to_previous(KotoPlaylist *self) { + gchar *current_uuid = koto_playlist_get_current_uuid(self); // Get the current UUID + + if (current_uuid == self->tracks->head->data) { // Current UUID matches the first item in the playlist + return NULL; + } + + self->current_position--; // Decrement our position + return koto_playlist_get_current_uuid(self); // Return the new UUID +} + +void koto_playlist_remove_track(KotoPlaylist *self, KotoIndexedFile *file) { + gchar *file_uuid = NULL; + g_object_get(file, "uuid", &file_uuid, NULL); + + if (file_uuid == NULL) { + return; + } + + koto_playlist_remove_track_by_uuid(self, file_uuid); +} + +void koto_playlist_remove_track_by_uuid(KotoPlaylist *self, gchar *uuid) { + if (uuid == NULL) { + return; + } + + gint file_index = g_queue_index(self->tracks, uuid); // Get the position of this uuid + + if (file_index == -1) { // Does not exist in our tracks list + return; + } + + g_queue_pop_nth(self->tracks, file_index); // Remove nth where it is the file index + return; +} + +void koto_playlist_set_artwork(KotoPlaylist *self, const gchar *path) { + if (path == NULL) { + return; + } + + magic_t cookie = magic_open(MAGIC_MIME); // Create our magic cookie so we can validate if what we are setting is an image + + if (cookie == NULL) { // Failed to allocate + return; + } + + if (magic_load(cookie, NULL) != 0) { // Failed to load our cookie + goto free_cookie; + } + + const gchar *mime_type = magic_file(cookie, path); // Get the mimetype for this file + + if ((mime_type == NULL) || !g_str_has_prefix(mime_type, "image/")) { // Failed to get our mimetype or not an image + goto free_cookie; + } + + if (self->art_path != NULL) { // art path is set + g_free(self->art_path); // Free the current path + } + + self->art_path = g_strdup(path); // Update our art path to a duplicate of provided path + +free_cookie: + magic_close(cookie); // Close and free the cookie to the cookie monster + return; +} + +void koto_playlist_set_name(KotoPlaylist *self, const gchar *name) { + if (name == NULL) { // No actual name + return; + } + + if (self->name != NULL) { // Have a name allocated already + g_free(self->name); // Free the name + } + + self->name = g_strdup(name); + return; +} + +void koto_playlist_set_uuid(KotoPlaylist *self, const gchar *uuid) { + if (uuid == NULL) { // No actual UUID + return; + } + + if (self->uuid != NULL) { // Have a UUID allocated already + return; // Do not allow changing the UUID + } + + self->uuid = g_strdup(uuid); // Set the new UUID + return; +} + +KotoPlaylist* koto_playlist_new() { + return g_object_new(KOTO_TYPE_PLAYLIST, + "uuid", g_uuid_string_random(), + NULL + ); +} + +KotoPlaylist* koto_playlist_new_with_uuid(const gchar *uuid) { + return g_object_new(KOTO_TYPE_PLAYLIST, + "uuid", uuid, + NULL + ); +} diff --git a/src/playlist/playlist.h b/src/playlist/playlist.h new file mode 100644 index 0000000..927e606 --- /dev/null +++ b/src/playlist/playlist.h @@ -0,0 +1,59 @@ +/* playlist.h + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include "../indexer/structs.h" + +G_BEGIN_DECLS + +/** + * Type Definition +**/ + +#define KOTO_TYPE_PLAYLIST koto_playlist_get_type() +G_DECLARE_FINAL_TYPE(KotoPlaylist, koto_playlist, KOTO, PLAYLIST, GObject); +#define KOTO_IS_PLAYLIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_PLAYLIST)) + +/** + * Playlist Functions +**/ + +KotoPlaylist* koto_playlist_new(); +KotoPlaylist* koto_playlist_new_with_uuid(const gchar *uuid); +void koto_playlist_add_track(KotoPlaylist *self, KotoIndexedFile *file); +void koto_playlist_add_track_by_uuid(KotoPlaylist *self, const gchar *uuid); +void koto_playlist_debug(KotoPlaylist *self); +void koto_playlist_debug_foreach(gpointer data, gpointer user_data); +gchar* koto_playlist_get_artwork(KotoPlaylist *self); +guint koto_playlist_get_current_position(KotoPlaylist *self); +gchar* koto_playlist_get_current_uuid(KotoPlaylist *self); +guint koto_playlist_get_length(KotoPlaylist *self); +gchar* koto_playlist_get_name(KotoPlaylist *self); +GQueue* koto_playlist_get_tracks(KotoPlaylist *self); +gchar* koto_playlist_get_uuid(KotoPlaylist *self); +gchar* koto_playlist_go_to_next(KotoPlaylist *self); +gchar* koto_playlist_go_to_previous(KotoPlaylist *self); +void koto_playlist_remove_track(KotoPlaylist *self, KotoIndexedFile *file); +void koto_playlist_remove_track_by_uuid(KotoPlaylist *self, gchar *uuid); +void koto_playlist_set_artwork(KotoPlaylist *self, const gchar *path); +void koto_playlist_save_state(KotoPlaylist *self); +void koto_playlist_set_name(KotoPlaylist *self, const gchar *name); +void koto_playlist_set_position(KotoPlaylist *self, guint pos); +void koto_playlist_set_uuid(KotoPlaylist *self, const gchar *uuid); + +G_END_DECLS diff --git a/theme/pages/_music-local.scss b/theme/pages/_music-local.scss index be50a5a..0ddfbc2 100644 --- a/theme/pages/_music-local.scss +++ b/theme/pages/_music-local.scss @@ -27,8 +27,12 @@ & > .album-list { & > flowboxchild > .album-view { - & > image { + & > overlay { margin-right: $itempadding; + + & > revealer > box { // Inner controls + background-color: rgba(0,0,0,0.75); + } } & > box {