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:
parent
134b4d79a8
commit
eac4940c77
17 changed files with 434 additions and 61 deletions
60
src/db/db.c
Normal file
60
src/db/db.c
Normal 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
22
src/db/db.h
Normal 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();
|
||||||
|
|
|
@ -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. / )
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
15
src/main.c
15
src/main.c
|
@ -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;
|
||||||
|
|
|
@ -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'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
218
src/pages/music/disc-view.c
Normal 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
|
||||||
|
);
|
||||||
|
}
|
36
src/pages/music/disc-view.h
Normal file
36
src/pages/music/disc-view.h
Normal 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
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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
20
theme/_disc-view.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue