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.
This commit is contained in:
parent
35762ca233
commit
cf81682f8c
13 changed files with 551 additions and 7 deletions
|
@ -19,9 +19,12 @@
|
||||||
#include <magic.h>
|
#include <magic.h>
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include "../playlist/current.h"
|
||||||
|
#include "../playlist/playlist.h"
|
||||||
#include "structs.h"
|
#include "structs.h"
|
||||||
#include "koto-utils.h"
|
#include "koto-utils.h"
|
||||||
|
|
||||||
|
extern KotoCurrentPlaylist *current_playlist;
|
||||||
extern sqlite3 *koto_db;
|
extern sqlite3 *koto_db;
|
||||||
|
|
||||||
struct _KotoIndexedAlbum {
|
struct _KotoIndexedAlbum {
|
||||||
|
@ -408,6 +411,23 @@ void koto_indexed_album_set_album_name(KotoIndexedAlbum *self, const gchar *albu
|
||||||
self->name = g_strdup(album_name);
|
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){
|
static void koto_indexed_album_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec){
|
||||||
KotoIndexedAlbum *self = KOTO_INDEXED_ALBUM(obj);
|
KotoIndexedAlbum *self = KOTO_INDEXED_ALBUM(obj);
|
||||||
|
|
||||||
|
|
|
@ -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_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_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_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);
|
void koto_indexed_album_update_path(KotoIndexedAlbum *self, const gchar *path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -125,7 +125,7 @@ void koto_playerbar_create_playback_details(KotoPlayerBar* bar) {
|
||||||
|
|
||||||
void koto_playerbar_create_primary_controls(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->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);
|
bar->forward_button = koto_button_new_with_icon("", "media-skip-forward-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
|
||||||
|
|
||||||
if (bar->back_button != NULL) {
|
if (bar->back_button != NULL) {
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
GtkWidget* koto_utils_create_image_from_filepath(gchar *filepath, gchar *fallback_icon, guint width, guint height) {
|
GtkWidget* koto_utils_create_image_from_filepath(gchar *filepath, gchar *fallback_icon, guint width, guint height) {
|
||||||
GtkWidget* image = NULL;
|
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
|
if (g_file_test(filepath, G_FILE_TEST_EXISTS)) { // File exists
|
||||||
image = gtk_image_new_from_file(filepath); // Load from the filepath
|
image = gtk_image_new_from_file(filepath); // Load from the filepath
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,14 +18,18 @@
|
||||||
#include <gtk-4.0/gdk/x11/gdkx.h>
|
#include <gtk-4.0/gdk/x11/gdkx.h>
|
||||||
#include "indexer/structs.h"
|
#include "indexer/structs.h"
|
||||||
#include "pages/music/music-local.h"
|
#include "pages/music/music-local.h"
|
||||||
|
#include "playlist/current.h"
|
||||||
#include "koto-config.h"
|
#include "koto-config.h"
|
||||||
#include "koto-nav.h"
|
#include "koto-nav.h"
|
||||||
#include "koto-playerbar.h"
|
#include "koto-playerbar.h"
|
||||||
#include "koto-window.h"
|
#include "koto-window.h"
|
||||||
|
|
||||||
|
extern KotoCurrentPlaylist *current_playlist;
|
||||||
|
|
||||||
struct _KotoWindow {
|
struct _KotoWindow {
|
||||||
GtkApplicationWindow parent_instance;
|
GtkApplicationWindow parent_instance;
|
||||||
KotoIndexedLibrary *library;
|
KotoIndexedLibrary *library;
|
||||||
|
KotoCurrentPlaylist *current_playlist;
|
||||||
|
|
||||||
GtkWidget *header_bar;
|
GtkWidget *header_bar;
|
||||||
GtkWidget *menu_button;
|
GtkWidget *menu_button;
|
||||||
|
@ -46,6 +50,8 @@ static void koto_window_class_init (KotoWindowClass *klass) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void koto_window_init (KotoWindow *self) {
|
static void koto_window_init (KotoWindow *self) {
|
||||||
|
current_playlist = koto_current_playlist_new();
|
||||||
|
|
||||||
GtkCssProvider* provider = gtk_css_provider_new();
|
GtkCssProvider* provider = gtk_css_provider_new();
|
||||||
gtk_css_provider_load_from_resource(provider, "/com/github/joshstrobl/koto/style.css");
|
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);
|
gtk_style_context_add_provider_for_display(gdk_display_get_default(), GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||||
|
|
|
@ -10,6 +10,8 @@ koto_sources = [
|
||||||
'pages/music/artist-view.c',
|
'pages/music/artist-view.c',
|
||||||
'pages/music/disc-view.c',
|
'pages/music/disc-view.c',
|
||||||
'pages/music/music-local.c',
|
'pages/music/music-local.c',
|
||||||
|
'playlist/current.c',
|
||||||
|
'playlist/playlist.c',
|
||||||
'main.c',
|
'main.c',
|
||||||
'koto-button.c',
|
'koto-button.c',
|
||||||
'koto-expander.c',
|
'koto-expander.c',
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
#include <glib-2.0/glib.h>
|
#include <glib-2.0/glib.h>
|
||||||
#include <gtk-4.0/gtk/gtk.h>
|
#include <gtk-4.0/gtk/gtk.h>
|
||||||
|
#include "../../koto-button.h"
|
||||||
#include "../../indexer/structs.h"
|
#include "../../indexer/structs.h"
|
||||||
#include "album-view.h"
|
#include "album-view.h"
|
||||||
#include "disc-view.h"
|
#include "disc-view.h"
|
||||||
|
@ -30,6 +31,12 @@ struct _KotoAlbumView {
|
||||||
GtkWidget *album_tracks_box;
|
GtkWidget *album_tracks_box;
|
||||||
GtkWidget *discs;
|
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;
|
GtkWidget *album_label;
|
||||||
GHashTable *cd_to_track_listbox;
|
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->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
|
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) {
|
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) 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) {
|
void koto_album_view_set_album(KotoAlbumView *self, KotoIndexedAlbum *album) {
|
||||||
if (album == NULL) {
|
if (album == NULL) {
|
||||||
return;
|
return;
|
||||||
|
@ -123,10 +166,7 @@ void koto_album_view_set_album(KotoAlbumView *self, KotoIndexedAlbum *album) {
|
||||||
self->album = album;
|
self->album = album;
|
||||||
|
|
||||||
gchar *album_art = koto_indexed_album_get_album_art(self->album); // Get the art for the 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_image_set_from_file(GTK_IMAGE(self->album_overlay_art), album_art);
|
||||||
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
|
|
||||||
|
|
||||||
gchar *album_name;
|
gchar *album_name;
|
||||||
g_object_get(album, "name", &album_name, NULL); // Get the 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);
|
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) {
|
int koto_album_view_sort_discs(GtkListBoxRow *disc1, GtkListBoxRow *disc2, gpointer user_data) {
|
||||||
(void) user_data;
|
(void) user_data;
|
||||||
KotoDiscView *disc1_item = KOTO_DISC_VIEW(gtk_list_box_row_get_child(disc1));
|
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) {
|
KotoAlbumView* koto_album_view_new(KotoIndexedAlbum *album) {
|
||||||
return g_object_new(KOTO_TYPE_ALBUM_VIEW, "album", album, NULL);
|
return g_object_new(KOTO_TYPE_ALBUM_VIEW, "album", album, NULL);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,10 @@ G_DECLARE_FINAL_TYPE(KotoAlbumView, koto_album_view, KOTO, ALBUM_VIEW, GObject)
|
||||||
KotoAlbumView* koto_album_view_new(KotoIndexedAlbum *album);
|
KotoAlbumView* koto_album_view_new(KotoIndexedAlbum *album);
|
||||||
GtkWidget* koto_album_view_get_main(KotoAlbumView *self);
|
GtkWidget* koto_album_view_get_main(KotoAlbumView *self);
|
||||||
void koto_album_view_add_track_to_listbox(KotoIndexedAlbum *self, KotoIndexedFile *file);
|
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_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);
|
int koto_album_view_sort_discs(GtkListBoxRow *track1, GtkListBoxRow *track2, gpointer user_data);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
60
src/playlist/current.c
Normal file
60
src/playlist/current.c
Normal file
|
@ -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 <glib-2.0/glib-object.h>
|
||||||
|
#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);
|
||||||
|
}
|
40
src/playlist/current.h
Normal file
40
src/playlist/current.h
Normal file
|
@ -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 <glib-2.0/glib-object.h>
|
||||||
|
#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
|
294
src/playlist/playlist.c
Normal file
294
src/playlist/playlist.c
Normal file
|
@ -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 <glib-2.0/glib.h>
|
||||||
|
#include <magic.h>
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#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
|
||||||
|
);
|
||||||
|
}
|
59
src/playlist/playlist.h
Normal file
59
src/playlist/playlist.h
Normal file
|
@ -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 <glib-2.0/glib-object.h>
|
||||||
|
#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
|
|
@ -27,8 +27,12 @@
|
||||||
|
|
||||||
& > .album-list {
|
& > .album-list {
|
||||||
& > flowboxchild > .album-view {
|
& > flowboxchild > .album-view {
|
||||||
& > image {
|
& > overlay {
|
||||||
margin-right: $itempadding;
|
margin-right: $itempadding;
|
||||||
|
|
||||||
|
& > revealer > box { // Inner controls
|
||||||
|
background-color: rgba(0,0,0,0.75);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& > box {
|
& > box {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue