Start implementation of database functionality, starting with table generation.

This database will be stored in the user's XDG_DATA_HOME directory under a folder called `com.github.joshstrobl.koto`.

Refactored Koto Track Listing into a dedicated Disc View component, break apart out CDs / Discs into relevant Disc View components, sorting them by the disc and showing a label if there are more than one discs / CDs.

Started using the row-activated event for our Artist GtkListBox instead of having press events on the artist buttons. Makes switching between artists far more reliable. Added a slide left / right stack animation and set its animation to 400ms so it's in the goldilocks zone (IMO) of not being too fast or too slow.

Fix a warning related to scale factor fetching in the PlayerBar.
This commit is contained in:
Joshua Strobl 2021-02-27 17:26:24 +02:00
parent 134b4d79a8
commit eac4940c77
17 changed files with 434 additions and 61 deletions

60
src/db/db.c Normal file
View file

@ -0,0 +1,60 @@
/* db.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 <sqlite3.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "db.h"
sqlite3 *koto_db = NULL;
void close_db() {
sqlite3_close(koto_db);
}
void create_db_tables() {
char *tables_creation_queries = "CREATE TABLE IF NOT EXISTS artists(id string UNIQUE, path string UNIQUE, type int, name string, art_path string);"
"CREATE TABLE IF NOT EXISTS albums(id string UNIQUE, path string UNIQUE, artist_id int, name string, art_path string);"
"CREATE TABLE IF NOT EXISTS tracks(id string UNIQUE, path string UNIQUE, type int, artist_id int, album_id int, file_name string, name string, disc int, position int);";
gchar *create_tables_errmsg = NULL;
int rc = sqlite3_exec(koto_db, tables_creation_queries, 0,0, &create_tables_errmsg);
if (rc != SQLITE_OK) {
g_critical("Failed to create required tables: %s", create_tables_errmsg);
}
}
void open_db() {
const gchar *data_home = g_get_user_data_dir();
gchar *data_dir = g_build_path(G_DIR_SEPARATOR_S, data_home, "com.github.joshstrobl.koto", NULL);
mkdir(data_home, 0755);
mkdir(data_dir, 0755);
chown(data_dir, getuid(), getgid());
gchar *db_path = g_build_filename(data_dir, "db", NULL); // Build out our path using XDG_DATA_HOME (e.g. .local/share/) + our namespace + db as the file name
int rc = sqlite3_open(db_path, &koto_db);
if (rc) {
g_critical("Failed to open or create database: %s", sqlite3_errmsg(koto_db));
return;
}
create_db_tables(); // Attempt to create our database tables
}

22
src/db/db.h Normal file
View file

@ -0,0 +1,22 @@
/* db.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.
*/
#include <sqlite3.h>
void close_db();
void open_db();

View file

@ -199,7 +199,7 @@ void koto_indexed_album_read_path(KotoIndexedAlbum *self, magic_t cookie, const
} 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 } 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
gchar *appended_slash_to_path = g_strdup_printf("%s%s", g_strdup(self->path), G_DIR_SEPARATOR_S); gchar *appended_slash_to_path = g_strdup_printf("%s%s", g_strdup(self->path), G_DIR_SEPARATOR_S);
gchar **possible_cd_split = g_strsplit(full_path, appended_slash_to_path, -1); // Split based on the album path gchar **possible_cd_split = g_strsplit(full_path, appended_slash_to_path, -1); // Split based on the album path
guint *cd = 0; guint *cd = (guint*) 1;
gchar *file_with_cd_sep = g_strdup(possible_cd_split[1]); // Duplicate gchar *file_with_cd_sep = g_strdup(possible_cd_split[1]); // Duplicate
gchar **split_on_cd = g_strsplit(file_with_cd_sep, G_DIR_SEPARATOR_S, -1); // Split based on separator (e.g. / ) gchar **split_on_cd = g_strsplit(file_with_cd_sep, G_DIR_SEPARATOR_S, -1); // Split based on separator (e.g. / )

View file

@ -268,7 +268,7 @@ void koto_indexed_file_set_file_name(KotoIndexedFile *self, gchar *new_file_name
} }
void koto_indexed_file_set_cd(KotoIndexedFile *self, guint cd) { void koto_indexed_file_set_cd(KotoIndexedFile *self, guint cd) {
if (cd <= 1) { // No change really if (cd == 0) { // No change really
return; return;
} }

View file

@ -98,7 +98,7 @@ void koto_playerbar_create_playback_details(KotoPlayerBar* bar) {
GtkIconTheme *default_icon_theme = gtk_icon_theme_get_for_display(gdk_display_get_default()); // Get the icon theme for this display 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) { if (default_icon_theme != NULL) {
gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(bar)); gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(bar->main));
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); 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 (GTK_IS_ICON_PAINTABLE(audio_paintable)) { if (GTK_IS_ICON_PAINTABLE(audio_paintable)) {

View file

@ -16,10 +16,13 @@
*/ */
#include <glib/gi18n.h> #include <glib/gi18n.h>
#include "db/db.h"
#include "koto-config.h" #include "koto-config.h"
#include "koto-window.h" #include "koto-window.h"
extern sqlite3 *koto_db;
static void on_activate (GtkApplication *app) { static void on_activate (GtkApplication *app) {
g_assert(GTK_IS_APPLICATION (app)); g_assert(GTK_IS_APPLICATION (app));
@ -33,6 +36,16 @@ static void on_activate (GtkApplication *app) {
gtk_window_present(window); gtk_window_present(window);
} }
static void on_shutdown(GtkApplication *app) {
(void) app;
if (koto_db != NULL) {
g_message("Have a db?");
}
close_db(); // Close the database
}
int main (int argc, char *argv[]) { int main (int argc, char *argv[]) {
g_autoptr(GtkApplication) app = NULL; g_autoptr(GtkApplication) app = NULL;
int ret; int ret;
@ -43,8 +56,10 @@ int main (int argc, char *argv[]) {
textdomain (GETTEXT_PACKAGE); textdomain (GETTEXT_PACKAGE);
gtk_init(); gtk_init();
open_db(); // Open our database
app = gtk_application_new ("com.github.joshstrobl.koto", G_APPLICATION_FLAGS_NONE); app = gtk_application_new ("com.github.joshstrobl.koto", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL); g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
g_signal_connect(app, "shutdown", G_CALLBACK(on_shutdown), NULL);
ret = g_application_run (G_APPLICATION (app), argc, argv); ret = g_application_run (G_APPLICATION (app), argc, argv);
return ret; return ret;

View file

@ -1,12 +1,14 @@
add_project_arguments('-Db_sanitize=address', language: 'c') add_project_arguments('-Db_sanitize=address', language: 'c')
koto_sources = [ koto_sources = [
'db/db.c',
'indexer/album.c', 'indexer/album.c',
'indexer/artist.c', 'indexer/artist.c',
'indexer/file.c', 'indexer/file.c',
'indexer/file-indexer.c', 'indexer/file-indexer.c',
'pages/music/album-view.c', 'pages/music/album-view.c',
'pages/music/artist-view.c', 'pages/music/artist-view.c',
'pages/music/disc-view.c',
'pages/music/music-local.c', 'pages/music/music-local.c',
'main.c', 'main.c',
'koto-button.c', 'koto-button.c',
@ -23,6 +25,7 @@ koto_deps = [
dependency('gio-2.0', version: '>= 2.66'), dependency('gio-2.0', version: '>= 2.66'),
dependency('gtk4', version: '>= 4.0'), dependency('gtk4', version: '>= 4.0'),
dependency('libmagic', version: '>=5.39'), dependency('libmagic', version: '>=5.39'),
dependency('sqlite3', version: '>=3.34'),
dependency('taglib_c', version: '>=1.11'), dependency('taglib_c', version: '>=1.11'),
] ]

View file

@ -19,8 +19,8 @@
#include <gtk-4.0/gtk/gtk.h> #include <gtk-4.0/gtk/gtk.h>
#include "../../indexer/album.h" #include "../../indexer/album.h"
#include "../../indexer/artist.h" #include "../../indexer/artist.h"
#include "../../koto-track-item.h"
#include "album-view.h" #include "album-view.h"
#include "disc-view.h"
#include "koto-config.h" #include "koto-config.h"
#include "koto-utils.h" #include "koto-utils.h"
@ -29,7 +29,7 @@ struct _KotoAlbumView {
KotoIndexedAlbum *album; KotoIndexedAlbum *album;
GtkWidget *main; GtkWidget *main;
GtkWidget *album_tracks_box; GtkWidget *album_tracks_box;
GtkWidget *tracks; GtkWidget *discs;
GtkWidget *album_label; GtkWidget *album_label;
GHashTable *cd_to_track_listbox; GHashTable *cd_to_track_listbox;
@ -71,13 +71,15 @@ static void koto_album_view_init(KotoAlbumView *self) {
gtk_widget_set_can_focus(self->main, FALSE); gtk_widget_set_can_focus(self->main, FALSE);
self->album_tracks_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); self->album_tracks_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
self->tracks = gtk_list_box_new(); // Create our list of our tracks self->discs = 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_list_box_set_selection_mode(GTK_LIST_BOX(self->discs), GTK_SELECTION_NONE);
gtk_widget_add_css_class(self->tracks, "track-list"); gtk_list_box_set_sort_func(GTK_LIST_BOX(self->discs), koto_album_view_sort_discs, NULL, NULL); // Ensure we can sort our discs
gtk_widget_set_size_request(self->tracks, 600, -1); gtk_widget_add_css_class(self->discs, "discs-list");
gtk_widget_set_can_focus(self->discs, FALSE);
gtk_widget_set_size_request(self->discs, 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->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 gtk_box_append(GTK_BOX(self->album_tracks_box), self->discs); // Add the discs list box to the albums tracks box
} }
GtkWidget* koto_album_view_get_main(KotoAlbumView *self) { GtkWidget* koto_album_view_get_main(KotoAlbumView *self) {
@ -134,40 +136,49 @@ void koto_album_view_set_album(KotoAlbumView *self, KotoIndexedAlbum *album) {
gtk_widget_set_halign(self->album_label, GTK_ALIGN_START); gtk_widget_set_halign(self->album_label, GTK_ALIGN_START);
gtk_box_prepend(GTK_BOX(self->album_tracks_box), self->album_label); // Prepend our new label to the album + tracks box gtk_box_prepend(GTK_BOX(self->album_tracks_box), self->album_label); // Prepend our new label to the album + tracks box
GList *t; GHashTable *discs = g_hash_table_new(g_str_hash, g_str_equal);
for (t = koto_indexed_album_get_files(self->album); t != NULL; t = t->next) { // For each file / track GList *tracks = koto_indexed_album_get_files(album); // Get the tracks for this album
KotoIndexedFile *file = (KotoIndexedFile*) t->data;
KotoTrackItem *track_item = koto_track_item_new(file); // Create our new track item for (guint i = 0; i < g_list_length(tracks); i++) {
gtk_list_box_prepend(GTK_LIST_BOX(self->tracks), GTK_WIDGET(track_item)); // Add to our tracks list box KotoIndexedFile *file = (KotoIndexedFile*) g_list_nth_data(tracks, i); // Get the
guint *disc_number;
g_object_get(file, "cd", &disc_number, NULL);
gchar *disc_num_as_str = g_strdup_printf("%u", GPOINTER_TO_UINT(disc_number));
if (g_hash_table_contains(discs, disc_num_as_str)) { // Already have this added
continue; // Skip
}
g_hash_table_insert(discs, disc_num_as_str, "0"); // Mark this disc number in the hash table
KotoDiscView *disc_view = koto_disc_view_new(album, disc_number);
gtk_list_box_append(GTK_LIST_BOX(self->discs), GTK_WIDGET(disc_view)); // Add the Disc View to the List Box
} }
if (g_list_length(g_hash_table_get_keys(discs)) == 1) { // Only have one album
GtkListBoxRow *row = gtk_list_box_get_row_at_index(GTK_LIST_BOX(self->discs), 0); // Get the first item
if (GTK_IS_LIST_BOX_ROW(row)) { // Is a row
koto_disc_view_set_disc_label_visible((KotoDiscView*) gtk_list_box_row_get_child(row), FALSE); // Get
}
}
g_hash_table_destroy(discs);
} }
int koto_album_view_sort_tracks(GtkListBoxRow *track1, GtkListBoxRow *track2, gpointer user_data) { int koto_album_view_sort_discs(GtkListBoxRow *disc1, GtkListBoxRow *disc2, gpointer user_data) {
(void) user_data; (void) user_data;
KotoTrackItem *track1_item = KOTO_TRACK_ITEM(gtk_list_box_row_get_child(track1)); KotoDiscView *disc1_item = KOTO_DISC_VIEW(gtk_list_box_row_get_child(disc1));
KotoTrackItem *track2_item = KOTO_TRACK_ITEM(gtk_list_box_row_get_child(track2)); KotoDiscView *disc2_item = KOTO_DISC_VIEW(gtk_list_box_row_get_child(disc2));
KotoIndexedFile *track1_file; guint disc1_num;
KotoIndexedFile *track2_file; guint disc2_num;
g_object_get(track1_item, "track", &track1_file, NULL); g_object_get(disc1_item, "disc", &disc1_num, NULL);
g_object_get(track2_item, "track", &track2_file, NULL); g_object_get(disc2_item, "disc", &disc2_num, NULL);
guint16 *track1_pos; if (disc1_num == disc2_num) { // Identical positions (like reported as 0)
guint16 *track2_pos; return 0;
} else if (disc1_num < disc2_num) {
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; return -1;
} else { } else {
return 1; return 1;

View file

@ -32,6 +32,6 @@ 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_set_album(KotoAlbumView *self, KotoIndexedAlbum *album); void koto_album_view_set_album(KotoAlbumView *self, KotoIndexedAlbum *album);
int koto_album_view_sort_tracks(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

218
src/pages/music/disc-view.c Normal file
View file

@ -0,0 +1,218 @@
/* disc-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 <gtk-4.0/gtk/gtk.h>
#include "../../indexer/album.h"
#include "../../koto-track-item.h"
#include "disc-view.h"
struct _KotoDiscView {
GtkBox parent_instance;
KotoIndexedAlbum *album;
GtkWidget *header;
GtkWidget *label;
GtkWidget *list;
guint *disc_number;
};
G_DEFINE_TYPE(KotoDiscView, koto_disc_view, GTK_TYPE_BOX);
enum {
PROP_0,
PROP_DISC,
PROP_ALBUM,
N_PROPERTIES
};
static GParamSpec *props[N_PROPERTIES] = { NULL, };
static void koto_disc_view_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec);
static void koto_disc_view_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec);
static void koto_disc_view_class_init(KotoDiscViewClass *c) {
GObjectClass *gobject_class;
gobject_class = G_OBJECT_CLASS(c);
gobject_class->set_property = koto_disc_view_set_property;
gobject_class->get_property = koto_disc_view_get_property;
props[PROP_DISC] = g_param_spec_uint(
"disc",
"Disc",
"Disc",
0,
G_MAXUINT16,
1,
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
);
props[PROP_ALBUM] = g_param_spec_object(
"album",
"Album",
"Album",
KOTO_TYPE_INDEXED_ALBUM,
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
);
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
}
static void koto_disc_view_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) {
KotoDiscView *self = KOTO_DISC_VIEW(obj);
switch (prop_id) {
case PROP_DISC:
g_value_set_uint(val, GPOINTER_TO_UINT(self->disc_number));
break;
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_disc_view_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec) {
KotoDiscView *self = KOTO_DISC_VIEW(obj);
switch (prop_id) {
case PROP_DISC:
koto_disc_view_set_disc_number(self, g_value_get_uint(val));
break;
case PROP_ALBUM:
koto_disc_view_set_album(self, (KotoIndexedAlbum*) g_value_get_object(val));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
}
}
static void koto_disc_view_init(KotoDiscView *self) {
gtk_widget_add_css_class(GTK_WIDGET(self), "disc-view");
gtk_widget_set_can_focus(GTK_WIDGET(self), FALSE);
self->header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_set_hexpand(self->header, TRUE);
gtk_widget_set_size_request(self->header, 16, -1);
GtkWidget *ico = gtk_image_new_from_icon_name("drive-optical-symbolic");
gtk_box_prepend(GTK_BOX(self->header), ico);
self->label = gtk_label_new(NULL); // Create an empty label
gtk_widget_set_halign(self->label, GTK_ALIGN_START);
gtk_widget_set_valign(self->label, GTK_ALIGN_CENTER);
gtk_box_append(GTK_BOX(self->header), self->label);
gtk_box_append(GTK_BOX(self), self->header);
}
void koto_disc_view_set_album(KotoDiscView *self, KotoIndexedAlbum *album) {
if (album == NULL) {
return;
}
if (self->album != NULL) {
g_free(self->album);
}
self->album = album;
if (GTK_IS_LIST_BOX(self->list)) { // Already have a listbox
gtk_box_remove(GTK_BOX(self), self->list); // Remove the box
g_object_unref(self->list); // Unref the list
}
self->list = gtk_list_box_new(); // Create our list of our tracks
gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->list), GTK_SELECTION_MULTIPLE);
gtk_list_box_set_sort_func(GTK_LIST_BOX(self->list), koto_album_view_sort_tracks, NULL, NULL); // Ensure we can sort our tracks
gtk_widget_add_css_class(self->list, "track-list");
gtk_widget_set_size_request(self->list, 600, -1);
gtk_box_append(GTK_BOX(self), self->list);
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;
guint *disc_number;
g_object_get(file, "cd", &disc_number, NULL); // get the disc number
if (GPOINTER_TO_UINT(self->disc_number) != GPOINTER_TO_UINT(disc_number)) { // Track does not belong to this CD
continue;
}
KotoTrackItem *track_item = koto_track_item_new(file); // Create our new track item
gtk_list_box_prepend(GTK_LIST_BOX(self->list), GTK_WIDGET(track_item)); // Add to our tracks list box
}
}
void koto_disc_view_set_disc_number(KotoDiscView *self, guint disc_number) {
if (disc_number == 0) {
return;
}
self->disc_number = GUINT_TO_POINTER(disc_number);
gchar *disc_label = g_strdup_printf("Disc %u", disc_number);
gtk_label_set_text(GTK_LABEL(self->label), disc_label); // Set the label
g_free(disc_label);
}
void koto_disc_view_set_disc_label_visible(KotoDiscView *self, gboolean visible) {
(visible) ? gtk_widget_show(self->header) : gtk_widget_hide(self->header);
}
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;
}
}
KotoDiscView* koto_disc_view_new(KotoIndexedAlbum *album, guint *disc_number) {
return g_object_new(KOTO_TYPE_DISC_VIEW,
"disc", disc_number,
"album", album,
"orientation", GTK_ORIENTATION_VERTICAL,
NULL
);
}

View file

@ -0,0 +1,36 @@
/* disc-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"
G_BEGIN_DECLS
#define KOTO_TYPE_DISC_VIEW (koto_disc_view_get_type())
G_DECLARE_FINAL_TYPE(KotoDiscView, koto_disc_view, KOTO, DISC_VIEW, GtkBox)
KotoDiscView* koto_disc_view_new(KotoIndexedAlbum *album, guint *disc);
void koto_disc_view_set_album(KotoDiscView *self, KotoIndexedAlbum *album);
void koto_disc_view_set_disc_label_visible(KotoDiscView *self, gboolean visible);
void koto_disc_view_set_disc_number(KotoDiscView *self, guint disc_number);
int koto_album_view_sort_tracks(GtkListBoxRow *track1, GtkListBoxRow *track2, gpointer user_data);
G_END_DECLS

View file

@ -117,17 +117,12 @@ void koto_page_music_local_add_artist(KotoPageMusicLocal *self, KotoIndexedArtis
KotoArtistView *artist_view = koto_artist_view_new(); // Create our new artist view KotoArtistView *artist_view = koto_artist_view_new(); // Create our new artist view
koto_artist_view_add_artist(artist_view, artist); // Add the artist 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); 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 koto_page_music_local_handle_artist_click(GtkListBox *box, GtkListBoxRow *row, gpointer data) {
(void) n_press; (void) x; (void) y; (void) box;
KotoPageMusicLocal *self = (KotoPageMusicLocal*) data; 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(gtk_list_box_row_get_child(row));
KotoButton *btn = KOTO_BUTTON(btn_widget);
gchar *artist_name; gchar *artist_name;
g_object_get(btn, "button-text", &artist_name, NULL); g_object_get(btn, "button-text", &artist_name, NULL);
@ -169,6 +164,7 @@ void koto_page_music_local_set_library(KotoPageMusicLocal *self, KotoIndexedLibr
gboolean list_created = GTK_IS_LIST_BOX(self->artist_list); gboolean list_created = GTK_IS_LIST_BOX(self->artist_list);
if (list_created) { // Successfully created our list if (list_created) { // Successfully created our list
g_signal_connect(GTK_LIST_BOX(self->artist_list), "row-activated", G_CALLBACK(koto_page_music_local_handle_artist_click), self);
gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(self->artist_list), TRUE); 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_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_list_box_set_sort_func(GTK_LIST_BOX(self->artist_list), koto_page_music_local_sort_artists, NULL, NULL); // Add our sort function
@ -181,6 +177,8 @@ void koto_page_music_local_set_library(KotoPageMusicLocal *self, KotoIndexedLibr
gtk_widget_set_size_request(GTK_WIDGET(self->artist_list), 300, -1); gtk_widget_set_size_request(GTK_WIDGET(self->artist_list), 300, -1);
self->stack = gtk_stack_new(); // Create a new stack self->stack = gtk_stack_new(); // Create a new stack
gtk_stack_set_transition_duration(GTK_STACK(self->stack), 400);
gtk_stack_set_transition_type(GTK_STACK(self->stack), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
gtk_widget_set_hexpand(self->stack, TRUE); gtk_widget_set_hexpand(self->stack, TRUE);
gboolean stack_created = GTK_IS_STACK(self->stack); gboolean stack_created = GTK_IS_STACK(self->stack);

View file

@ -31,7 +31,7 @@ G_DECLARE_FINAL_TYPE (KotoPageMusicLocal, koto_page_music_local, KOTO, PAGE_MUSI
KotoPageMusicLocal* koto_page_music_local_new(); KotoPageMusicLocal* koto_page_music_local_new();
void koto_page_music_local_add_artist(KotoPageMusicLocal *self, KotoIndexedArtist *artist); 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_handle_artist_click(GtkListBox *box, GtkListBoxRow *row, gpointer data);
void koto_page_music_local_set_library(KotoPageMusicLocal *self, KotoIndexedLibrary *lib); 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); int koto_page_music_local_sort_artists(GtkListBoxRow *artist1, GtkListBoxRow *artist2, gpointer user_data);

20
theme/_disc-view.scss Normal file
View file

@ -0,0 +1,20 @@
@import "vars";
.disc-view {
& > box { // Horizontal box with image and disc labe
color: #ccc;
margin: 10px 0;
}
& .track-list {
& > row {
&:nth-child(odd):not(:hover) {
background-color: $midnight;
}
&:nth-child(even), &:hover {
background-color: $grey;
}
}
}
}

View file

@ -1,11 +1,12 @@
@import 'pages/music-local.scss'; @import 'pages/music-local.scss';
@import 'button'; @import 'button';
@import 'vars'; @import 'disc-view';
@import 'expander'; @import 'expander';
@import 'player-bar'; @import 'player-bar';
@import 'primary-nav'; @import 'primary-nav';
@import 'track-item'; @import 'track-item';
@import 'vars';
window { window {
background-color: $grey; background-color: $grey;

View file

@ -11,11 +11,12 @@ theme = custom_target('Theme generation',
depend_files: files([ depend_files: files([
'pages/_music-local.scss', 'pages/_music-local.scss',
'_button.scss', '_button.scss',
'_vars.scss', '_disc-view.scss',
'_expander.scss', '_expander.scss',
'_player-bar.scss', '_player-bar.scss',
'_primary-nav.scss', '_primary-nav.scss',
'_track-item.scss', '_track-item.scss',
'_vars.scss',
]), ]),
build_by_default: true, build_by_default: true,
) )

View file

@ -37,18 +37,6 @@
font-weight: 900; font-weight: 900;
padding: $halvedpadding 0; padding: $halvedpadding 0;
} }
& > .track-list {
& > row {
&:nth-child(odd):not(:hover) {
background-color: $midnight;
}
&:nth-child(even), &:hover {
background-color: $grey;
}
}
}
} }
} }
} }