Port to GTK4, start implementation of Local Music view, Artist and Album Views.
This commit is contained in:
parent
8948a8ec9f
commit
588a68b2cc
28 changed files with 1103 additions and 270 deletions
|
@ -1,3 +1,11 @@
|
|||
window {
|
||||
background-color: #2e2e2e;
|
||||
}
|
||||
|
||||
headerbar, .player-bar {
|
||||
background-color: #1d1d1d;
|
||||
}
|
||||
|
||||
.primary-nav > .frame > box >.koto-button > box {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
@ -19,3 +27,11 @@
|
|||
.expander-header label {
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
.page-music-local .artist-list {
|
||||
background-color: #1d1d1d;
|
||||
}
|
||||
|
||||
.page-music-local row {
|
||||
padding: 10px;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,6 @@ subdir('po')
|
|||
|
||||
gnome.post_install(
|
||||
glib_compile_schemas: true,
|
||||
gtk_update_icon_cache: true
|
||||
gtk_update_icon_cache: false
|
||||
)
|
||||
meson.add_install_script('build-aux/meson/postinstall.py')
|
||||
|
|
|
@ -267,7 +267,7 @@ void koto_indexed_album_update_path(KotoIndexedAlbum *self, const gchar* new_pat
|
|||
|
||||
g_free(album_art_no_ext);
|
||||
g_free(lower_art);
|
||||
} else if (g_str_has_prefix(mime_type, "audio/")) { // Is an audio file
|
||||
} else if (g_str_has_prefix(mime_type, "audio/") || g_str_has_prefix(mime_type, "video/ogg")) { // Is an audio file or ogg because it is special
|
||||
KotoIndexedFile *file = koto_indexed_file_new(full_path);
|
||||
|
||||
if (file != NULL) { // Is a file
|
||||
|
|
|
@ -72,9 +72,7 @@ void koto_indexed_library_add_artist(KotoIndexedLibrary *self, KotoIndexedArtist
|
|||
return;
|
||||
}
|
||||
|
||||
if (self->music_artists == NULL) { // Not a HashTable
|
||||
self->music_artists = g_hash_table_new(g_str_hash, g_str_equal);
|
||||
}
|
||||
koto_indexed_library_get_artists(self); // Call to generate if needed
|
||||
|
||||
gchar *artist_name;
|
||||
g_object_get(artist, "name", &artist_name, NULL);
|
||||
|
@ -92,21 +90,25 @@ KotoIndexedArtist* koto_indexed_library_get_artist(KotoIndexedLibrary *self, gch
|
|||
return NULL;
|
||||
}
|
||||
|
||||
if (self->music_artists == NULL) { // Not a HashTable
|
||||
return NULL;
|
||||
}
|
||||
koto_indexed_library_get_artists(self); // Call to generate if needed
|
||||
|
||||
return g_hash_table_lookup(self->music_artists, (KotoIndexedArtist*) artist_name);
|
||||
}
|
||||
|
||||
GHashTable* koto_indexed_library_get_artists(KotoIndexedLibrary *self) {
|
||||
if (self->music_artists == NULL) { // Not a HashTable
|
||||
self->music_artists = g_hash_table_new(g_str_hash, g_str_equal);
|
||||
}
|
||||
|
||||
return self->music_artists;
|
||||
}
|
||||
|
||||
void koto_indexed_library_remove_artist(KotoIndexedLibrary *self, KotoIndexedArtist *artist) {
|
||||
if (artist == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->music_artists == NULL) { // Not a HashTable
|
||||
return;
|
||||
}
|
||||
koto_indexed_library_get_artists(self); // Call to generate if needed
|
||||
|
||||
gchar *artist_name;
|
||||
g_object_get(artist, "name", &artist_name, NULL);
|
||||
|
@ -223,6 +225,8 @@ void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth) {
|
|||
}
|
||||
|
||||
void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data) {
|
||||
(void)artist_key;
|
||||
(void)data;
|
||||
KotoIndexedArtist *artist = (KotoIndexedArtist*) artist_ptr;
|
||||
gchar *artist_name;
|
||||
g_object_get(artist, "name", &artist_name, NULL);
|
||||
|
|
|
@ -28,6 +28,7 @@ KotoIndexedLibrary* koto_indexed_library_new(const gchar *path);
|
|||
|
||||
void koto_indexed_library_add_artist(KotoIndexedLibrary *self, KotoIndexedArtist *artist);
|
||||
KotoIndexedArtist* koto_indexed_library_get_artist(KotoIndexedLibrary *self, gchar* artist_name);
|
||||
GHashTable* koto_indexed_library_get_artists(KotoIndexedLibrary *self);
|
||||
void koto_indexed_library_remove_artist(KotoIndexedLibrary *self, KotoIndexedArtist *artist);
|
||||
void start_indexing(KotoIndexedLibrary *self);
|
||||
void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth);
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <gtk-3.0/gtk/gtk.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "koto-button.h"
|
||||
#include "koto-config.h"
|
||||
|
||||
|
@ -51,10 +51,10 @@ guint koto_get_pixbuf_size(KotoButtonPixbufSize s) {
|
|||
|
||||
enum {
|
||||
PROP_BTN_0,
|
||||
PROP_USE_FROM_FILE,
|
||||
PROP_PIX_SIZE,
|
||||
PROP_TEXT,
|
||||
PROP_BADGE_TEXT,
|
||||
PROP_PIX,
|
||||
PROP_ICON_NAME,
|
||||
PROP_ALT_ICON_NAME,
|
||||
N_BTN_PROPERTIES
|
||||
|
@ -63,11 +63,10 @@ enum {
|
|||
static GParamSpec *btn_props[N_BTN_PROPERTIES] = { NULL, };
|
||||
|
||||
struct _KotoButton {
|
||||
GtkEventBox parent_instance;
|
||||
GtkBox *content;
|
||||
GtkBox parent_instance;
|
||||
guint pix_size;
|
||||
|
||||
GtkWidget *button_image;
|
||||
GtkWidget *button_pic;
|
||||
GtkWidget *badge_label;
|
||||
GtkWidget *button_label;
|
||||
|
||||
|
@ -76,7 +75,7 @@ struct _KotoButton {
|
|||
gchar *alt_icon_name;
|
||||
gchar *text;
|
||||
|
||||
GdkPixbuf *pix;
|
||||
gboolean use_from_file;
|
||||
gboolean currently_showing_alt;
|
||||
};
|
||||
|
||||
|
@ -84,17 +83,27 @@ struct _KotoButtonClass {
|
|||
GtkBoxClass parent_class;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoButton, koto_button, GTK_TYPE_EVENT_BOX);
|
||||
G_DEFINE_TYPE(KotoButton, koto_button, GTK_TYPE_BOX);
|
||||
|
||||
static void koto_button_constructed(GObject *obj);
|
||||
static void koto_button_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec);
|
||||
static void koto_button_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec);
|
||||
|
||||
static void koto_button_class_init(KotoButtonClass *c) {
|
||||
GObjectClass *gobject_class;
|
||||
gobject_class = G_OBJECT_CLASS(c);
|
||||
gobject_class->constructed = koto_button_constructed;
|
||||
gobject_class->set_property = koto_button_set_property;
|
||||
gobject_class->get_property = koto_button_get_property;
|
||||
|
||||
btn_props[PROP_USE_FROM_FILE] = g_param_spec_boolean(
|
||||
"use-from-file",
|
||||
"Use from a file / file name",
|
||||
"Use from a file / file name",
|
||||
FALSE,
|
||||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
btn_props[PROP_PIX_SIZE] = g_param_spec_uint(
|
||||
"pixbuf-size",
|
||||
"Pixbuf Size",
|
||||
|
@ -121,14 +130,6 @@ static void koto_button_class_init(KotoButtonClass *c) {
|
|||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
btn_props[PROP_PIX] = g_param_spec_object(
|
||||
"pixbuf",
|
||||
"Pixbuf",
|
||||
"Pixbuf",
|
||||
GDK_TYPE_PIXBUF,
|
||||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
btn_props[PROP_ICON_NAME] = g_param_spec_string(
|
||||
"icon-name",
|
||||
"Icon Name",
|
||||
|
@ -149,17 +150,24 @@ static void koto_button_class_init(KotoButtonClass *c) {
|
|||
}
|
||||
|
||||
static void koto_button_init(KotoButton *self) {
|
||||
self->currently_showing_alt = FALSE;
|
||||
}
|
||||
|
||||
static void koto_button_constructed(GObject *obj) {
|
||||
KotoButton *self = KOTO_BUTTON(obj);
|
||||
GtkStyleContext *style = gtk_widget_get_style_context(GTK_WIDGET(self));
|
||||
gtk_style_context_add_class(style, "koto-button");
|
||||
self->currently_showing_alt = FALSE;
|
||||
self->content = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
|
||||
gtk_container_add(GTK_CONTAINER(self), GTK_WIDGET(self->content));
|
||||
|
||||
G_OBJECT_CLASS (koto_button_parent_class)->constructed (obj);
|
||||
}
|
||||
|
||||
static void koto_button_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) {
|
||||
KotoButton *self = KOTO_BUTTON(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_USE_FROM_FILE:
|
||||
g_value_set_boolean(val, self->use_from_file);
|
||||
break;
|
||||
case PROP_PIX_SIZE:
|
||||
g_value_set_uint(val, self->pix_size);
|
||||
break;
|
||||
|
@ -175,9 +183,6 @@ static void koto_button_get_property(GObject *obj, guint prop_id, GValue *val, G
|
|||
case PROP_ALT_ICON_NAME:
|
||||
g_value_set_string(val, self->alt_icon_name);
|
||||
break;
|
||||
case PROP_PIX:
|
||||
g_value_set_object(val, self->pix);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
|
@ -188,18 +193,23 @@ static void koto_button_set_property(GObject *obj, guint prop_id, const GValue *
|
|||
KotoButton *self = KOTO_BUTTON(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_USE_FROM_FILE:
|
||||
self->use_from_file = g_value_get_boolean(val);
|
||||
break;
|
||||
case PROP_PIX_SIZE:
|
||||
koto_button_set_pixbuf_size(self, g_value_get_uint(val));
|
||||
break;
|
||||
case PROP_TEXT:
|
||||
koto_button_set_text(self, g_strdup(g_value_get_string(val)));
|
||||
if (val == NULL) {
|
||||
koto_button_set_text(self, NULL);
|
||||
} else {
|
||||
koto_button_set_text(self, g_strdup(g_value_get_string(val)));
|
||||
}
|
||||
|
||||
break;
|
||||
case PROP_BADGE_TEXT:
|
||||
koto_button_set_badge_text(self, g_strdup(g_value_get_string(val)));
|
||||
break;
|
||||
case PROP_PIX:
|
||||
koto_button_set_pixbuf(self, (GdkPixbuf*) g_value_get_object(val));
|
||||
break;
|
||||
case PROP_ICON_NAME:
|
||||
koto_button_set_icon_name(self, g_strdup(g_value_get_string(val)), FALSE);
|
||||
if (!self->currently_showing_alt) { // Not showing alt
|
||||
|
@ -234,7 +244,7 @@ void koto_button_set_badge_text(KotoButton *self, gchar *text) {
|
|||
gtk_label_set_text(GTK_LABEL(self->badge_label), self->badge_text);
|
||||
} else {
|
||||
self->badge_label = gtk_label_new(self->badge_text); // Create our label
|
||||
gtk_box_pack_end(self->content, self->badge_label, FALSE, FALSE, 0); // Add to the end of the box
|
||||
gtk_box_append(GTK_BOX(self), self->badge_label);
|
||||
}
|
||||
|
||||
if (strcmp(self->badge_text, "") != 0) { // Empty badge
|
||||
|
@ -271,8 +281,8 @@ void koto_button_set_icon_name(KotoButton *self, gchar *icon_name, gboolean for_
|
|||
}
|
||||
|
||||
if (hide_image) { // Should hide the image
|
||||
if (GTK_IS_IMAGE(self->button_image)) { // If we already have a button image
|
||||
gtk_widget_hide(self->button_image); // Hide
|
||||
if (GTK_IS_PICTURE(self->button_pic)) { // If we already have a button image
|
||||
gtk_widget_hide(self->button_pic); // Hide
|
||||
}
|
||||
|
||||
return;
|
||||
|
@ -281,48 +291,20 @@ void koto_button_set_icon_name(KotoButton *self, gchar *icon_name, gboolean for_
|
|||
g_object_notify_by_pspec(G_OBJECT(self), for_alt ? btn_props[PROP_ALT_ICON_NAME] : btn_props[PROP_ICON_NAME]);
|
||||
}
|
||||
|
||||
void koto_button_set_pixbuf(KotoButton *self, GdkPixbuf *pix) {
|
||||
g_return_if_fail(self->pix_size != 0); // Return if don't have a pixbuf size to indicate what to scale to
|
||||
|
||||
if (!GDK_IS_PIXBUF(pix)) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->pix = pix;
|
||||
|
||||
GdkPixbuf *use_pix = pix; // Default to using our provided pixbuf
|
||||
if (gdk_pixbuf_get_height(pix) > (int) self->pix_size) { // If our height is greater than our desired pixel size
|
||||
use_pix = gdk_pixbuf_scale_simple(pix, -1, self->pix_size, GDK_INTERP_BILINEAR); // Scale using bilnear
|
||||
g_return_if_fail(use_pix != NULL); // Return if we could not allocate memory for the scaling
|
||||
}
|
||||
|
||||
if (GTK_IS_IMAGE(self->button_image)) { // If we already have a button image
|
||||
gtk_image_set_from_pixbuf(GTK_IMAGE(self->button_image), use_pix); // Update our pixbuf
|
||||
gtk_widget_show(self->button_image); // Ensure we show the image
|
||||
} else { // No image set
|
||||
GtkWidget *new_image = gtk_image_new_from_pixbuf(use_pix); // Create our new image
|
||||
g_return_if_fail(GTK_IS_IMAGE(new_image)); // Return if we failed to create our image
|
||||
self->button_image = new_image;
|
||||
gtk_box_pack_start(self->content, self->button_image, FALSE, FALSE, 0); // Prepend the image
|
||||
gtk_box_reorder_child(self->content, self->button_image, 0);
|
||||
}
|
||||
|
||||
g_object_notify_by_pspec(G_OBJECT(self), btn_props[PROP_PIX]);
|
||||
}
|
||||
|
||||
void koto_button_set_pixbuf_size(KotoButton *self, guint size) {
|
||||
g_return_if_fail(size != self->pix_size); // If the sizes aren't different, return
|
||||
|
||||
self->pix_size = size;
|
||||
|
||||
if (GDK_PIXBUF(self->pix)) { // If we already have a pixbuf set and we're changing the size
|
||||
koto_button_set_pixbuf(self, self->pix);
|
||||
}
|
||||
gtk_widget_set_size_request(GTK_WIDGET(self), self->pix_size, self->pix_size);
|
||||
|
||||
g_object_notify_by_pspec(G_OBJECT(self), btn_props[PROP_PIX_SIZE]);
|
||||
}
|
||||
|
||||
void koto_button_set_text(KotoButton *self, gchar *text) {
|
||||
if (text == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
gchar *copied_text = g_strdup(text); // Copy our text
|
||||
|
||||
if (strcmp(copied_text, "") == 0) { // Clearing our text
|
||||
|
@ -336,17 +318,18 @@ void koto_button_set_text(KotoButton *self, gchar *text) {
|
|||
gtk_label_set_text(GTK_LABEL(self->button_label), self->text);
|
||||
gtk_widget_show(self->button_label); // Show the label
|
||||
} else { // Have a label but no longer text
|
||||
gtk_container_remove(GTK_CONTAINER(self), self->button_label); // Remove the label
|
||||
gtk_box_remove(GTK_BOX(self), self->button_label);
|
||||
g_free(self->button_label);
|
||||
}
|
||||
} else { // If we do not have a button label
|
||||
if (strcmp(self->text, "") != 0) { // If we have text
|
||||
self->button_label = gtk_label_new(self->text); // Create our label
|
||||
gtk_label_set_xalign(GTK_LABEL(self->button_label), 0);
|
||||
gtk_box_pack_start(self->content, self->button_label, FALSE, FALSE, 0); // Add to the beginning of the box
|
||||
|
||||
if (GTK_IS_IMAGE(self->button_image)) { // If we have an image
|
||||
gtk_box_reorder_child(self->content, self->button_image, 0); // Move to the beginning
|
||||
if (GTK_IS_IMAGE(self->button_pic)) { // If we have an image
|
||||
gtk_box_insert_child_after(GTK_BOX(self), self->button_label, self->button_pic);
|
||||
} else {
|
||||
gtk_box_prepend(GTK_BOX(self), GTK_WIDGET(self->button_label));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -361,11 +344,22 @@ void koto_button_show_image(KotoButton *self, gboolean use_alt) {
|
|||
return;
|
||||
}
|
||||
|
||||
GtkIconTheme *theme = gtk_icon_theme_get_default(); // Get the default icon theme
|
||||
GdkPixbuf* icon_pix = gtk_icon_theme_load_icon_for_scale(theme, use_alt ? self->alt_icon_name : self->icon_name, (gint) self->pix_size, 1, GTK_ICON_LOOKUP_USE_BUILTIN & GTK_ICON_LOOKUP_GENERIC_FALLBACK, NULL);
|
||||
g_return_if_fail(GDK_IS_PIXBUF(icon_pix)); // Return if not a pixbuf
|
||||
koto_button_set_pixbuf(self, icon_pix);
|
||||
self->currently_showing_alt = use_alt;
|
||||
if (self->use_from_file) { // Use from a file instead of icon name
|
||||
// TODO: Add
|
||||
} else { // From icon name
|
||||
self->currently_showing_alt = use_alt;
|
||||
gchar *name = use_alt ? self->alt_icon_name : self->icon_name;
|
||||
|
||||
if (GTK_IS_IMAGE(self->button_pic)) {
|
||||
gtk_image_set_from_icon_name(GTK_IMAGE(self->button_pic), name); // Just update the existing iamge
|
||||
} else { // Not an image
|
||||
self->button_pic = gtk_image_new_from_icon_name(name); // Get our new image
|
||||
gtk_image_set_pixel_size(GTK_IMAGE(self->button_pic), self->pix_size);
|
||||
gtk_box_prepend(GTK_BOX(self), self->button_pic); // Prepend to the box
|
||||
}
|
||||
|
||||
gtk_image_set_icon_size(GTK_IMAGE(self->button_pic), GTK_ICON_SIZE_INHERIT); // Inherit height of parent widget
|
||||
}
|
||||
}
|
||||
|
||||
KotoButton* koto_button_new_plain(gchar *label) {
|
||||
|
@ -384,12 +378,3 @@ KotoButton* koto_button_new_with_icon(gchar *label, gchar *icon_name, gchar *alt
|
|||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
KotoButton* koto_button_new_with_pixbuf(gchar *label, GdkPixbuf *pix, KotoButtonPixbufSize size) {
|
||||
return g_object_new(KOTO_TYPE_BUTTON,
|
||||
"button-text", label,
|
||||
"pixbuf", pix,
|
||||
"pixbuf-size", koto_get_pixbuf_size(size),
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <glib-2.0/glib-object.h>
|
||||
#include <gdk-pixbuf-2.0/gdk-pixbuf/gdk-pixbuf.h>
|
||||
#include <gtk-3.0/gtk/gtk.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
|
@ -37,7 +37,7 @@ typedef enum {
|
|||
|
||||
#define KOTO_TYPE_BUTTON (koto_button_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (KotoButton, koto_button, KOTO, BUTTON, GtkEventBox)
|
||||
G_DECLARE_FINAL_TYPE (KotoButton, koto_button, KOTO, BUTTON, GtkBox)
|
||||
|
||||
guint koto_get_pixbuf_size(KotoButtonPixbufSize size);
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#include <gtk-3.0/gtk/gtk.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "koto-config.h"
|
||||
#include "koto-button.h"
|
||||
#include "koto-expander.h"
|
||||
|
@ -128,7 +128,8 @@ static void koto_expander_set_property(GObject *obj, guint prop_id, const GValue
|
|||
|
||||
if (GTK_IS_WIDGET(new_button)) { // Created our widget successfully
|
||||
self->header_button = new_button;
|
||||
gtk_box_pack_start(GTK_BOX(self->header), GTK_WIDGET(self->header_button), TRUE, TRUE, 0); // Add it
|
||||
gtk_widget_set_hexpand(GTK_WIDGET(self->header_button), TRUE);
|
||||
gtk_box_prepend(GTK_BOX(self->header), GTK_WIDGET(self->header_button));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,11 +155,9 @@ static void koto_expander_set_property(GObject *obj, guint prop_id, const GValue
|
|||
}
|
||||
|
||||
static void koto_expander_init(KotoExpander *self) {
|
||||
GtkSettings *settings = gtk_settings_get_default();
|
||||
g_object_set(settings, "gtk_application_prefer_dark_theme", TRUE, NULL);
|
||||
|
||||
GtkStyleContext *style = gtk_widget_get_style_context(GTK_WIDGET(self));
|
||||
gtk_style_context_add_class(style, "expander");
|
||||
gtk_widget_set_hexpand((GTK_WIDGET(self)), TRUE);
|
||||
|
||||
self->header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
|
||||
|
||||
|
@ -170,14 +169,16 @@ static void koto_expander_init(KotoExpander *self) {
|
|||
gtk_revealer_set_transition_type(GTK_REVEALER(self->revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
|
||||
|
||||
self->header_expand_button = koto_button_new_with_icon("", "pan-down-symbolic", "pan-up-symbolic", KOTO_BUTTON_PIXBUF_SIZE_SMALL);
|
||||
gtk_box_pack_end(GTK_BOX(self->header), GTK_WIDGET(self->header_expand_button), FALSE, FALSE, 0);
|
||||
gtk_box_append(GTK_BOX(self->header), GTK_WIDGET(self->header_expand_button));
|
||||
|
||||
gtk_box_pack_start(GTK_BOX(self), self->header, FALSE, FALSE, 0);
|
||||
gtk_box_pack_end(GTK_BOX(self), self->revealer, TRUE, TRUE, 0);
|
||||
gtk_box_prepend(GTK_BOX(self), self->header);
|
||||
gtk_box_append(GTK_BOX(self), self->revealer);
|
||||
|
||||
self->constructed = TRUE;
|
||||
|
||||
g_signal_connect(self->header_expand_button, "button-release-event", G_CALLBACK(koto_expander_toggle_content), self);
|
||||
GtkGesture *controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(controller, "pressed", G_CALLBACK(koto_expander_toggle_content), self);
|
||||
gtk_widget_add_controller(GTK_WIDGET(self->header_expand_button), GTK_EVENT_CONTROLLER(controller));
|
||||
}
|
||||
|
||||
void koto_expander_set_secondary_button(KotoExpander *self, KotoButton *new_button) {
|
||||
|
@ -190,11 +191,11 @@ void koto_expander_set_secondary_button(KotoExpander *self, KotoButton *new_butt
|
|||
}
|
||||
|
||||
if (GTK_IS_WIDGET(self->header_secondary_button)) { // Already have a button
|
||||
gtk_container_remove(GTK_CONTAINER(self->header), GTK_WIDGET(self->header_secondary_button));
|
||||
gtk_box_remove(GTK_BOX(self->header), GTK_WIDGET(self->header_secondary_button));
|
||||
}
|
||||
|
||||
self->header_secondary_button = new_button;
|
||||
gtk_box_pack_end(GTK_BOX(self->header), GTK_WIDGET(self->header_secondary_button), FALSE, FALSE, 0);
|
||||
gtk_box_append(GTK_BOX(self->header), GTK_WIDGET(self->header_secondary_button));
|
||||
|
||||
g_object_notify_by_pspec(G_OBJECT(self), expander_props[PROP_HEADER_SECONDARY_BUTTON]);
|
||||
}
|
||||
|
@ -204,20 +205,22 @@ void koto_expander_set_content(KotoExpander *self, GtkWidget *new_content) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (self->content != NULL) { // Already have content
|
||||
gtk_container_remove(GTK_CONTAINER(self->revealer), self->content);
|
||||
g_free(self->content);
|
||||
if (GTK_IS_WIDGET(self->content)) { // Already have content
|
||||
gtk_revealer_set_child(GTK_REVEALER(self->revealer), NULL);
|
||||
g_object_unref(self->content);
|
||||
}
|
||||
|
||||
self->content = new_content;
|
||||
gtk_container_add(GTK_CONTAINER(self->revealer), self->content);
|
||||
gtk_revealer_set_child(GTK_REVEALER(self->revealer), self->content);
|
||||
|
||||
g_object_notify_by_pspec(G_OBJECT(self), expander_props[PROP_CONTENT]);
|
||||
}
|
||||
|
||||
void koto_expander_toggle_content(GtkWidget *button, GdkEvent *event, gpointer data) {
|
||||
void koto_expander_toggle_content(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data) {
|
||||
(void) gesture; (void) n_press; (void) x; (void) y;
|
||||
KotoExpander* self = data;
|
||||
koto_button_flip(KOTO_BUTTON(button));
|
||||
|
||||
koto_button_flip(KOTO_BUTTON(self->header_expand_button));
|
||||
GtkRevealer* rev = GTK_REVEALER(self->revealer);
|
||||
gtk_revealer_set_reveal_child(rev, !gtk_revealer_get_reveal_child(rev)); // Invert our values
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <gtk-3.0/gtk/gtk.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "koto-button.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
@ -32,6 +32,6 @@ void koto_expander_set_icon_name(KotoExpander *self, const gchar *in);
|
|||
void koto_expander_set_label(KotoExpander *self, const gchar *label);
|
||||
void koto_expander_set_secondary_button(KotoExpander *self, KotoButton *new_button);
|
||||
void koto_expander_set_content(KotoExpander *self, GtkWidget *new_content);
|
||||
void koto_expander_toggle_content(GtkWidget *button, GdkEvent *event, gpointer data);
|
||||
void koto_expander_toggle_content(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
|
||||
|
||||
G_END_DECLS
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
/* koto-headerbar.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 "koto-config.h"
|
||||
#include "koto-headerbar.h"
|
||||
|
||||
struct _KotoHeaderBar {
|
||||
GtkHeaderBar parent_instance;
|
||||
GtkButton *menu_button;
|
||||
GtkWidget *search;
|
||||
};
|
||||
|
||||
struct _KotoHeaderBarClass {
|
||||
GtkHeaderBarClass parent_class;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoHeaderBar, koto_headerbar, GTK_TYPE_HEADER_BAR)
|
||||
|
||||
static void koto_headerbar_class_init(KotoHeaderBarClass *c) {
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(c);
|
||||
gtk_widget_class_set_template_from_resource(widget_class, "/com/github/joshstrobl/koto/koto-headerbar.ui");
|
||||
}
|
||||
|
||||
static void koto_headerbar_init(KotoHeaderBar *self) {
|
||||
gtk_widget_init_template(GTK_WIDGET(self));
|
||||
gtk_widget_show_all(GTK_WIDGET(self));
|
||||
}
|
||||
|
||||
KotoHeaderBar* koto_headerbar_new(void) {
|
||||
return g_object_new(KOTO_TYPE_HEADERBAR, NULL);
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
<property name="icon-name">audio-headphones</property>
|
||||
<property name="icon_size">3</property>
|
||||
</object>
|
||||
<template class="KotoHeaderBar" parent="GtkHeaderBar">
|
||||
<template class="GtkHeaderBar" parent="GtkHeaderBar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="show-close-button">True</property>
|
||||
|
|
|
@ -15,14 +15,15 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <gtk-3.0/gtk/gtk.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "koto-config.h"
|
||||
#include "koto-button.h"
|
||||
#include "koto-expander.h"
|
||||
#include "koto-nav.h"
|
||||
|
||||
struct _KotoNav {
|
||||
GtkScrolledWindow parent_instance;
|
||||
GObject parent_instance;
|
||||
GtkWidget *win;
|
||||
GtkWidget *content;
|
||||
|
||||
KotoButton *home_button;
|
||||
|
@ -49,78 +50,81 @@ struct _KotoNav {
|
|||
};
|
||||
|
||||
struct _KotoNavClass {
|
||||
GtkScrolledWindowClass parent_class;
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoNav, koto_nav, GTK_TYPE_SCROLLED_WINDOW);
|
||||
G_DEFINE_TYPE(KotoNav, koto_nav, G_TYPE_OBJECT);
|
||||
|
||||
static void koto_nav_class_init(KotoNavClass *c) {
|
||||
|
||||
(void) c;
|
||||
}
|
||||
|
||||
static void koto_nav_init(KotoNav *self) {
|
||||
gtk_widget_set_size_request(GTK_WIDGET(self), 300, -1); // Take up 300px width
|
||||
self->win = gtk_scrolled_window_new();
|
||||
gtk_widget_set_hexpand_set(self->win, TRUE); // using hexpand-set works, hexpand seems to break it by causing it to take up way too much space
|
||||
gtk_widget_set_size_request(self->win, 300, -1);
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(self->win), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
||||
gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(self->win), 300);
|
||||
gtk_scrolled_window_set_max_content_width(GTK_SCROLLED_WINDOW(self->win), 300);
|
||||
gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(self->win), TRUE);
|
||||
|
||||
self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
|
||||
|
||||
GtkStyleContext *style = gtk_widget_get_style_context(GTK_WIDGET(self));
|
||||
gtk_style_context_add_class(style, "primary-nav");
|
||||
gtk_widget_add_css_class(self->win, "primary-nav");
|
||||
gtk_widget_set_vexpand(self->win, TRUE);
|
||||
|
||||
gtk_container_add(GTK_CONTAINER(self), self->content);
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->win), self->content);
|
||||
|
||||
KotoButton *h_button = koto_button_new_with_icon("Home", "user-home-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_SMALL);
|
||||
|
||||
if (h_button != NULL) {
|
||||
self->home_button = h_button;
|
||||
gtk_box_pack_start(GTK_BOX(self->content), GTK_WIDGET(self->home_button), FALSE, FALSE, 0);
|
||||
gtk_box_append(GTK_BOX(self->content), GTK_WIDGET(self->home_button));
|
||||
}
|
||||
|
||||
koto_nav_create_audiobooks_section(self);
|
||||
koto_nav_create_music_section(self);
|
||||
koto_nav_create_podcasts_section(self);
|
||||
|
||||
|
||||
KotoButton *playlist_add_button = koto_button_new_with_icon("", "list-add-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_SMALL);
|
||||
KotoExpander *pl_expander = koto_expander_new_with_button("playlist-symbolic", "Playlists", playlist_add_button);
|
||||
|
||||
if (pl_expander != NULL) {
|
||||
self->playlists_expander = pl_expander;
|
||||
gtk_box_pack_start(GTK_BOX(self->content), GTK_WIDGET(self->playlists_expander), FALSE, FALSE, 0);
|
||||
gtk_box_append(GTK_BOX(self->content), GTK_WIDGET(self->playlists_expander));
|
||||
}
|
||||
|
||||
gtk_widget_show_all(GTK_WIDGET(self));
|
||||
}
|
||||
|
||||
void koto_nav_create_audiobooks_section(KotoNav *self) {
|
||||
KotoExpander *a_expander = koto_expander_new("ephy-bookmarks-symbolic", "Audiobooks");
|
||||
|
||||
self->audiobook_expander = a_expander;
|
||||
gtk_box_pack_start(GTK_BOX(self->content), GTK_WIDGET(self->audiobook_expander), FALSE, FALSE, 0);
|
||||
gtk_box_append(GTK_BOX(self->content), GTK_WIDGET(self->audiobook_expander));
|
||||
|
||||
GtkWidget *new_content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
koto_expander_set_content(a_expander, new_content);
|
||||
|
||||
self->audiobooks_local = koto_button_new_plain("Local Library");
|
||||
self->audiobooks_audible = koto_button_new_plain("Audible");
|
||||
self->audiobooks_librivox = koto_button_new_plain("LibriVox");
|
||||
|
||||
gtk_box_pack_start(GTK_BOX(new_content), GTK_WIDGET(self->audiobooks_local), FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(new_content), GTK_WIDGET(self->audiobooks_audible), FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(new_content), GTK_WIDGET(self->audiobooks_librivox), FALSE, FALSE, 0);
|
||||
|
||||
koto_expander_set_content(a_expander, new_content);
|
||||
gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->audiobooks_local));
|
||||
gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->audiobooks_audible));
|
||||
gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->audiobooks_librivox));
|
||||
}
|
||||
|
||||
void koto_nav_create_music_section(KotoNav *self) {
|
||||
KotoExpander *m_expander = koto_expander_new("emblem-music-symbolic", "Music");
|
||||
self->music_expander = m_expander;
|
||||
gtk_box_pack_start(GTK_BOX(self->content), GTK_WIDGET(self->music_expander), FALSE, FALSE, 0);
|
||||
gtk_box_append(GTK_BOX(self->content), GTK_WIDGET(self->music_expander));
|
||||
|
||||
GtkWidget *new_content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
|
||||
self->music_local = koto_button_new_plain("Local Library");
|
||||
self->music_radio = koto_button_new_plain("Radio");
|
||||
|
||||
gtk_box_pack_start(GTK_BOX(new_content), GTK_WIDGET(self->music_local), FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(new_content), GTK_WIDGET(self->music_radio), FALSE, FALSE, 0);
|
||||
gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->music_local));
|
||||
gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->music_radio));
|
||||
|
||||
koto_expander_set_content(m_expander, new_content);
|
||||
}
|
||||
|
@ -128,26 +132,23 @@ void koto_nav_create_music_section(KotoNav *self) {
|
|||
void koto_nav_create_podcasts_section(KotoNav *self) {
|
||||
KotoExpander *p_expander = koto_expander_new("microphone-sensitivity-high-symbolic", "Podcasts");
|
||||
self->podcast_expander = p_expander;
|
||||
gtk_box_pack_start(GTK_BOX(self->content), GTK_WIDGET(self->podcast_expander), FALSE, FALSE, 0);
|
||||
gtk_box_append(GTK_BOX(self->content), GTK_WIDGET(self->podcast_expander));
|
||||
|
||||
GtkWidget *new_content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
|
||||
self->podcasts_local = koto_button_new_plain("Library");
|
||||
self->podcasts_discover = koto_button_new_plain("Find New Podcasts");
|
||||
|
||||
gtk_box_pack_start(GTK_BOX(new_content), GTK_WIDGET(self->podcasts_local), FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(new_content), GTK_WIDGET(self->podcasts_discover), FALSE, FALSE, 0);
|
||||
gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->podcasts_local));
|
||||
gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->podcasts_discover));
|
||||
|
||||
koto_expander_set_content(p_expander, new_content);
|
||||
}
|
||||
|
||||
KotoNav* koto_nav_new(void) {
|
||||
return g_object_new(KOTO_TYPE_NAV,
|
||||
"hscrollbar-policy", GTK_POLICY_NEVER,
|
||||
"min-content-width", 300,
|
||||
"max-content-width", 300,
|
||||
"propagate-natural-height", TRUE,
|
||||
"propagate-natural-width", TRUE,
|
||||
"shadow-type", GTK_SHADOW_NONE
|
||||
,NULL);
|
||||
GtkWidget* koto_nav_get_nav(KotoNav *self) {
|
||||
return self->win;
|
||||
}
|
||||
|
||||
KotoNav* koto_nav_new(void) {
|
||||
return g_object_new(KOTO_TYPE_NAV ,NULL);
|
||||
}
|
||||
|
|
|
@ -16,17 +16,18 @@
|
|||
*/
|
||||
|
||||
#pragma once
|
||||
#include <gtk-3.0/gtk/gtk.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define KOTO_TYPE_NAV (koto_nav_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (KotoNav, koto_nav, KOTO, NAV, GtkScrolledWindow)
|
||||
G_DECLARE_FINAL_TYPE (KotoNav, koto_nav, KOTO, NAV, GObject)
|
||||
|
||||
KotoNav* koto_nav_new (void);
|
||||
void koto_nav_create_audiobooks_section(KotoNav *self);
|
||||
void koto_nav_create_music_section(KotoNav *self);
|
||||
void koto_nav_create_podcasts_section(KotoNav *self);
|
||||
GtkWidget* koto_nav_get_nav(KotoNav *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
|
|
@ -15,12 +15,14 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "koto-button.h"
|
||||
#include "koto-config.h"
|
||||
#include "koto-playerbar.h"
|
||||
|
||||
struct _KotoPlayerBar {
|
||||
GtkBox parent_instance;
|
||||
GObject parent_instance;
|
||||
GtkWidget *main;
|
||||
|
||||
/* Sections */
|
||||
GtkWidget *playback_section;
|
||||
|
@ -47,10 +49,10 @@ struct _KotoPlayerBar {
|
|||
};
|
||||
|
||||
struct _KotoPlayerBarClass {
|
||||
GtkBoxClass parent_class;
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoPlayerBar, koto_playerbar, GTK_TYPE_BOX);
|
||||
G_DEFINE_TYPE(KotoPlayerBar, koto_playerbar, G_TYPE_OBJECT);
|
||||
|
||||
static void koto_playerbar_constructed(GObject *obj);
|
||||
|
||||
|
@ -63,109 +65,111 @@ static void koto_playerbar_class_init(KotoPlayerBarClass *c) {
|
|||
|
||||
static void koto_playerbar_constructed(GObject *obj) {
|
||||
KotoPlayerBar *self = KOTO_PLAYERBAR(obj);
|
||||
self->main = gtk_center_box_new();
|
||||
gtk_center_box_set_baseline_position(GTK_CENTER_BOX(self->main), GTK_BASELINE_POSITION_CENTER);
|
||||
gtk_widget_add_css_class(self->main, "player-bar");
|
||||
|
||||
self->playback_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
self->primary_controls_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
self->secondary_controls_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
|
||||
gtk_box_pack_start(GTK_BOX(self), GTK_WIDGET(self->primary_controls_section), FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(self), GTK_WIDGET(self->playback_section), TRUE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(self), GTK_WIDGET(self->secondary_controls_section), FALSE, FALSE, 0);
|
||||
gtk_center_box_set_start_widget(GTK_CENTER_BOX(self->main), GTK_WIDGET(self->primary_controls_section));
|
||||
gtk_center_box_set_center_widget(GTK_CENTER_BOX(self->main), GTK_WIDGET(self->playback_section));
|
||||
gtk_center_box_set_end_widget(GTK_CENTER_BOX(self->main), GTK_WIDGET(self->secondary_controls_section));
|
||||
|
||||
koto_playerbar_create_playback_details(self);
|
||||
koto_playerbar_create_primary_controls(self);
|
||||
koto_playerbar_create_secondary_controls(self);
|
||||
|
||||
gtk_widget_set_margin_top(GTK_WIDGET(self), 10);
|
||||
gtk_widget_set_margin_bottom(GTK_WIDGET(self), 10);
|
||||
gtk_widget_set_margin_start(GTK_WIDGET(self), 10);
|
||||
gtk_widget_set_margin_end(GTK_WIDGET(self), 10);
|
||||
gtk_widget_set_hexpand(GTK_WIDGET(self->main), TRUE);
|
||||
}
|
||||
|
||||
static void koto_playerbar_init(KotoPlayerBar *self) {
|
||||
gtk_widget_show_all(GTK_WIDGET(self));
|
||||
(void) self;
|
||||
}
|
||||
|
||||
KotoPlayerBar* koto_playerbar_new(void) {
|
||||
return g_object_new(KOTO_TYPE_PLAYERBAR, "orientation", GTK_ORIENTATION_HORIZONTAL, NULL);
|
||||
return g_object_new(KOTO_TYPE_PLAYERBAR, NULL);
|
||||
}
|
||||
|
||||
void koto_playerbar_create_playback_details(KotoPlayerBar* bar) {
|
||||
bar->playback_details_section = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
|
||||
GtkIconTheme* default_icon_theme = gtk_icon_theme_get_default();
|
||||
GtkIconTheme *default_icon_theme = gtk_icon_theme_get_for_display(gdk_display_get_default()); // Get the icon theme for this display
|
||||
|
||||
if (default_icon_theme != NULL) {
|
||||
gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(bar));
|
||||
GdkPixbuf* audio_pixbuf = gtk_icon_theme_load_icon_for_scale(default_icon_theme, "audio-x-generic-symbolic", 96, scale_factor, GTK_ICON_LOOKUP_USE_BUILTIN & GTK_ICON_LOOKUP_FORCE_SIZE, NULL);
|
||||
GtkIconPaintable* audio_paintable = gtk_icon_theme_lookup_icon(default_icon_theme, "audio-x-generic-symbolic", NULL, 96, scale_factor, GTK_TEXT_DIR_NONE, GTK_ICON_LOOKUP_PRELOAD);
|
||||
|
||||
if (audio_pixbuf != NULL) {
|
||||
bar->artwork = gtk_image_new_from_pixbuf(audio_pixbuf);
|
||||
if (GTK_IS_ICON_PAINTABLE(audio_paintable)) {
|
||||
if (GTK_IS_IMAGE(bar->artwork)) {
|
||||
gtk_image_set_from_paintable(GTK_IMAGE(bar->artwork), GDK_PAINTABLE(audio_paintable));
|
||||
} else { // Not an image
|
||||
bar->artwork = gtk_image_new_from_paintable(GDK_PAINTABLE(audio_paintable));
|
||||
gtk_widget_set_size_request(bar->artwork, 96, 96);
|
||||
gtk_box_append(GTK_BOX(bar->playback_section), bar->artwork);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bar->artwork == NULL) {
|
||||
bar->artwork = gtk_image_new_from_icon_name("audio-x-generic-symbolic", GTK_ICON_SIZE_DIALOG);
|
||||
}
|
||||
|
||||
if (bar->artwork != NULL) {
|
||||
gtk_widget_set_margin_end(bar->artwork, 10);
|
||||
gtk_box_pack_start(GTK_BOX(bar->playback_section), bar->artwork, FALSE, FALSE, 0);
|
||||
}
|
||||
|
||||
bar->playback_title = gtk_label_new("Title");
|
||||
bar->playback_album = gtk_label_new("Album");
|
||||
bar->playback_artist = gtk_label_new("Artist");
|
||||
|
||||
gtk_box_pack_start(GTK_BOX(bar->playback_details_section), GTK_WIDGET(bar->playback_title), TRUE, TRUE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(bar->playback_details_section), GTK_WIDGET(bar->playback_album), TRUE, TRUE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(bar->playback_details_section), GTK_WIDGET(bar->playback_artist), TRUE, TRUE, 0);
|
||||
gtk_box_append(GTK_BOX(bar->playback_details_section), GTK_WIDGET(bar->playback_title));
|
||||
gtk_box_append(GTK_BOX(bar->playback_details_section), GTK_WIDGET(bar->playback_album));
|
||||
gtk_box_append(GTK_BOX(bar->playback_details_section), GTK_WIDGET(bar->playback_artist));
|
||||
|
||||
gtk_box_pack_end(GTK_BOX(bar->playback_section), GTK_WIDGET(bar->playback_details_section), FALSE, FALSE, 0);
|
||||
gtk_box_append(GTK_BOX(bar->playback_section), GTK_WIDGET(bar->playback_details_section));
|
||||
}
|
||||
|
||||
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->forward_button = koto_button_new_with_icon("", "media-skip-forward-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
|
||||
bar->back_button = koto_button_new_with_icon("", "media-skip-backward-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_SMALL);
|
||||
bar->play_pause_button = koto_button_new_with_icon("", "media-playback-start-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL); // 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_SMALL);
|
||||
|
||||
if (bar->back_button != NULL) {
|
||||
gtk_box_pack_start(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->back_button), FALSE, FALSE, 0);
|
||||
gtk_box_append(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->back_button));
|
||||
}
|
||||
|
||||
if (bar->play_pause_button != NULL) {
|
||||
gtk_box_pack_start(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->play_pause_button), FALSE, FALSE, 0);
|
||||
gtk_box_append(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->play_pause_button));
|
||||
}
|
||||
|
||||
if (bar->forward_button != NULL) {
|
||||
gtk_box_pack_start(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->forward_button), FALSE, FALSE, 0);
|
||||
gtk_box_append(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->forward_button));
|
||||
}
|
||||
}
|
||||
|
||||
void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar) {
|
||||
bar->repeat_button = koto_button_new_with_icon("", "media-playlist-repeat-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
|
||||
bar->shuffle_button = koto_button_new_with_icon("", "media-playlist-shuffle-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
|
||||
bar->playlist_button = koto_button_new_with_icon("", "playlist-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
|
||||
bar->eq_button = koto_button_new_with_icon("", "multimedia-equalizer-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
|
||||
bar->repeat_button = koto_button_new_with_icon("", "media-playlist-repeat-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_SMALL);
|
||||
bar->shuffle_button = koto_button_new_with_icon("", "media-playlist-shuffle-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_SMALL);
|
||||
bar->playlist_button = koto_button_new_with_icon("", "playlist-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_SMALL);
|
||||
bar->eq_button = koto_button_new_with_icon("", "multimedia-equalizer-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_SMALL);
|
||||
bar->volume_button = gtk_volume_button_new(); // Have this take in a state and switch to a different icon if necessary
|
||||
gtk_scale_button_set_value(GTK_SCALE_BUTTON(bar->volume_button), 0.5);
|
||||
|
||||
if (bar->repeat_button != NULL) {
|
||||
gtk_box_pack_start(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->repeat_button), FALSE, FALSE, 0);
|
||||
gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->repeat_button));
|
||||
}
|
||||
|
||||
if (bar->shuffle_button != NULL) {
|
||||
gtk_box_pack_start(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->shuffle_button), FALSE, FALSE, 0);
|
||||
gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->shuffle_button));
|
||||
}
|
||||
|
||||
if (bar->playlist_button != NULL) {
|
||||
gtk_box_pack_start(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->playlist_button), FALSE, FALSE, 0);
|
||||
gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->playlist_button));
|
||||
}
|
||||
|
||||
if (bar->eq_button != NULL) {
|
||||
gtk_box_pack_start(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->eq_button), FALSE, FALSE, 0);
|
||||
gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->eq_button));
|
||||
}
|
||||
|
||||
if (bar->volume_button != NULL) {
|
||||
gtk_box_pack_start(GTK_BOX(bar->secondary_controls_section), bar->volume_button, FALSE, FALSE, 0);
|
||||
gtk_box_append(GTK_BOX(bar->secondary_controls_section), bar->volume_button);
|
||||
}
|
||||
}
|
||||
|
||||
GtkWidget* koto_playerbar_get_main(KotoPlayerBar* bar) {
|
||||
return bar->main;
|
||||
}
|
||||
|
|
|
@ -16,15 +16,16 @@
|
|||
*/
|
||||
|
||||
#pragma once
|
||||
#include <gtk-3.0/gtk/gtk.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define KOTO_TYPE_PLAYERBAR (koto_playerbar_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (KotoPlayerBar, koto_playerbar, KOTO, PLAYERBAR, GtkBox)
|
||||
G_DECLARE_FINAL_TYPE (KotoPlayerBar, koto_playerbar, KOTO, PLAYERBAR, GObject)
|
||||
|
||||
KotoPlayerBar* koto_playerbar_new (void);
|
||||
GtkWidget* koto_playerbar_get_main(KotoPlayerBar* bar);
|
||||
void koto_playerbar_create_playback_details(KotoPlayerBar* bar);
|
||||
void koto_playerbar_create_primary_controls(KotoPlayerBar* bar);
|
||||
void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar);
|
||||
|
|
120
src/koto-track-item.c
Normal file
120
src/koto-track-item.c
Normal file
|
@ -0,0 +1,120 @@
|
|||
/* koto-track-item.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 <gtk-4.0/gtk/gtk.h>
|
||||
#include "koto-button.h"
|
||||
#include "koto-track-item.h"
|
||||
|
||||
struct _KotoTrackItem {
|
||||
GtkBox parent_instance;
|
||||
KotoIndexedFile *track;
|
||||
|
||||
GtkWidget *track_label;
|
||||
KotoButton *add_to_playlist_button;
|
||||
};
|
||||
|
||||
struct _KotoTrackItemClass {
|
||||
GtkBoxClass parent_class;
|
||||
};
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_TRACK,
|
||||
N_PROPERTIES
|
||||
};
|
||||
|
||||
static GParamSpec *props[N_PROPERTIES] = { NULL, };
|
||||
|
||||
G_DEFINE_TYPE(KotoTrackItem, koto_track_item, GTK_TYPE_BOX);
|
||||
|
||||
static void koto_track_item_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec);
|
||||
static void koto_track_item_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec);
|
||||
|
||||
static void koto_track_item_class_init(KotoTrackItemClass *c) {
|
||||
GObjectClass *gobject_class;
|
||||
gobject_class = G_OBJECT_CLASS(c);
|
||||
gobject_class->set_property = koto_track_item_set_property;
|
||||
gobject_class->get_property = koto_track_item_get_property;
|
||||
|
||||
props[PROP_TRACK] = g_param_spec_object(
|
||||
"track",
|
||||
"Track",
|
||||
"Track",
|
||||
KOTO_TYPE_INDEXED_FILE,
|
||||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
|
||||
}
|
||||
|
||||
static void koto_track_item_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) {
|
||||
KotoTrackItem *self = KOTO_TRACK_ITEM(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_TRACK:
|
||||
g_value_set_object(val, self->track);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void koto_track_item_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec) {
|
||||
KotoTrackItem *self = KOTO_TRACK_ITEM(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_TRACK:
|
||||
koto_track_item_set_track(self, (KotoIndexedFile*) g_value_get_object(val));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void koto_track_item_init(KotoTrackItem *self) {
|
||||
self->track_label = gtk_label_new(NULL); // Create with no track name
|
||||
gtk_label_set_xalign(GTK_LABEL(self->track_label), 0.0);
|
||||
|
||||
self->add_to_playlist_button = koto_button_new_with_icon(NULL, "playlist-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_TINY);
|
||||
|
||||
gtk_widget_set_hexpand(GTK_WIDGET(self), TRUE);
|
||||
gtk_widget_set_hexpand(GTK_WIDGET(self->track_label), TRUE);
|
||||
|
||||
gtk_box_prepend(GTK_BOX(self), self->track_label);
|
||||
gtk_box_append(GTK_BOX(self), GTK_WIDGET(self->add_to_playlist_button));
|
||||
}
|
||||
|
||||
void koto_track_item_set_track(KotoTrackItem *self, KotoIndexedFile *file) {
|
||||
if (file == NULL) { // Not a file
|
||||
return;
|
||||
}
|
||||
|
||||
self->track = file;
|
||||
gchar *track_name;
|
||||
g_object_get(self->track, "parsed-name", &track_name, NULL);
|
||||
gtk_label_set_text(GTK_LABEL(self->track_label), track_name); // Update the text
|
||||
}
|
||||
|
||||
KotoTrackItem* koto_track_item_new(KotoIndexedFile *file) {
|
||||
return g_object_new(KOTO_TYPE_TRACK_ITEM,
|
||||
"track",
|
||||
file,
|
||||
NULL
|
||||
);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
/* koto-headerbar.h
|
||||
/* koto-track-item.h
|
||||
*
|
||||
* Copyright 2021 Joshua Strobl
|
||||
*
|
||||
|
@ -17,14 +17,17 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <gtk-3.0/gtk/gtk.h>
|
||||
#include <glib-2.0/glib-object.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "indexer/file.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define KOTO_TYPE_HEADERBAR (koto_headerbar_get_type())
|
||||
#define KOTO_TYPE_TRACK_ITEM (koto_track_item_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (KotoHeaderBar, koto_headerbar, KOTO, HEADERBAR, GtkHeaderBar)
|
||||
G_DECLARE_FINAL_TYPE(KotoTrackItem, koto_track_item, KOTO, TRACK_ITEM, GtkBox)
|
||||
|
||||
KotoHeaderBar* koto_headerbar_new (void);
|
||||
KotoTrackItem* koto_track_item_new(KotoIndexedFile *file);
|
||||
void koto_track_item_set_track(KotoTrackItem *self, KotoIndexedFile *file);
|
||||
|
||||
G_END_DECLS
|
|
@ -16,6 +16,26 @@
|
|||
*/
|
||||
|
||||
#include <glib-2.0/glib.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
|
||||
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 (g_file_test(filepath, G_FILE_TEST_EXISTS)) { // File exists
|
||||
image = gtk_image_new_from_file(filepath); // Load from the filepath
|
||||
}
|
||||
}
|
||||
|
||||
if (!GTK_IS_IMAGE(image)) { // If we failed to get the image or never passed a valid filepath to begin with
|
||||
image = gtk_image_new_from_icon_name(fallback_icon); // Set to the fallback icon
|
||||
}
|
||||
|
||||
gtk_image_set_icon_size(GTK_IMAGE(image), GTK_ICON_SIZE_INHERIT);
|
||||
gtk_widget_set_size_request(image, width, height);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
gchar* koto_utils_get_filename_without_extension(gchar *filename) {
|
||||
gchar *trimmed_file_name = g_strdup(filename);
|
||||
|
@ -42,7 +62,7 @@ gchar* koto_utils_get_filename_without_extension(gchar *filename) {
|
|||
g_free(new_parsed_name);
|
||||
}
|
||||
|
||||
gchar *stripped_file_name = g_strstrip(trimmed_file_name); // Strip leading and trailing whitespace
|
||||
gchar *stripped_file_name = g_strstrip(g_strdup(trimmed_file_name)); // Strip leading and trailing whitespace
|
||||
g_free(trimmed_file_name);
|
||||
return stripped_file_name;
|
||||
}
|
||||
|
|
|
@ -17,9 +17,11 @@
|
|||
|
||||
#pragma once
|
||||
#include <glib-2.0/glib.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
GtkWidget* koto_utils_create_image_from_filepath(gchar *filepath, gchar *fallback_icon, guint width, guint height);
|
||||
gchar* koto_utils_get_filename_without_extension(gchar *filename);
|
||||
|
||||
G_END_DECLS
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
*/
|
||||
|
||||
#include "indexer/file-indexer.h"
|
||||
#include "pages/music/music-local.h"
|
||||
#include "koto-config.h"
|
||||
#include "koto-headerbar.h"
|
||||
#include "koto-nav.h"
|
||||
#include "koto-playerbar.h"
|
||||
#include "koto-window.h"
|
||||
|
@ -25,60 +25,90 @@
|
|||
struct _KotoWindow {
|
||||
GtkApplicationWindow parent_instance;
|
||||
KotoIndexedLibrary *library;
|
||||
KotoHeaderBar *header_bar;
|
||||
|
||||
GtkWidget *header_bar;
|
||||
GtkWidget *menu_button;
|
||||
GtkWidget *search_entry;
|
||||
|
||||
GtkWidget *primary_layout;
|
||||
GtkWidget *content_layout;
|
||||
|
||||
KotoNav *nav;
|
||||
GtkWidget *pages;
|
||||
KotoPlayerBar *player_bar;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (KotoWindow, koto_window, GTK_TYPE_APPLICATION_WINDOW)
|
||||
|
||||
static void koto_window_class_init (KotoWindowClass *klass) {
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
(void)klass;
|
||||
}
|
||||
|
||||
static void koto_window_init (KotoWindow *self) {
|
||||
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_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
// TODO: Change 900 back to GTK_STYLE_PROVIDER_PRIORITY_APPLICATION so users are allowed to override the style.
|
||||
// We want them to be able to override it. It's their system, style it how they want. We just need this to test our own currently.
|
||||
gtk_style_context_add_provider_for_display(gdk_display_get_default(), GTK_STYLE_PROVIDER(provider), 900);
|
||||
|
||||
//KotoHeaderBar *header = koto_headerbar_new();
|
||||
self->header_bar = koto_headerbar_new();
|
||||
|
||||
if (self->header_bar != NULL) {
|
||||
gtk_window_set_titlebar(GTK_WINDOW(self), GTK_WIDGET(self->header_bar));
|
||||
}
|
||||
create_new_headerbar(self); // Create our headerbar
|
||||
|
||||
self->primary_layout = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_widget_set_hexpand(self->primary_layout, TRUE);
|
||||
gtk_widget_set_vexpand(self->primary_layout, TRUE);
|
||||
|
||||
self->content_layout = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
gtk_widget_set_hexpand(self->content_layout, TRUE);
|
||||
gtk_widget_set_vexpand(self->content_layout, TRUE);
|
||||
|
||||
self->nav = koto_nav_new();
|
||||
|
||||
if (self->nav != NULL) {
|
||||
gtk_box_pack_start(GTK_BOX(self->content_layout), GTK_WIDGET(self->nav), FALSE, TRUE, 10);
|
||||
|
||||
GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
|
||||
gtk_box_pack_end(GTK_BOX(self->content_layout), sep, FALSE, TRUE, 0);
|
||||
gtk_box_prepend(GTK_BOX(self->content_layout), koto_nav_get_nav(self->nav));
|
||||
}
|
||||
|
||||
gtk_box_pack_start(GTK_BOX(self->primary_layout), self->content_layout, TRUE, TRUE, 0);
|
||||
self->pages = gtk_stack_new(); // New stack to hold our pages
|
||||
|
||||
if (GTK_IS_STACK(self->pages)) { // Created our stack successfully
|
||||
gtk_stack_set_transition_type(GTK_STACK(self->pages), GTK_STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT);
|
||||
gtk_box_append(GTK_BOX(self->content_layout), self->pages);
|
||||
}
|
||||
|
||||
gtk_box_prepend(GTK_BOX(self->primary_layout), self->content_layout);
|
||||
|
||||
self->player_bar = koto_playerbar_new();
|
||||
|
||||
if (self->player_bar != NULL) {
|
||||
gtk_box_pack_start(GTK_BOX(self->primary_layout), GTK_WIDGET(self->player_bar), FALSE, FALSE, 0);
|
||||
GtkWidget *playerbar_main = koto_playerbar_get_main(self->player_bar);
|
||||
gtk_box_append(GTK_BOX(self->primary_layout), playerbar_main);
|
||||
}
|
||||
|
||||
gtk_container_add(GTK_CONTAINER(self), self->primary_layout);
|
||||
gtk_window_set_child(GTK_WINDOW(self), self->primary_layout);
|
||||
gtk_widget_set_size_request(GTK_WIDGET(self), 1200, 675);
|
||||
gtk_window_set_title(GTK_WINDOW(self), "Koto");
|
||||
gtk_window_set_wmclass(GTK_WINDOW(self), "com.github.joshstrobl.koto", "com.github.joshstrobl.koto");
|
||||
gtk_window_set_icon_name(GTK_WINDOW(self), "audio-headphones");
|
||||
gtk_window_set_startup_id(GTK_WINDOW(self), "com.github.joshstrobl.koto");
|
||||
|
||||
gtk_widget_show_all(GTK_WIDGET(self));
|
||||
g_thread_new("load-library", load_library, self);
|
||||
g_thread_new("load-library", (void*) load_library, self);
|
||||
}
|
||||
|
||||
void create_new_headerbar(KotoWindow *self) {
|
||||
self->header_bar = gtk_header_bar_new();
|
||||
g_return_if_fail(GTK_IS_HEADER_BAR(self->header_bar));
|
||||
|
||||
self->menu_button = gtk_button_new_from_icon_name("audio-headphones");
|
||||
|
||||
self->search_entry = gtk_search_entry_new();
|
||||
gtk_widget_add_css_class(self->menu_button, "flat");
|
||||
gtk_widget_set_can_focus(self->search_entry, TRUE);
|
||||
gtk_widget_set_size_request(self->search_entry, 400, -1); // Have 400px width
|
||||
g_object_set(self->search_entry, "placeholder-text", "Search...", NULL);
|
||||
|
||||
gtk_header_bar_pack_start(GTK_HEADER_BAR(self->header_bar), self->menu_button);
|
||||
gtk_header_bar_set_show_title_buttons(GTK_HEADER_BAR(self->header_bar), TRUE);
|
||||
gtk_header_bar_set_title_widget(GTK_HEADER_BAR(self->header_bar), self->search_entry);
|
||||
|
||||
gtk_window_set_titlebar(GTK_WINDOW(self), self->header_bar);
|
||||
}
|
||||
|
||||
void load_library(KotoWindow *self) {
|
||||
|
@ -86,5 +116,13 @@ void load_library(KotoWindow *self) {
|
|||
|
||||
if (lib != NULL) {
|
||||
self->library = lib;
|
||||
KotoPageMusicLocal* l = koto_page_music_local_new();
|
||||
|
||||
if (GTK_IS_WIDGET(l)) { // Created our local library page
|
||||
koto_page_music_local_set_library(l, self->library);
|
||||
gtk_stack_add_named(GTK_STACK(self->pages), GTK_WIDGET(l), "music.local");
|
||||
// TODO: Remove and do some fancy state loading
|
||||
gtk_stack_set_visible_child_name(GTK_STACK(self->pages), "music.local");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <gtk-3.0/gtk/gtk.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
|
@ -27,4 +27,5 @@ G_DECLARE_FINAL_TYPE (KotoWindow, koto_window, KOTO, WINDOW, GtkApplicationWindo
|
|||
|
||||
G_END_DECLS
|
||||
|
||||
void create_new_headerbar(KotoWindow *self);
|
||||
void load_library(KotoWindow *self);
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
add_project_arguments('-Db_sanitize=address', language: 'c')
|
||||
|
||||
koto_sources = [
|
||||
'indexer/album.c',
|
||||
'indexer/artist.c',
|
||||
'indexer/file.c',
|
||||
'indexer/file-indexer.c',
|
||||
'pages/music/album-view.c',
|
||||
'pages/music/artist-view.c',
|
||||
'pages/music/music-local.c',
|
||||
'main.c',
|
||||
'koto-button.c',
|
||||
'koto-expander.c',
|
||||
'koto-headerbar.c',
|
||||
'koto-nav.c',
|
||||
'koto-playerbar.c',
|
||||
'koto-track-item.c',
|
||||
'koto-utils.c',
|
||||
'koto-window.c',
|
||||
]
|
||||
|
@ -16,7 +21,7 @@ koto_sources = [
|
|||
koto_deps = [
|
||||
dependency('glib-2.0', version: '>= 2.66'),
|
||||
dependency('gio-2.0', version: '>= 2.66'),
|
||||
dependency('gtk+-3.0', version: '>= 3.24'),
|
||||
dependency('gtk4', version: '>= 4.0'),
|
||||
dependency('libmagic', version: '>=5.39'),
|
||||
dependency('taglib_c', version: '>=1.11'),
|
||||
]
|
||||
|
|
169
src/pages/music/album-view.c
Normal file
169
src/pages/music/album-view.c
Normal file
|
@ -0,0 +1,169 @@
|
|||
/* album-view.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 <gtk-4.0/gtk/gtk.h>
|
||||
#include "../../indexer/album.h"
|
||||
#include "../../indexer/artist.h"
|
||||
#include "../../koto-track-item.h"
|
||||
#include "album-view.h"
|
||||
#include "koto-config.h"
|
||||
#include "koto-utils.h"
|
||||
|
||||
struct _KotoAlbumView {
|
||||
GObject parent_instance;
|
||||
KotoIndexedAlbum *album;
|
||||
GtkWidget *main;
|
||||
GtkWidget *album_tracks_box;
|
||||
GtkWidget *tracks;
|
||||
|
||||
GtkWidget *album_label;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoAlbumView, koto_album_view, G_TYPE_OBJECT);
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_ALBUM,
|
||||
N_PROPERTIES
|
||||
};
|
||||
|
||||
static GParamSpec *props[N_PROPERTIES] = { NULL, };
|
||||
static void koto_album_view_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec);
|
||||
static void koto_album_view_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec);
|
||||
|
||||
static void koto_album_view_class_init(KotoAlbumViewClass *c) {
|
||||
GObjectClass *gobject_class;
|
||||
gobject_class = G_OBJECT_CLASS(c);
|
||||
gobject_class->set_property = koto_album_view_set_property;
|
||||
gobject_class->get_property = koto_album_view_get_property;
|
||||
|
||||
props[PROP_ALBUM] = g_param_spec_object(
|
||||
"album",
|
||||
"Album",
|
||||
"Album",
|
||||
KOTO_TYPE_INDEXED_ALBUM,
|
||||
G_PARAM_CONSTRUCT_ONLY|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
|
||||
}
|
||||
|
||||
static void koto_album_view_init(KotoAlbumView *self) {
|
||||
self->main = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
gtk_widget_set_can_focus(self->main, FALSE);
|
||||
self->album_tracks_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
|
||||
self->tracks = gtk_list_box_new(); // Create our list of our tracks
|
||||
gtk_list_box_set_sort_func(GTK_LIST_BOX(self->tracks), koto_album_view_sort_tracks, NULL, NULL); // Ensure we can sort our tracks
|
||||
gtk_widget_set_size_request(self->tracks, 600, -1);
|
||||
|
||||
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->tracks); // Add the tracks list box to the albums tracks box
|
||||
}
|
||||
|
||||
GtkWidget* koto_album_view_get_main(KotoAlbumView *self) {
|
||||
return self->main;
|
||||
}
|
||||
|
||||
static void koto_album_view_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) {
|
||||
KotoAlbumView *self = KOTO_ALBUM_VIEW(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_ALBUM:
|
||||
g_value_set_object(val, self->album);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void koto_album_view_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec) {
|
||||
KotoAlbumView *self = KOTO_ALBUM_VIEW(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_ALBUM:
|
||||
koto_album_view_set_album(self, (KotoIndexedAlbum*) g_value_get_object(val));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void koto_album_view_set_album(KotoAlbumView *self, KotoIndexedAlbum *album) {
|
||||
if (album == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
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_box_prepend(GTK_BOX(self->main), art_image); // Prepend the image to the art info box
|
||||
|
||||
gchar *album_name;
|
||||
g_object_get(album, "name", &album_name, NULL); // Get the album name
|
||||
|
||||
self->album_label = gtk_label_new(album_name);
|
||||
gtk_box_prepend(GTK_BOX(self->album_tracks_box), self->album_label); // Prepend our new label to the album + tracks box
|
||||
|
||||
GList *t;
|
||||
for (t = koto_indexed_album_get_files(self->album); t != NULL; t = t->next) { // For each file / track
|
||||
KotoIndexedFile *file = (KotoIndexedFile*) t->data;
|
||||
KotoTrackItem *track_item = koto_track_item_new(file); // Create our new track item
|
||||
gtk_list_box_prepend(GTK_LIST_BOX(self->tracks), GTK_WIDGET(track_item)); // Add to our tracks list box
|
||||
}
|
||||
}
|
||||
|
||||
int koto_album_view_sort_tracks(GtkListBoxRow *track1, GtkListBoxRow *track2, gpointer user_data) {
|
||||
(void) user_data;
|
||||
KotoTrackItem *track1_item = KOTO_TRACK_ITEM(gtk_list_box_row_get_child(track1));
|
||||
KotoTrackItem *track2_item = KOTO_TRACK_ITEM(gtk_list_box_row_get_child(track2));
|
||||
|
||||
KotoIndexedFile *track1_file;
|
||||
KotoIndexedFile *track2_file;
|
||||
|
||||
g_object_get(track1_item, "track", &track1_file, NULL);
|
||||
g_object_get(track2_item, "track", &track2_file, NULL);
|
||||
|
||||
guint16 *track1_pos;
|
||||
guint16 *track2_pos;
|
||||
|
||||
g_object_get(track1_file, "position", &track1_pos, NULL);
|
||||
g_object_get(track2_file, "position", &track2_pos, NULL);
|
||||
|
||||
if (track1_pos == track2_pos) { // Identical positions (like reported as 0)
|
||||
gchar *track1_name;
|
||||
gchar *track2_name;
|
||||
|
||||
g_object_get(track1_file, "parsed-name", &track1_name, NULL);
|
||||
g_object_get(track2_file, "parsed-name", &track2_name, NULL);
|
||||
|
||||
return g_utf8_collate(track1_name, track2_name);
|
||||
} else if (track1_pos < track2_pos) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
KotoAlbumView* koto_album_view_new(KotoIndexedAlbum *album) {
|
||||
return g_object_new(KOTO_TYPE_ALBUM_VIEW, "album", album, NULL);
|
||||
}
|
36
src/pages/music/album-view.h
Normal file
36
src/pages/music/album-view.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/* album-view.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 <gtk-4.0/gtk/gtk.h>
|
||||
#include "../../indexer/album.h"
|
||||
#include "../../indexer/artist.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define KOTO_TYPE_ALBUM_VIEW (koto_album_view_get_type())
|
||||
|
||||
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_set_album(KotoAlbumView *self, KotoIndexedAlbum *album);
|
||||
int koto_album_view_sort_tracks(GtkListBoxRow *track1, GtkListBoxRow *track2, gpointer user_data);
|
||||
|
||||
G_END_DECLS
|
167
src/pages/music/artist-view.c
Normal file
167
src/pages/music/artist-view.c
Normal file
|
@ -0,0 +1,167 @@
|
|||
/* artist-view.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 <gtk-4.0/gtk/gtk.h>
|
||||
#include "../../indexer/album.h"
|
||||
#include "../../indexer/artist.h"
|
||||
#include "album-view.h"
|
||||
#include "artist-view.h"
|
||||
#include "koto-config.h"
|
||||
#include "koto-utils.h"
|
||||
|
||||
struct _KotoArtistView {
|
||||
GObject parent_instance;
|
||||
KotoIndexedArtist *artist;
|
||||
GtkWidget *scrolled_window;
|
||||
GtkWidget *content;
|
||||
GtkWidget *albums_strip;
|
||||
GtkWidget *album_list;
|
||||
|
||||
GHashTable *albums_to_component;
|
||||
|
||||
gboolean constructed;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoArtistView, koto_artist_view, G_TYPE_OBJECT);
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_ARTIST,
|
||||
N_PROPERTIES
|
||||
};
|
||||
|
||||
static GParamSpec *props[N_PROPERTIES] = { NULL, };
|
||||
static void koto_artist_view_constructed(GObject *obj);
|
||||
static void koto_artist_view_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec);
|
||||
static void koto_artist_view_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec);
|
||||
|
||||
static void koto_artist_view_class_init(KotoArtistViewClass *c) {
|
||||
GObjectClass *gobject_class;
|
||||
gobject_class = G_OBJECT_CLASS(c);
|
||||
gobject_class->constructed = koto_artist_view_constructed;
|
||||
gobject_class->set_property = koto_artist_view_set_property;
|
||||
gobject_class->get_property = koto_artist_view_get_property;
|
||||
|
||||
props[PROP_ARTIST] = g_param_spec_object(
|
||||
"artist",
|
||||
"Artist",
|
||||
"Artist",
|
||||
KOTO_TYPE_INDEXED_ARTIST,
|
||||
G_PARAM_CONSTRUCT_ONLY|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
|
||||
}
|
||||
|
||||
static void koto_artist_view_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) {
|
||||
KotoArtistView *self = KOTO_ARTIST_VIEW(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_ARTIST:
|
||||
g_value_set_object(val, self->artist);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void koto_artist_view_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec) {
|
||||
KotoArtistView *self = KOTO_ARTIST_VIEW(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_ARTIST:
|
||||
koto_artist_view_add_artist(self, (KotoIndexedArtist*) g_value_get_object(val));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void koto_artist_view_init(KotoArtistView *self) {
|
||||
self->artist = NULL;
|
||||
self->constructed = FALSE;
|
||||
}
|
||||
|
||||
static void koto_artist_view_constructed(GObject *obj) {
|
||||
KotoArtistView *self = KOTO_ARTIST_VIEW(obj);
|
||||
self->albums_to_component = g_hash_table_new(g_str_hash, g_str_equal);
|
||||
self->scrolled_window = gtk_scrolled_window_new(); // Create our scrolled window
|
||||
self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); // Create our content as a GtkBox
|
||||
|
||||
gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(self->scrolled_window), TRUE);
|
||||
gtk_scrolled_window_set_propagate_natural_width(GTK_SCROLLED_WINDOW(self->scrolled_window), TRUE);
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->scrolled_window), self->content); // Add the content as the widget for the scrolled window
|
||||
|
||||
self->albums_strip = gtk_flow_box_new(); // Create our album strip
|
||||
gtk_flow_box_set_activate_on_single_click(GTK_FLOW_BOX(self->albums_strip), TRUE);
|
||||
gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->albums_strip), GTK_SELECTION_NONE);
|
||||
|
||||
self->album_list = gtk_flow_box_new(); // Create our list of albums as a flow box
|
||||
gtk_flow_box_set_activate_on_single_click(GTK_FLOW_BOX(self->album_list), FALSE);
|
||||
gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->album_list), GTK_SELECTION_NONE);
|
||||
|
||||
gtk_box_prepend(GTK_BOX(self->content), self->albums_strip); // Add the strip
|
||||
gtk_box_append(GTK_BOX(self->content), self->album_list); // Add the list
|
||||
|
||||
gtk_widget_set_hexpand(GTK_WIDGET(self->albums_strip), TRUE);
|
||||
gtk_widget_set_hexpand(GTK_WIDGET(self->album_list), TRUE);
|
||||
|
||||
G_OBJECT_CLASS (koto_artist_view_parent_class)->constructed (obj);
|
||||
self->constructed = TRUE;
|
||||
}
|
||||
|
||||
void koto_artist_view_add_album(KotoArtistView *self, KotoIndexedAlbum *album) {
|
||||
gchar *album_art = koto_indexed_album_get_album_art(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_flow_box_insert(GTK_FLOW_BOX(self->albums_strip), art_image, -1); // Append the album art
|
||||
|
||||
KotoAlbumView* album_view = koto_album_view_new(album); // Create our new album view
|
||||
GtkWidget* album_view_main = koto_album_view_get_main(album_view);
|
||||
gtk_flow_box_insert(GTK_FLOW_BOX(self->album_list), album_view_main, -1); // Append the album view to the album list
|
||||
}
|
||||
|
||||
void koto_artist_view_add_artist(KotoArtistView *self, KotoIndexedArtist *artist) {
|
||||
if (artist == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->artist = artist;
|
||||
|
||||
if (!self->constructed) {
|
||||
return;
|
||||
}
|
||||
|
||||
GList *albums = koto_indexed_artist_get_albums(self->artist); // Get the albums
|
||||
|
||||
GList *a;
|
||||
for (a = albums; a != NULL; a = a->next) {
|
||||
KotoIndexedAlbum *album = (KotoIndexedAlbum*) a->data;
|
||||
koto_artist_view_add_album(self, album); // Add the album
|
||||
}
|
||||
}
|
||||
|
||||
GtkWidget* koto_artist_view_get_main(KotoArtistView *self) {
|
||||
return self->scrolled_window;
|
||||
}
|
||||
|
||||
KotoArtistView* koto_artist_view_new() {
|
||||
return g_object_new(KOTO_TYPE_ARTIST_VIEW, NULL);
|
||||
}
|
36
src/pages/music/artist-view.h
Normal file
36
src/pages/music/artist-view.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/* artist-view.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 <gtk-4.0/gtk/gtk.h>
|
||||
#include "../../indexer/album.h"
|
||||
#include "../../indexer/artist.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define KOTO_TYPE_ARTIST_VIEW (koto_artist_view_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (KotoArtistView, koto_artist_view, KOTO, ARTIST_VIEW, GObject)
|
||||
|
||||
KotoArtistView* koto_artist_view_new();
|
||||
void koto_artist_view_add_album(KotoArtistView *self, KotoIndexedAlbum *album);
|
||||
void koto_artist_view_add_artist(KotoArtistView *self, KotoIndexedArtist *artist);
|
||||
GtkWidget* koto_artist_view_get_main(KotoArtistView *self);
|
||||
|
||||
G_END_DECLS
|
227
src/pages/music/music-local.c
Normal file
227
src/pages/music/music-local.c
Normal file
|
@ -0,0 +1,227 @@
|
|||
/* music-local.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 <gtk-4.0/gtk/gtk.h>
|
||||
#include "../../indexer/file-indexer.h"
|
||||
#include "koto-button.h"
|
||||
#include "koto-config.h"
|
||||
#include "music-local.h"
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_LIB,
|
||||
N_PROPERTIES
|
||||
};
|
||||
|
||||
static GParamSpec *props[N_PROPERTIES] = { NULL, };
|
||||
|
||||
struct _KotoPageMusicLocal {
|
||||
GtkBox parent_instance;
|
||||
GtkWidget *scrolled_window;
|
||||
GtkWidget *artist_list;
|
||||
GtkWidget *stack;
|
||||
|
||||
KotoIndexedLibrary *lib;
|
||||
gboolean constructed;
|
||||
};
|
||||
|
||||
struct _KotoPageMusicLocalClass {
|
||||
GtkBoxClass parent_class;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoPageMusicLocal, koto_page_music_local, GTK_TYPE_BOX);
|
||||
|
||||
static void koto_page_music_local_constructed(GObject *obj);
|
||||
static void koto_page_music_local_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec);
|
||||
static void koto_page_music_local_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec);
|
||||
|
||||
static void koto_page_music_local_class_init(KotoPageMusicLocalClass *c) {
|
||||
GObjectClass *gobject_class;
|
||||
gobject_class = G_OBJECT_CLASS(c);
|
||||
gobject_class->constructed = koto_page_music_local_constructed;
|
||||
gobject_class->set_property = koto_page_music_local_set_property;
|
||||
gobject_class->get_property = koto_page_music_local_get_property;
|
||||
|
||||
props[PROP_LIB] = g_param_spec_object(
|
||||
"lib",
|
||||
"Library",
|
||||
"Library",
|
||||
KOTO_TYPE_INDEXED_LIBRARY,
|
||||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
|
||||
}
|
||||
|
||||
static void koto_page_music_local_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) {
|
||||
KotoPageMusicLocal *self = KOTO_PAGE_MUSIC_LOCAL(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_LIB:
|
||||
g_value_set_object(val, self->lib);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void koto_page_music_local_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec) {
|
||||
KotoPageMusicLocal *self = KOTO_PAGE_MUSIC_LOCAL(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_LIB:
|
||||
koto_page_music_local_set_library(self, (KotoIndexedLibrary*) g_value_get_object(val));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void koto_page_music_local_init(KotoPageMusicLocal *self) {
|
||||
self->lib = NULL;
|
||||
self->constructed = FALSE;
|
||||
}
|
||||
|
||||
static void koto_page_music_local_constructed(GObject *obj) {
|
||||
KotoPageMusicLocal *self = KOTO_PAGE_MUSIC_LOCAL(obj);
|
||||
gtk_widget_add_css_class(GTK_WIDGET(self), "page-music-local");
|
||||
gtk_widget_set_hexpand(GTK_WIDGET(self), TRUE);
|
||||
|
||||
G_OBJECT_CLASS (koto_page_music_local_parent_class)->constructed (obj);
|
||||
self->constructed = TRUE;
|
||||
}
|
||||
|
||||
void koto_page_music_local_add_artist(KotoPageMusicLocal *self, KotoIndexedArtist *artist) {
|
||||
gchar *artist_name;
|
||||
g_object_get(artist, "name", &artist_name, NULL);
|
||||
KotoButton *artist_button = koto_button_new_plain(artist_name);
|
||||
gtk_list_box_prepend(GTK_LIST_BOX(self->artist_list), GTK_WIDGET(artist_button));
|
||||
|
||||
KotoArtistView *artist_view = koto_artist_view_new(); // Create our new artist view
|
||||
koto_artist_view_add_artist(artist_view, artist); // Add the artist
|
||||
gtk_stack_add_named(GTK_STACK(self->stack), koto_artist_view_get_main(artist_view), artist_name);
|
||||
|
||||
GtkGesture *controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(controller, "pressed", G_CALLBACK(koto_page_music_local_handle_artist_click), self);
|
||||
gtk_widget_add_controller(GTK_WIDGET(artist_button), GTK_EVENT_CONTROLLER(controller));
|
||||
}
|
||||
|
||||
void koto_page_music_local_handle_artist_click(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data) {
|
||||
(void) n_press; (void) x; (void) y;
|
||||
KotoPageMusicLocal *self = (KotoPageMusicLocal*) data;
|
||||
GtkWidget *btn_widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture)); // Get the widget that applied to this gesture
|
||||
KotoButton *btn = KOTO_BUTTON(btn_widget);
|
||||
|
||||
gchar *artist_name;
|
||||
g_object_get(btn, "button-text", &artist_name, NULL);
|
||||
gtk_stack_set_visible_child_name(GTK_STACK(self->stack), artist_name);
|
||||
}
|
||||
|
||||
void koto_page_music_local_set_library(KotoPageMusicLocal *self, KotoIndexedLibrary *lib) {
|
||||
if (lib == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->lib != NULL) { // If lib is already set
|
||||
g_free(self->lib);
|
||||
}
|
||||
|
||||
self->lib = lib;
|
||||
|
||||
if (!self->constructed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GTK_IS_SCROLLED_WINDOW(self->scrolled_window)) {
|
||||
self->scrolled_window = gtk_scrolled_window_new();
|
||||
gtk_widget_add_css_class(self->scrolled_window, "artist-list");
|
||||
gtk_box_prepend(GTK_BOX(self), self->scrolled_window);
|
||||
}
|
||||
|
||||
if (GTK_IS_LIST_BOX(self->artist_list)) { // artist list is a list box
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->scrolled_window), NULL); // Set to null to maybe clear?
|
||||
g_object_unref(self->artist_list); // Unref
|
||||
}
|
||||
|
||||
if (GTK_IS_STACK(self->stack)) { // Stack is a Stack
|
||||
gtk_box_remove(GTK_BOX(self), GTK_WIDGET(self->stack)); // Destroy to free references
|
||||
}
|
||||
|
||||
self->artist_list = gtk_list_box_new(); // Create our artist list
|
||||
|
||||
gboolean list_created = GTK_IS_LIST_BOX(self->artist_list);
|
||||
|
||||
if (list_created) { // Successfully created our list
|
||||
gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(self->artist_list), TRUE);
|
||||
gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->artist_list), GTK_SELECTION_BROWSE);
|
||||
gtk_list_box_set_sort_func(GTK_LIST_BOX(self->artist_list), koto_page_music_local_sort_artists, NULL, NULL); // Add our sort function
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->scrolled_window), self->artist_list);
|
||||
gtk_widget_show(GTK_WIDGET(self->artist_list));
|
||||
}
|
||||
|
||||
gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(self->scrolled_window), 300);
|
||||
gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(self->scrolled_window), FALSE);
|
||||
gtk_widget_set_size_request(GTK_WIDGET(self->artist_list), 300, -1);
|
||||
|
||||
self->stack = gtk_stack_new(); // Create a new stack
|
||||
gtk_widget_set_hexpand(self->stack, TRUE);
|
||||
gboolean stack_created = GTK_IS_STACK(self->stack);
|
||||
|
||||
if (list_created && stack_created) {
|
||||
GHashTableIter artist_list_iter;
|
||||
gpointer artist_key;
|
||||
gpointer artist_data;
|
||||
|
||||
GHashTable *artists = koto_indexed_library_get_artists(self->lib); // Get the artists
|
||||
|
||||
g_hash_table_iter_init(&artist_list_iter, artists);
|
||||
while (g_hash_table_iter_next(&artist_list_iter, &artist_key, &artist_data)) { // For each of the music artists
|
||||
KotoIndexedArtist *artist = (KotoIndexedArtist*) artist_data; // Cast our data as a KotoIndexedArtist
|
||||
koto_page_music_local_add_artist(self, artist);
|
||||
}
|
||||
}
|
||||
|
||||
if (stack_created) { // Successfully created our stack
|
||||
gtk_box_append(GTK_BOX(self), self->stack);
|
||||
}
|
||||
|
||||
gtk_widget_show(GTK_WIDGET(self));
|
||||
}
|
||||
|
||||
int koto_page_music_local_sort_artists(GtkListBoxRow *artist1, GtkListBoxRow *artist2, gpointer user_data) {
|
||||
(void) user_data;
|
||||
KotoButton *artist1_btn = KOTO_BUTTON(gtk_list_box_row_get_child(artist1));
|
||||
KotoButton *artist2_btn = KOTO_BUTTON(gtk_list_box_row_get_child(artist2));
|
||||
|
||||
gchar *artist1_text;
|
||||
gchar *artist2_text;
|
||||
|
||||
g_object_get(artist1_btn, "button-text", &artist1_text, NULL);
|
||||
g_object_get(artist2_btn, "button-text", &artist2_text, NULL);
|
||||
|
||||
return g_utf8_collate(artist1_text, artist2_text);
|
||||
}
|
||||
|
||||
KotoPageMusicLocal* koto_page_music_local_new() {
|
||||
return g_object_new(KOTO_TYPE_PAGE_MUSIC_LOCAL,
|
||||
"orientation", GTK_ORIENTATION_HORIZONTAL,
|
||||
NULL
|
||||
);
|
||||
}
|
38
src/pages/music/music-local.h
Normal file
38
src/pages/music/music-local.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/* music-local.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 <gtk-4.0/gtk/gtk.h>
|
||||
#include "../../indexer/artist.h"
|
||||
#include "../../indexer/file-indexer.h"
|
||||
#include "artist-view.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define KOTO_TYPE_PAGE_MUSIC_LOCAL (koto_page_music_local_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (KotoPageMusicLocal, koto_page_music_local, KOTO, PAGE_MUSIC_LOCAL, GtkBox)
|
||||
|
||||
KotoPageMusicLocal* koto_page_music_local_new();
|
||||
void koto_page_music_local_add_artist(KotoPageMusicLocal *self, KotoIndexedArtist *artist);
|
||||
void koto_page_music_local_handle_artist_click(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
|
||||
void koto_page_music_local_set_library(KotoPageMusicLocal *self, KotoIndexedLibrary *lib);
|
||||
int koto_page_music_local_sort_artists(GtkListBoxRow *artist1, GtkListBoxRow *artist2, gpointer user_data);
|
||||
|
||||
G_END_DECLS
|
Loading…
Add table
Add a link
Reference in a new issue